refactor(M03): extract M2 placement transform resolver

This commit is contained in:
2026-07-16 23:51:59 +04:00
parent 91f0ba8433
commit 0a09d7bc1e
9 changed files with 573 additions and 90 deletions
+32 -88
View File
@@ -27,6 +27,9 @@ const TERRAIN_CHUNK_GEOMETRY_QUEUE_PLANNER_SCRIPT := preload(
const M2_UNIQUE_PLACEMENT_REGISTRY_SCRIPT := preload(
"res://src/render/m2/m2_unique_placement_registry.gd"
)
const M2_PLACEMENT_TRANSFORM_RESOLVER_SCRIPT := preload(
"res://src/render/m2/m2_placement_transform_resolver.gd"
)
const STREAMING_TARGET_PLANNER_SCRIPT := preload("res://src/render/streaming/streaming_target_planner.gd")
const STREAMING_TARGET_POLICY_SCRIPT := preload("res://src/render/streaming/streaming_target_policy.gd")
const RENDER_BUDGET_SCHEDULER_SCRIPT := preload("res://src/render/streaming/render_budget_scheduler.gd")
@@ -39,8 +42,6 @@ const RENDER_GROUND_QUERY_RAY_HEIGHT_UNITS := 5000.0
const TILE_SIZE := COORDINATE_MAPPER_SCRIPT.ADT_TILE_SIZE_YARDS
const CHUNK_SIZE := COORDINATE_MAPPER_SCRIPT.ADT_CHUNK_SIZE_YARDS
const ADT_CLIFFROCK_WORLD_YAW_OFFSET := PI
const ADT_WATERFALL_WORLD_YAW_OFFSET := PI * 0.5
const QUALITY_CUSTOM := "Custom"
const QUALITY_PERFORMANCE := "Performance"
const QUALITY_BALANCED := "Balanced"
@@ -213,6 +214,7 @@ var _m2_build_queue: Array = []
var _m2_unique_placement_registry := (
M2_UNIQUE_PLACEMENT_REGISTRY_SCRIPT.new()
)
var _m2_placement_transform_resolver := M2_PLACEMENT_TRANSFORM_RESOLVER_SCRIPT.new()
var _m2_mesh_cache: Dictionary = {}
var _m2_mesh_load_requests: Dictionary = {}
var _m2_mesh_finalize_queue: Array = []
@@ -3978,86 +3980,6 @@ func _rebuild_cached_wmo_material(material: Material) -> Material:
return WMO_BUILDER_SCRIPT._build_material(mat_def, textures, extracted_dir)
func _adt_m2_placement_basis(rot: Vector3, rel_path: String = "") -> Basis:
if not _is_waterfall_m2_path(rel_path):
return _adt_m2_regular_placement_basis(rot, rel_path)
# ADTLoader stores MDDF yaw as raw_yaw - 90 degrees for historical Godot
# facing alignment. In our tile-positive ADT world, waterfall models need a
# world-space +90 degree yaw to anchor their top to the source pool. Twist
# the sheet around its own local fall axis afterwards, so the anchor stays in
# place but the alpha plane is not rendered edge-on.
var basis := Basis.from_euler(rot)
return (
Basis(Vector3.UP, ADT_WATERFALL_WORLD_YAW_OFFSET)
* basis
* Basis(_waterfall_local_fall_axis(rel_path), _waterfall_sheet_twist(rel_path))
)
func _adt_m2_regular_placement_basis(rot: Vector3, rel_path: String = "") -> Basis:
if not _is_elwynn_cliffrock_m2_path(rel_path):
return Basis.from_euler(rot)
# Elwynn cliff rock M2s are open-backed shell meshes. Plain Godot Euler
# points their hollow side out of the cliff at waterfall placements. Keep the
# correction model-specific so normal props retain the native ADT rotation.
return (
Basis(Vector3.UP, rot.y + ADT_CLIFFROCK_WORLD_YAW_OFFSET)
* Basis(Vector3.BACK, rot.x)
* Basis(Vector3.RIGHT, -rot.z)
)
func _adt_m2_placement_origin_offset(rot: Vector3, rel_path: String, scale_value: float) -> Vector3:
if not _is_waterfall_m2_path(rel_path):
return Vector3.ZERO
var twist := _waterfall_sheet_twist(rel_path)
if is_zero_approx(twist):
return Vector3.ZERO
var anchor := _waterfall_sheet_twist_anchor(rel_path)
if anchor == Vector3.ZERO:
return Vector3.ZERO
var base := Basis(Vector3.UP, ADT_WATERFALL_WORLD_YAW_OFFSET) * Basis.from_euler(rot)
var twisted := base * Basis(_waterfall_local_fall_axis(rel_path), twist)
return (base * anchor - twisted * anchor) * maxf(scale_value, 0.0001)
func _is_waterfall_m2_path(rel_path: String) -> bool:
var name := rel_path.replace("\\", "/").to_lower().get_file().get_basename()
return name == "newwaterfall" or name == "elwynntallwaterfall01"
func _is_elwynn_cliffrock_m2_path(rel_path: String) -> bool:
var name := rel_path.replace("\\", "/").to_lower().get_file().get_basename()
return name == "elwynncliffrock01" or name == "elwynncliffrock02"
func _waterfall_local_fall_axis(rel_path: String) -> Vector3:
var normalized := rel_path.replace("\\", "/").to_lower()
if normalized.ends_with("newwaterfall.m2") or normalized.ends_with("newwaterfall.mdx"):
return Vector3(-0.138742, 0.990329, 0.0).normalized()
if normalized.ends_with("elwynntallwaterfall01.m2") or normalized.ends_with("elwynntallwaterfall01.mdx"):
return Vector3(-0.068962, 0.997619, 0.0).normalized()
return Vector3.UP
func _waterfall_sheet_twist(rel_path: String) -> float:
var normalized := rel_path.replace("\\", "/").to_lower()
if normalized.ends_with("newwaterfall.m2") or normalized.ends_with("newwaterfall.mdx"):
return PI * 0.5
if normalized.ends_with("elwynntallwaterfall01.m2") or normalized.ends_with("elwynntallwaterfall01.mdx"):
return -PI * 0.5
return 0.0
func _waterfall_sheet_twist_anchor(rel_path: String) -> Vector3:
var normalized := rel_path.replace("\\", "/").to_lower()
if normalized.ends_with("elwynntallwaterfall01.m2") or normalized.ends_with("elwynntallwaterfall01.mdx"):
return Vector3(-2.667799, 89.62273, 0.00129)
return Vector3.ZERO
func _cancel_wmo_build_job(tile_key: String) -> void:
if _wmo_build_jobs.has(tile_key):
_wmo_build_jobs.erase(tile_key)
@@ -4216,8 +4138,12 @@ func _group_tile_m2_task(
var pos: Vector3 = world_pos - tile_origin
var rot: Vector3 = placement.get("rot", Vector3.ZERO)
var scale_value: float = float(placement.get("scale", 1.0))
var basis := _adt_m2_placement_basis(rot, normalized)
var offset := _adt_m2_placement_origin_offset(rot, normalized, scale_value)
var basis: Basis = _m2_placement_transform_resolver.resolve_basis(rot, normalized)
var offset: Vector3 = _m2_placement_transform_resolver.resolve_origin_offset(
rot,
normalized,
scale_value
)
var xform := Transform3D(basis.scaled(Vector3.ONE * maxf(scale_value, 0.0001)), pos + offset)
if not groups.has(normalized):
@@ -4905,8 +4831,18 @@ func _build_tile_m2_placeholders(tile_root: Node3D, tile_origin: Vector3, m2_nam
var local_pos: Vector3 = placement.get("pos", Vector3.ZERO) - tile_origin
var rot: Vector3 = placement.get("rot", Vector3.ZERO)
var scale_value: float = float(placement.get("scale", 1.0))
var xform_basis := _adt_m2_placement_basis(rot, rel_path).scaled(Vector3.ONE * maxf(scale_value, 0.01))
var xf := Transform3D(xform_basis, local_pos + _adt_m2_placement_origin_offset(rot, rel_path, scale_value))
var xform_basis := _m2_placement_transform_resolver.resolve_basis(
rot,
rel_path
).scaled(Vector3.ONE * maxf(scale_value, 0.01))
var xf := Transform3D(
xform_basis,
local_pos + _m2_placement_transform_resolver.resolve_origin_offset(
rot,
rel_path,
scale_value
)
)
if is_tree:
tree_transforms.append(xf)
else:
@@ -4956,8 +4892,16 @@ func _instantiate_m2(rel_path: String, placement: Dictionary, tile_origin: Vecto
var rot: Vector3 = placement.get("rot", Vector3.ZERO)
var scale_value: float = float(placement.get("scale", 1.0))
node.transform = Transform3D(
_adt_m2_placement_basis(rot, rel_path).scaled(Vector3.ONE * maxf(scale_value, 0.0001)),
local_pos + _adt_m2_placement_origin_offset(rot, rel_path, scale_value))
_m2_placement_transform_resolver.resolve_basis(
rot,
rel_path
).scaled(Vector3.ONE * maxf(scale_value, 0.0001)),
local_pos + _m2_placement_transform_resolver.resolve_origin_offset(
rot,
rel_path,
scale_value
)
)
return node