Files
open-wc/addons/mpq_extractor/loaders/m2_builder.gd
T

128 lines
4.3 KiB
GDScript

## Converts raw M2Loader data into a Godot Node3D scene tree.
## Usage:
## var data = M2Loader.new().load_m2(abs_path)
## var node = M2Builder.build(data, "res://data/extracted")
## add_child(node)
class_name M2Builder
static var _texture_cache: Dictionary = {}
static func build(data: Dictionary, extracted_dir: String = "") -> Node3D:
var root := Node3D.new()
root.name = "M2"
var verts: PackedVector3Array = data.get("vertices", PackedVector3Array())
var normals: PackedVector3Array = data.get("normals", PackedVector3Array())
var uvs: PackedVector2Array = data.get("uvs", PackedVector2Array())
var indices: PackedInt32Array = data.get("indices", PackedInt32Array())
var batches: Array = data.get("batches", [])
if verts.is_empty() or indices.is_empty() or batches.is_empty():
return root
var textures: PackedStringArray = data.get("textures", PackedStringArray())
var materials: Array = data.get("materials", [])
var tex_combos: PackedInt32Array = data.get("texture_combos", PackedInt32Array())
var mesh := ArrayMesh.new()
for batch_variant in batches:
if not (batch_variant is Dictionary):
continue
var batch: Dictionary = batch_variant
var idx_start: int = int(batch.get("index_start", 0))
var idx_count: int = int(batch.get("index_count", 0))
var mat_id: int = int(batch.get("material_id", -1))
var tc_idx: int = int(batch.get("texture_combo_index", -1))
if idx_count <= 0 or idx_start + idx_count > indices.size():
continue
var batch_indices := indices.slice(idx_start, idx_start + idx_count)
if batch_indices.is_empty():
continue
var arrays := []
arrays.resize(Mesh.ARRAY_MAX)
arrays[Mesh.ARRAY_VERTEX] = verts
if normals.size() == verts.size():
arrays[Mesh.ARRAY_NORMAL] = normals
if uvs.size() == verts.size():
arrays[Mesh.ARRAY_TEX_UV] = uvs
arrays[Mesh.ARRAY_INDEX] = batch_indices
mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, arrays)
var surf_idx := mesh.get_surface_count() - 1
var mat_def: Dictionary = materials[mat_id] if mat_id >= 0 and mat_id < materials.size() else {}
mesh.surface_set_material(surf_idx, _build_material(mat_def, tc_idx, textures, tex_combos, extracted_dir))
if mesh.get_surface_count() == 0:
return root
var mi := MeshInstance3D.new()
mi.name = "Mesh"
mi.mesh = mesh
root.add_child(mi)
return root
static func _build_material(
mat_def: Dictionary,
texture_combo_index: int,
textures: PackedStringArray,
tex_combos: PackedInt32Array,
extracted_dir: String) -> StandardMaterial3D:
var mat := StandardMaterial3D.new()
mat.cull_mode = BaseMaterial3D.CULL_DISABLED
mat.roughness = 0.85
mat.metallic = 0.0
mat.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
mat.specular_mode = BaseMaterial3D.SPECULAR_DISABLED
mat.vertex_color_use_as_albedo = false
var blend_mode: int = mat_def.get("blend_mode", 0)
match blend_mode:
0: mat.transparency = BaseMaterial3D.TRANSPARENCY_DISABLED
1: mat.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA_SCISSOR
_: mat.transparency = BaseMaterial3D.TRANSPARENCY_ALPHA
if texture_combo_index >= 0 and texture_combo_index < tex_combos.size():
var tex_idx: int = tex_combos[texture_combo_index]
if tex_idx >= 0 and tex_idx < textures.size():
var tex_path: String = str(textures[tex_idx]).replace("\\", "/")
if not tex_path.is_empty():
var tex := _load_texture(tex_path, extracted_dir)
if tex:
mat.albedo_texture = tex
mat.texture_filter = BaseMaterial3D.TEXTURE_FILTER_LINEAR_WITH_MIPMAPS_ANISOTROPIC
return mat
static func _load_texture(rel_path: String, extracted_dir: String) -> Texture2D:
if rel_path.is_empty() or extracted_dir.is_empty():
return null
var abs_path := ProjectSettings.globalize_path(extracted_dir.path_join(rel_path))
if _texture_cache.has(abs_path):
return _texture_cache[abs_path]
if not ClassDB.class_exists("BLPLoader"):
return null
var loader = ClassDB.instantiate("BLPLoader")
if loader == null:
return null
var img: Image = loader.call("load_image", abs_path)
if img == null or img.is_empty():
_texture_cache[abs_path] = null
return null
img.generate_mipmaps()
var tex := ImageTexture.create_from_image(img)
_texture_cache[abs_path] = tex
return tex