first commit
This commit is contained in:
@@ -0,0 +1,164 @@
|
||||
## Converts raw WMOLoader data into a Godot Node3D scene tree.
|
||||
## Usage:
|
||||
## var data = WMOLoader.new().load_wmo(abs_path)
|
||||
## var node = WMOBuilder.build(data, "res://extracted")
|
||||
## add_child(node)
|
||||
class_name WMOBuilder
|
||||
|
||||
static var _texture_cache: Dictionary = {}
|
||||
|
||||
# Returns a Node3D containing one MeshInstance3D per WMO group.
|
||||
static func build(data: Dictionary, extracted_dir: String = "") -> Node3D:
|
||||
var root := Node3D.new()
|
||||
root.name = "WMO"
|
||||
|
||||
if not data.has("groups"):
|
||||
return root
|
||||
|
||||
var textures: PackedStringArray = data.get("textures", PackedStringArray())
|
||||
var materials: Array = data.get("materials", [])
|
||||
|
||||
# Build Godot materials from WMO material definitions
|
||||
var godot_materials: Array[StandardMaterial3D] = []
|
||||
for mat_def in materials:
|
||||
godot_materials.append(_build_material(mat_def, textures, extracted_dir))
|
||||
|
||||
# Build one MeshInstance3D per group
|
||||
var groups: Array = data.get("groups", [])
|
||||
for gi in groups.size():
|
||||
var g: Dictionary = groups[gi]
|
||||
if g.is_empty():
|
||||
continue
|
||||
var mesh_inst := _build_group_mesh(g, godot_materials)
|
||||
mesh_inst.name = "Group_%d" % gi
|
||||
root.add_child(mesh_inst)
|
||||
|
||||
return root
|
||||
|
||||
|
||||
static func _build_group_mesh(
|
||||
g: Dictionary,
|
||||
godot_mats: Array[StandardMaterial3D]) -> MeshInstance3D:
|
||||
|
||||
var verts: PackedVector3Array = g.get("vertices", PackedVector3Array())
|
||||
var normals: PackedVector3Array = g.get("normals", PackedVector3Array())
|
||||
var uvs: PackedVector2Array = g.get("uvs", PackedVector2Array())
|
||||
var colors: PackedColorArray = g.get("colors", PackedColorArray())
|
||||
var indices: PackedInt32Array = g.get("indices", PackedInt32Array())
|
||||
var batches: Array = g.get("batches", [])
|
||||
var mesh := ArrayMesh.new()
|
||||
if verts.is_empty() or indices.is_empty():
|
||||
var empty := MeshInstance3D.new()
|
||||
empty.mesh = mesh
|
||||
return empty
|
||||
|
||||
if batches.is_empty():
|
||||
batches = [{
|
||||
"index_start": 0,
|
||||
"index_count": indices.size(),
|
||||
"material_id": -1,
|
||||
}]
|
||||
|
||||
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))
|
||||
if idx_count <= 0 or idx_start >= indices.size():
|
||||
continue
|
||||
|
||||
var batch_indices := PackedInt32Array()
|
||||
for i in range(idx_start, mini(idx_start + idx_count, indices.size()), 3):
|
||||
if i + 2 >= indices.size():
|
||||
break
|
||||
# Reverse winding for the current local basis conversion.
|
||||
batch_indices.append(indices[i + 0])
|
||||
batch_indices.append(indices[i + 2])
|
||||
batch_indices.append(indices[i + 1])
|
||||
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
|
||||
if colors.size() == verts.size():
|
||||
arrays[Mesh.ARRAY_COLOR] = colors
|
||||
arrays[Mesh.ARRAY_INDEX] = batch_indices
|
||||
|
||||
mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, arrays)
|
||||
|
||||
var surf_idx := mesh.get_surface_count() - 1
|
||||
if mat_id >= 0 and mat_id < godot_mats.size():
|
||||
mesh.surface_set_material(surf_idx, godot_mats[mat_id])
|
||||
|
||||
var mi := MeshInstance3D.new()
|
||||
mi.mesh = mesh
|
||||
return mi
|
||||
|
||||
|
||||
static func _build_material(
|
||||
mat_def: Dictionary,
|
||||
textures: PackedStringArray,
|
||||
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
|
||||
|
||||
# Texture is loaded later by the scene assembler (needs BLP→Image conversion)
|
||||
# Store the path as metadata for the assembler to pick up
|
||||
var tex0_id: int = mat_def.get("texture0", -1)
|
||||
if tex0_id >= 0 and tex0_id < textures.size():
|
||||
var tex_path: String = str(textures[tex0_id]).replace("\\", "/")
|
||||
mat.set_meta("texture0_path", tex_path)
|
||||
var tex := _load_texture(tex_path, extracted_dir)
|
||||
if tex:
|
||||
mat.albedo_texture = tex
|
||||
mat.texture_filter = BaseMaterial3D.TEXTURE_FILTER_LINEAR_WITH_MIPMAPS_ANISOTROPIC
|
||||
|
||||
mat.set_meta("wow_flags", mat_def.get("flags", 0))
|
||||
mat.set_meta("wow_shader", mat_def.get("shader", 0))
|
||||
|
||||
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.replace("\\", "/")))
|
||||
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
|
||||
Reference in New Issue
Block a user