523 lines
18 KiB
GDScript
523 lines
18 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
|
|
|
|
const WOW_M2_MATERIAL := preload("res://addons/mpq_extractor/loaders/wow_m2_material.gd")
|
|
const MATERIAL_FORMAT_VERSION := 1
|
|
|
|
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 uvs2: PackedVector2Array = data.get("uvs2", 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 texture_types: PackedInt32Array = data.get("texture_types", PackedInt32Array())
|
|
var texture_flags: PackedInt32Array = data.get("texture_flags", PackedInt32Array())
|
|
var materials: Array = data.get("materials", [])
|
|
var tex_combos: PackedInt32Array = data.get("texture_combos", PackedInt32Array())
|
|
var tex_coord_combos: PackedInt32Array = data.get("texture_coord_combos", PackedInt32Array())
|
|
var tex_weight_combos: PackedInt32Array = data.get("texture_weight_combos", PackedInt32Array())
|
|
var tex_transform_combos: PackedInt32Array = data.get("texture_transform_combos", PackedInt32Array())
|
|
var tex_combiner_combos: PackedInt32Array = data.get("texture_combiner_combos", PackedInt32Array())
|
|
var m2_colors: Array = data.get("m2_colors", [])
|
|
var tex_weights: PackedFloat32Array = data.get("texture_weights", PackedFloat32Array())
|
|
var tex_transforms: Array = data.get("texture_transforms", [])
|
|
var m2_flags: int = int(data.get("m2_flags", 0))
|
|
var model_path: String = str(data.get("model_path", ""))
|
|
|
|
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
|
|
if uvs2.size() == verts.size():
|
|
arrays[Mesh.ARRAY_TEX_UV2] = uvs2
|
|
arrays[Mesh.ARRAY_INDEX] = batch_indices
|
|
|
|
var surface_flags := _apply_billboard_custom_data(arrays, batch, verts.size())
|
|
mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, arrays, [], {}, surface_flags)
|
|
|
|
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,
|
|
texture_types,
|
|
texture_flags,
|
|
tex_combos,
|
|
tex_coord_combos,
|
|
tex_weight_combos,
|
|
tex_transform_combos,
|
|
tex_combiner_combos,
|
|
m2_colors,
|
|
tex_weights,
|
|
tex_transforms,
|
|
m2_flags,
|
|
extracted_dir,
|
|
model_path,
|
|
batch))
|
|
|
|
if mesh.get_surface_count() == 0:
|
|
return root
|
|
mesh.set_meta("wow_m2_material_refresh_version", MATERIAL_FORMAT_VERSION)
|
|
mesh.set_meta("wow_m2_source_path", model_path)
|
|
|
|
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,
|
|
texture_types: PackedInt32Array,
|
|
texture_flags: PackedInt32Array,
|
|
tex_combos: PackedInt32Array,
|
|
tex_coord_combos: PackedInt32Array,
|
|
tex_weight_combos: PackedInt32Array,
|
|
tex_transform_combos: PackedInt32Array,
|
|
tex_combiner_combos: PackedInt32Array,
|
|
m2_colors: Array,
|
|
tex_weights: PackedFloat32Array,
|
|
tex_transforms: Array,
|
|
m2_flags: int,
|
|
extracted_dir: String,
|
|
model_path: String,
|
|
batch: Dictionary = {}) -> Material:
|
|
|
|
var flags: int = int(mat_def.get("flags", 0))
|
|
var blend_mode: int = mat_def.get("blend_mode", 0)
|
|
var tex_flags := 0
|
|
var tex: Texture2D = null
|
|
var tex2: Texture2D = null
|
|
var tex3: Texture2D = null
|
|
var tex4: Texture2D = null
|
|
var tex2_flags := 0
|
|
var tex3_flags := 0
|
|
var tex4_flags := 0
|
|
|
|
var resolved0 := _resolve_combo_texture(texture_combo_index, textures, texture_types, texture_flags, tex_combos, extracted_dir, model_path)
|
|
tex = resolved0.get("texture", null)
|
|
tex_flags = int(resolved0.get("flags", 0))
|
|
var texture_count := maxi(1, int(batch.get("texture_count", 1)))
|
|
if texture_count > 1:
|
|
var resolved1 := _resolve_combo_texture(texture_combo_index + 1, textures, texture_types, texture_flags, tex_combos, extracted_dir, model_path)
|
|
tex2 = resolved1.get("texture", null)
|
|
tex2_flags = int(resolved1.get("flags", 0))
|
|
if texture_count > 2:
|
|
var resolved2 := _resolve_combo_texture(texture_combo_index + 2, textures, texture_types, texture_flags, tex_combos, extracted_dir, model_path)
|
|
tex3 = resolved2.get("texture", null)
|
|
tex3_flags = int(resolved2.get("flags", 0))
|
|
if texture_count > 3:
|
|
var resolved3 := _resolve_combo_texture(texture_combo_index + 3, textures, texture_types, texture_flags, tex_combos, extracted_dir, model_path)
|
|
tex4 = resolved3.get("texture", null)
|
|
tex4_flags = int(resolved3.get("flags", 0))
|
|
|
|
var combiner := _resolve_combiner_state(blend_mode, texture_count, batch, tex_coord_combos, tex_combiner_combos, m2_flags)
|
|
combiner["mesh_color"] = _resolve_mesh_color(batch, m2_colors, tex_weights, tex_weight_combos)
|
|
combiner["tex_sample_alpha"] = _resolve_texture_sample_alpha(batch, tex_weights, tex_weight_combos)
|
|
combiner["stage0_uv_transform"] = _resolve_texture_transform(batch, 0, tex_transform_combos, tex_transforms)
|
|
combiner["stage1_uv_transform"] = _resolve_texture_transform(batch, 1, tex_transform_combos, tex_transforms)
|
|
combiner["stage2_uv_transform"] = _resolve_texture_transform(batch, 2, tex_transform_combos, tex_transforms)
|
|
combiner["stage3_uv_transform"] = _resolve_texture_transform(batch, 3, tex_transform_combos, tex_transforms)
|
|
combiner["stage0_uv_scroll"] = _resolve_texture_scroll(batch, 0, tex_transform_combos, tex_transforms)
|
|
combiner["stage1_uv_scroll"] = _resolve_texture_scroll(batch, 1, tex_transform_combos, tex_transforms)
|
|
combiner["stage2_uv_scroll"] = _resolve_texture_scroll(batch, 2, tex_transform_combos, tex_transforms)
|
|
combiner["stage3_uv_scroll"] = _resolve_texture_scroll(batch, 3, tex_transform_combos, tex_transforms)
|
|
combiner["vertex_shader_id"] = _m2_vertex_shader_id(texture_count, int(batch.get("shader_id", 0)))
|
|
combiner["pixel_shader_id"] = _m2_pixel_shader_id(texture_count, int(batch.get("shader_id", 0)))
|
|
combiner["priority_plane"] = int(batch.get("priority_plane", 0))
|
|
combiner["billboard_enabled"] = bool(batch.get("has_billboard", false)) or int(batch.get("billboard_vertex_count", 0)) > 0
|
|
if _is_soft_waterfall_material(model_path, blend_mode, combiner):
|
|
combiner["effect_alpha_scale"] = 1.12
|
|
combiner["effect_alpha_power"] = 0.92
|
|
combiner["uv_edge_fade_strength"] = 0.22
|
|
|
|
var mat := WOW_M2_MATERIAL.build(
|
|
tex,
|
|
tex2,
|
|
tex3,
|
|
tex4,
|
|
flags,
|
|
blend_mode,
|
|
tex_flags,
|
|
tex2_flags,
|
|
tex3_flags,
|
|
tex4_flags,
|
|
model_path.get_file().get_basename(),
|
|
combiner)
|
|
mat.set_meta("wow_flags", flags)
|
|
mat.set_meta("wow_blend_mode", blend_mode)
|
|
mat.set_meta("wow_m2_shader_id", int(batch.get("shader_id", 0)))
|
|
mat.set_meta("wow_m2_vertex_shader_id", int(combiner.get("vertex_shader_id", 0)))
|
|
mat.set_meta("wow_m2_pixel_shader_id", int(combiner.get("pixel_shader_id", 0)))
|
|
mat.set_meta("wow_priority_plane", int(batch.get("priority_plane", 0)))
|
|
mat.set_meta("wow_m2_billboard_enabled", bool(combiner.get("billboard_enabled", false)))
|
|
mat.set_meta("wow_m2_billboard_bone_flags", int(batch.get("billboard_bone_flags", 0)))
|
|
return mat
|
|
|
|
|
|
static func _apply_billboard_custom_data(arrays: Array, batch: Dictionary, vertex_count: int) -> int:
|
|
var billboard_data: PackedFloat32Array = batch.get("billboard_data", PackedFloat32Array())
|
|
if billboard_data.size() != vertex_count * 4:
|
|
return 0
|
|
arrays[Mesh.ARRAY_CUSTOM0] = billboard_data
|
|
return int(Mesh.ARRAY_CUSTOM_RGBA_FLOAT) << int(Mesh.ARRAY_FORMAT_CUSTOM0_SHIFT)
|
|
|
|
|
|
static func _is_soft_waterfall_material(model_path: String, blend_mode: int, combiner: Dictionary) -> bool:
|
|
if blend_mode != 2:
|
|
return false
|
|
var lower_path := model_path.to_lower()
|
|
if not lower_path.contains("waterfall"):
|
|
return false
|
|
var scroll0: Vector2 = combiner.get("stage0_uv_scroll", Vector2.ZERO)
|
|
var scroll1: Vector2 = combiner.get("stage1_uv_scroll", Vector2.ZERO)
|
|
return scroll0.length_squared() > 0.000001 or scroll1.length_squared() > 0.000001
|
|
|
|
|
|
static func _m2_vertex_shader_id(texture_count: int, shader_id: int) -> int:
|
|
if (shader_id & 0x8000) != 0:
|
|
return _m2_shader_array_vertex_id(shader_id & 0x7fff)
|
|
if texture_count == 1:
|
|
if (shader_id & 0x80) != 0:
|
|
return 1 # Diffuse_Env
|
|
if (shader_id & 0x4000) != 0:
|
|
return 10 # Diffuse_T2
|
|
return 0 # Diffuse_T1
|
|
|
|
if (shader_id & 0x80) != 0:
|
|
if (shader_id & 0x8) != 0:
|
|
return 5 # Diffuse_Env_Env
|
|
return 4 # Diffuse_Env_T1
|
|
if (shader_id & 0x8) != 0:
|
|
return 3 # Diffuse_T1_Env
|
|
if (shader_id & 0x4000) != 0:
|
|
return 2 # Diffuse_T1_T2
|
|
return 7 # Diffuse_T1_T1
|
|
|
|
|
|
static func _m2_pixel_shader_id(texture_count: int, shader_id: int) -> int:
|
|
if (shader_id & 0x8000) != 0:
|
|
return _m2_shader_array_pixel_id(shader_id & 0x7fff)
|
|
if texture_count == 1:
|
|
return 1 if (shader_id & 0x70) != 0 else 0
|
|
|
|
if (shader_id & 0x70) != 0:
|
|
match shader_id & 7:
|
|
3:
|
|
return 8 # Combiners_Mod_Add
|
|
4:
|
|
return 7 # Combiners_Mod_Mod2x
|
|
6:
|
|
return 9 # Combiners_Mod_Mod2xNA
|
|
7:
|
|
return 10 # Combiners_Mod_AddNA
|
|
_:
|
|
return 6 # Combiners_Mod_Mod
|
|
|
|
match shader_id & 7:
|
|
0:
|
|
return 5 # Combiners_Opaque_Opaque
|
|
3, 7:
|
|
return 13 # Combiners_Opaque_AddAlpha
|
|
4:
|
|
return 3 # Combiners_Opaque_Mod2x
|
|
6:
|
|
return 4 # Combiners_Opaque_Mod2xNA
|
|
_:
|
|
return 2 # Combiners_Opaque_Mod
|
|
|
|
|
|
static func _m2_shader_array_vertex_id(shader_array_index: int) -> int:
|
|
match shader_array_index:
|
|
0:
|
|
return 8
|
|
1:
|
|
return 0
|
|
2:
|
|
return 1
|
|
3:
|
|
return 1
|
|
4:
|
|
return 1
|
|
5:
|
|
return 1
|
|
6:
|
|
return 0
|
|
7:
|
|
return 12
|
|
8:
|
|
return 9
|
|
9:
|
|
return 12
|
|
_:
|
|
return 0
|
|
|
|
|
|
static func _m2_shader_array_pixel_id(shader_array_index: int) -> int:
|
|
match shader_array_index:
|
|
0:
|
|
return 35
|
|
1:
|
|
return 0
|
|
2:
|
|
return 7
|
|
3:
|
|
return 1
|
|
4:
|
|
return 6
|
|
5:
|
|
return 4
|
|
6:
|
|
return 0
|
|
7:
|
|
return 7
|
|
8:
|
|
return 1
|
|
9:
|
|
return 6
|
|
_:
|
|
return 0
|
|
|
|
|
|
static func _resolve_combo_texture(
|
|
combo_index: int,
|
|
textures: PackedStringArray,
|
|
texture_types: PackedInt32Array,
|
|
texture_flags: PackedInt32Array,
|
|
tex_combos: PackedInt32Array,
|
|
extracted_dir: String,
|
|
model_path: String) -> Dictionary:
|
|
|
|
if combo_index < 0 or combo_index >= tex_combos.size():
|
|
return {}
|
|
var tex_idx: int = tex_combos[combo_index]
|
|
if tex_idx < 0 or tex_idx >= textures.size():
|
|
return {}
|
|
var tex_path: String = str(textures[tex_idx]).replace("\\", "/")
|
|
var tex_type := int(texture_types[tex_idx]) if tex_idx < texture_types.size() else 0
|
|
var tex_flags := int(texture_flags[tex_idx]) if tex_idx < texture_flags.size() else 0
|
|
if tex_path.is_empty():
|
|
tex_path = _default_texture_path(model_path, tex_type)
|
|
var tex: Texture2D = null
|
|
if not tex_path.is_empty():
|
|
tex = _load_texture(tex_path, extracted_dir)
|
|
return {
|
|
"texture": tex,
|
|
"flags": tex_flags,
|
|
"path": tex_path,
|
|
}
|
|
|
|
|
|
static func _resolve_combiner_state(
|
|
blend_mode: int,
|
|
texture_count: int,
|
|
batch: Dictionary,
|
|
tex_coord_combos: PackedInt32Array,
|
|
tex_combiner_combos: PackedInt32Array,
|
|
m2_flags: int) -> Dictionary:
|
|
|
|
var state := {
|
|
"op0": 0 if blend_mode == 0 else 1,
|
|
"op1": 1,
|
|
"env0": false,
|
|
"env1": false,
|
|
"use_uv2_0": false,
|
|
"use_uv2_1": false,
|
|
}
|
|
var shader_id := int(batch.get("shader_id", 0))
|
|
var coord_index := int(batch.get("texture_coord_combo_index", 0))
|
|
if (m2_flags & 0x8) != 0:
|
|
for texture_index in mini(texture_count, 2):
|
|
var op := 0 if texture_index == 0 and blend_mode == 0 else 1
|
|
var combiner_index := shader_id + texture_index
|
|
if combiner_index >= 0 and combiner_index < tex_combiner_combos.size():
|
|
op = int(tex_combiner_combos[combiner_index])
|
|
state["op%d" % texture_index] = op
|
|
_apply_texture_coord_state(state, texture_index, coord_index + texture_index, tex_coord_combos)
|
|
else:
|
|
_apply_texture_coord_state(state, 0, coord_index, tex_coord_combos)
|
|
return state
|
|
|
|
|
|
static func _apply_texture_coord_state(state: Dictionary, texture_index: int, combo_index: int, tex_coord_combos: PackedInt32Array) -> void:
|
|
if combo_index < 0 or combo_index >= tex_coord_combos.size():
|
|
return
|
|
var coord := int(tex_coord_combos[combo_index])
|
|
state["use_uv2_%d" % texture_index] = coord == 1
|
|
state["env%d" % texture_index] = coord > 2
|
|
|
|
|
|
static func _resolve_mesh_color(
|
|
batch: Dictionary,
|
|
m2_colors: Array,
|
|
tex_weights: PackedFloat32Array,
|
|
tex_weight_combos: PackedInt32Array) -> Color:
|
|
|
|
var color := Color.WHITE
|
|
var color_index := int(batch.get("color_index", -1))
|
|
if color_index >= 0 and color_index < m2_colors.size():
|
|
var color_entry: Dictionary = m2_colors[color_index] if m2_colors[color_index] is Dictionary else {}
|
|
color = color_entry.get("color", Color.WHITE)
|
|
|
|
var weight_combo_index := int(batch.get("texture_weight_combo_index", -1))
|
|
if weight_combo_index >= 0 and weight_combo_index < tex_weight_combos.size():
|
|
var weight_index := int(tex_weight_combos[weight_combo_index])
|
|
if weight_index >= 0 and weight_index < tex_weights.size():
|
|
color.a *= clampf(tex_weights[weight_index], 0.0, 1.0)
|
|
return color
|
|
|
|
|
|
static func _resolve_texture_sample_alpha(
|
|
batch: Dictionary,
|
|
tex_weights: PackedFloat32Array,
|
|
tex_weight_combos: PackedInt32Array) -> Vector3:
|
|
|
|
var result := Vector3.ONE
|
|
var weight_combo_index := int(batch.get("texture_weight_combo_index", -1))
|
|
for stage in 3:
|
|
var combo_index := weight_combo_index + stage
|
|
if combo_index < 0 or combo_index >= tex_weight_combos.size():
|
|
continue
|
|
var weight_index := int(tex_weight_combos[combo_index])
|
|
if weight_index >= 0 and weight_index < tex_weights.size():
|
|
result[stage] = clampf(tex_weights[weight_index], 0.0, 1.0)
|
|
return result
|
|
|
|
|
|
static func _resolve_texture_transform(
|
|
batch: Dictionary,
|
|
stage: int,
|
|
tex_transform_combos: PackedInt32Array,
|
|
tex_transforms: Array) -> Vector4:
|
|
|
|
var combo_index := int(batch.get("texture_transform_combo_index", -1)) + stage
|
|
if combo_index < 0 or combo_index >= tex_transform_combos.size():
|
|
return Vector4(1.0, 1.0, 0.0, 0.0)
|
|
var transform_index := int(tex_transform_combos[combo_index])
|
|
if transform_index < 0 or transform_index >= tex_transforms.size():
|
|
return Vector4(1.0, 1.0, 0.0, 0.0)
|
|
var transform: Dictionary = tex_transforms[transform_index] if tex_transforms[transform_index] is Dictionary else {}
|
|
var scale: Vector2 = transform.get("scale", Vector2.ONE)
|
|
var translation: Vector2 = transform.get("translation", Vector2.ZERO)
|
|
return Vector4(scale.x, scale.y, translation.x, translation.y)
|
|
|
|
|
|
static func _resolve_texture_scroll(
|
|
batch: Dictionary,
|
|
stage: int,
|
|
tex_transform_combos: PackedInt32Array,
|
|
tex_transforms: Array) -> Vector2:
|
|
|
|
var combo_index := int(batch.get("texture_transform_combo_index", -1)) + stage
|
|
if combo_index < 0 or combo_index >= tex_transform_combos.size():
|
|
return Vector2.ZERO
|
|
var transform_index := int(tex_transform_combos[combo_index])
|
|
if transform_index < 0 or transform_index >= tex_transforms.size():
|
|
return Vector2.ZERO
|
|
var transform: Dictionary = tex_transforms[transform_index] if tex_transforms[transform_index] is Dictionary else {}
|
|
return transform.get("translation_speed", Vector2.ZERO)
|
|
|
|
|
|
static func _default_texture_path(model_path: String, texture_type: int) -> String:
|
|
var normalized := model_path.replace("\\", "/")
|
|
var rel_model := _relative_extracted_model_path(normalized)
|
|
var rel_lower := rel_model.to_lower()
|
|
if rel_lower.begins_with("item/objectcomponents/weapon/") and texture_type == 2:
|
|
var stem := rel_model.get_file().get_basename()
|
|
if stem.to_lower() == "axe_1h_horde_a_01":
|
|
return "Item/ObjectComponents/Weapon/Axe_1H_Horde_A_01Gray.blp"
|
|
if not stem.is_empty():
|
|
return "Item/ObjectComponents/Weapon/%s.blp" % stem
|
|
if rel_lower == "creature/wolf/wolf.m2" or rel_lower == "creature/wolf/wolf.mdx":
|
|
if texture_type == 11:
|
|
return "Creature/Wolf/WolfSkinCoyote.blp"
|
|
if texture_type == 12:
|
|
return "Creature/Wolf/WolfSkinCoyoteAlpha.blp"
|
|
if rel_lower == "creature/boar/boar.m2" or rel_lower == "creature/boar/boar.mdx":
|
|
if texture_type == 11:
|
|
return "Creature/Boar/BoarSkinIvory.blp"
|
|
if rel_lower == "creature/kobold/kobold.m2" or rel_lower == "creature/kobold/kobold.mdx":
|
|
if texture_type == 11:
|
|
return "Creature/Kobold/koboldskinAlbino.blp"
|
|
if rel_lower == "creature/murloc/murloc.m2" or rel_lower == "creature/murloc/murloc.mdx":
|
|
if texture_type == 11:
|
|
return "Creature/Murloc/SahauginskinBlue.blp"
|
|
return ""
|
|
|
|
|
|
static func _relative_extracted_model_path(path: String) -> String:
|
|
var marker := "/data/extracted/"
|
|
var lower := path.to_lower()
|
|
var marker_pos := lower.find(marker)
|
|
if marker_pos >= 0:
|
|
return path.substr(marker_pos + marker.length())
|
|
return path
|
|
|
|
|
|
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
|