# 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] = preload("res://fx/effect_space.gd").position(Vector3( float(data.decode_float(base)), float(data.decode_float(base + 4)), float(data.decode_float(base + 8)))) 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