198 lines
6.3 KiB
GDScript
198 lines
6.3 KiB
GDScript
# Mde (P5) —— ClientVS22 EffectMesh 的 .mde 二进制读取器。
|
||
#
|
||
# 40250/ClientVS22/source/EffectLib/EffectMesh.cpp 支持两种布局:
|
||
# EffectData —— v1:每个几何体固定顶点/索引/UV 数量,每帧只有 visibility;
|
||
# MDEData002 —— v2:每帧带 changed/visibility 以及三组数量。
|
||
# 参考端把索引顶点展开成 TPTVertex,并把 UV.y 取反;这里同样展开,
|
||
# 同时把客户端厘米坐标换成 Godot 的米坐标。
|
||
extends RefCounted
|
||
|
||
const HEADER_SIZE := 11
|
||
const GEOMETRY_HEADER_SIZE := 160 # object name 32 + diffuse path 128
|
||
const MAX_GEOMETRIES := 256
|
||
const MAX_FRAMES := 4096
|
||
const MAX_ELEMENTS := 1_000_000
|
||
|
||
var last_error := ""
|
||
|
||
func parse_file(path: String) -> Dictionary:
|
||
last_error = ""
|
||
if not FileAccess.file_exists(path):
|
||
last_error = "no such file: " + path
|
||
return {}
|
||
return parse_bytes(FileAccess.get_file_as_bytes(path))
|
||
|
||
func parse_bytes(data: PackedByteArray) -> Dictionary:
|
||
last_error = ""
|
||
if data.size() < HEADER_SIZE + 8:
|
||
last_error = "truncated header"
|
||
return {}
|
||
|
||
var header := _fixed_string(data, 0, HEADER_SIZE)
|
||
var version := 0
|
||
if header == "EffectData":
|
||
version = 1
|
||
elif header == "MDEData002":
|
||
version = 2
|
||
else:
|
||
last_error = "unknown header: " + header
|
||
return {}
|
||
|
||
var offset := HEADER_SIZE
|
||
var geometry_count := int(data.decode_s32(offset))
|
||
offset += 4
|
||
var frame_count := int(data.decode_s32(offset))
|
||
offset += 4
|
||
if geometry_count < 1 or geometry_count > MAX_GEOMETRIES:
|
||
last_error = "invalid geometry count: %d" % geometry_count
|
||
return {}
|
||
if frame_count < 1 or frame_count > MAX_FRAMES:
|
||
last_error = "invalid frame count: %d" % frame_count
|
||
return {}
|
||
|
||
var geometries: Array = []
|
||
for geometry_index in geometry_count:
|
||
if not _has(data, offset, GEOMETRY_HEADER_SIZE):
|
||
last_error = "truncated geometry header %d" % geometry_index
|
||
return {}
|
||
var name := _fixed_string(data, offset, 32)
|
||
offset += 32
|
||
var diffuse := _fixed_string(data, offset, 128)
|
||
offset += 128
|
||
|
||
var fixed_vertex_count := 0
|
||
var fixed_index_count := 0
|
||
var fixed_uv_count := 0
|
||
if version == 1:
|
||
if not _has(data, offset, 12):
|
||
last_error = "truncated v1 geometry counts %d" % geometry_index
|
||
return {}
|
||
fixed_vertex_count = int(data.decode_u32(offset))
|
||
fixed_index_count = int(data.decode_u32(offset + 4))
|
||
fixed_uv_count = int(data.decode_u32(offset + 8))
|
||
offset += 12
|
||
if not _valid_counts(fixed_vertex_count, fixed_index_count, fixed_uv_count):
|
||
last_error = "invalid v1 geometry counts %d" % geometry_index
|
||
return {}
|
||
|
||
var frames: Array = []
|
||
for frame_index in frame_count:
|
||
var changed := 0
|
||
var visibility := 1.0
|
||
var vertex_count := fixed_vertex_count
|
||
var index_count := fixed_index_count
|
||
var uv_count := fixed_uv_count
|
||
if version == 2:
|
||
if not _has(data, offset, 17): # byte + float + 3 x DWORD
|
||
last_error = "truncated v2 frame header %d/%d" % [geometry_index, frame_index]
|
||
return {}
|
||
changed = int(data.decode_u8(offset))
|
||
offset += 1
|
||
visibility = float(data.decode_float(offset))
|
||
offset += 4
|
||
vertex_count = int(data.decode_u32(offset))
|
||
offset += 4
|
||
index_count = int(data.decode_u32(offset))
|
||
offset += 4
|
||
uv_count = int(data.decode_u32(offset))
|
||
offset += 4
|
||
else:
|
||
if not _has(data, offset, 4):
|
||
last_error = "truncated v1 frame header %d/%d" % [geometry_index, frame_index]
|
||
return {}
|
||
visibility = float(data.decode_float(offset))
|
||
offset += 4
|
||
if not _valid_counts(vertex_count, index_count, uv_count):
|
||
last_error = "invalid frame counts %d/%d" % [geometry_index, frame_index]
|
||
return {}
|
||
|
||
var bytes_needed := vertex_count * 12 + index_count * 4 + uv_count * 8 + index_count * 4
|
||
if not _has(data, offset, bytes_needed):
|
||
last_error = "truncated frame data %d/%d" % [geometry_index, frame_index]
|
||
return {}
|
||
|
||
var positions := PackedVector3Array()
|
||
positions.resize(vertex_count)
|
||
for i in vertex_count:
|
||
var base := offset + i * 12
|
||
positions[i] = Vector3(
|
||
float(data.decode_float(base)),
|
||
float(data.decode_float(base + 4)),
|
||
float(data.decode_float(base + 8))) * 0.01
|
||
offset += vertex_count * 12
|
||
|
||
var indices := PackedInt32Array()
|
||
indices.resize(index_count)
|
||
for i in index_count:
|
||
indices[i] = int(data.decode_s32(offset + i * 4))
|
||
offset += index_count * 4
|
||
|
||
var uvs := PackedVector2Array()
|
||
uvs.resize(uv_count)
|
||
for i in uv_count:
|
||
var base := offset + i * 8
|
||
uvs[i] = Vector2(
|
||
float(data.decode_float(base)),
|
||
-float(data.decode_float(base + 4)))
|
||
offset += uv_count * 8
|
||
|
||
var texture_indices := PackedInt32Array()
|
||
texture_indices.resize(index_count)
|
||
for i in index_count:
|
||
texture_indices[i] = int(data.decode_s32(offset + i * 4))
|
||
offset += index_count * 4
|
||
|
||
# EffectMesh.cpp renders a non-indexed TPTVertex array. Keep the
|
||
# same order so a frame is directly usable as a TriangleList.
|
||
var render_vertices := PackedVector3Array()
|
||
var render_uvs := PackedVector2Array()
|
||
render_vertices.resize(index_count)
|
||
render_uvs.resize(index_count)
|
||
for i in index_count:
|
||
var vi := indices[i]
|
||
var ti := texture_indices[i]
|
||
if vi < 0 or vi >= positions.size() or ti < 0 or ti >= uvs.size():
|
||
last_error = "index out of range %d/%d" % [geometry_index, frame_index]
|
||
return {}
|
||
render_vertices[i] = positions[vi]
|
||
render_uvs[i] = uvs[ti]
|
||
frames.append({
|
||
"changed": changed,
|
||
"visibility": visibility,
|
||
"vertex_count": vertex_count,
|
||
"index_count": index_count,
|
||
"uv_count": uv_count,
|
||
"vertices": render_vertices,
|
||
"uvs": render_uvs,
|
||
})
|
||
|
||
geometries.append({
|
||
"name": name,
|
||
"diffuse": diffuse,
|
||
"frames": frames,
|
||
})
|
||
|
||
return {
|
||
"version": version,
|
||
"geometry_count": geometry_count,
|
||
"frame_count": frame_count,
|
||
"geometries": geometries,
|
||
}
|
||
|
||
func _valid_counts(vertex_count: int, index_count: int, uv_count: int) -> bool:
|
||
return vertex_count >= 0 and index_count >= 0 and uv_count >= 0 \
|
||
and vertex_count <= MAX_ELEMENTS and index_count <= MAX_ELEMENTS \
|
||
and uv_count <= MAX_ELEMENTS
|
||
|
||
func _has(data: PackedByteArray, offset: int, length: int) -> bool:
|
||
return offset >= 0 and length >= 0 and offset <= data.size() - length
|
||
|
||
func _fixed_string(data: PackedByteArray, offset: int, length: int) -> String:
|
||
var out := ""
|
||
for i in length:
|
||
var value := int(data[offset + i])
|
||
if value == 0:
|
||
break
|
||
out += String.chr(value)
|
||
return out
|