class_name M2PlacementTransformResolver extends RefCounted ## Stateless ADT M2 placement orientation and origin-correction rules. const CLIFFROCK_WORLD_YAW_OFFSET := PI const WATERFALL_WORLD_YAW_OFFSET := PI * 0.5 ## Resolves the unscaled Godot-space basis for one ADT M2 placement. func resolve_basis(rotation_radians: Vector3, relative_m2_path: String = "") -> Basis: if not _is_waterfall_path(relative_m2_path): return _resolve_regular_basis(rotation_radians, relative_m2_path) var placement_basis := Basis.from_euler(rotation_radians) return ( Basis(Vector3.UP, WATERFALL_WORLD_YAW_OFFSET) * placement_basis * Basis( _resolve_waterfall_local_fall_axis(relative_m2_path), _resolve_waterfall_sheet_twist(relative_m2_path) ) ) ## Resolves local origin compensation for a twisted waterfall sheet. Regular ## models and waterfall models without a measured anchor return zero. func resolve_origin_offset( rotation_radians: Vector3, relative_m2_path: String, scale_value: float ) -> Vector3: if not _is_waterfall_path(relative_m2_path): return Vector3.ZERO var twist_radians := _resolve_waterfall_sheet_twist(relative_m2_path) if is_zero_approx(twist_radians): return Vector3.ZERO var anchor_local_position := _resolve_waterfall_sheet_twist_anchor(relative_m2_path) if anchor_local_position == Vector3.ZERO: return Vector3.ZERO var placement_basis := ( Basis(Vector3.UP, WATERFALL_WORLD_YAW_OFFSET) * Basis.from_euler(rotation_radians) ) var twisted_basis := placement_basis * Basis( _resolve_waterfall_local_fall_axis(relative_m2_path), twist_radians ) return ( placement_basis * anchor_local_position - twisted_basis * anchor_local_position ) * maxf(scale_value, 0.0001) func _resolve_regular_basis(rotation_radians: Vector3, relative_m2_path: String) -> Basis: if not _is_elwynn_cliffrock_path(relative_m2_path): return Basis.from_euler(rotation_radians) return ( Basis(Vector3.UP, rotation_radians.y + CLIFFROCK_WORLD_YAW_OFFSET) * Basis(Vector3.BACK, rotation_radians.x) * Basis(Vector3.RIGHT, -rotation_radians.z) ) func _is_waterfall_path(relative_m2_path: String) -> bool: var model_name := _normalized_model_name(relative_m2_path) return model_name == "newwaterfall" or model_name == "elwynntallwaterfall01" func _is_elwynn_cliffrock_path(relative_m2_path: String) -> bool: var model_name := _normalized_model_name(relative_m2_path) return model_name == "elwynncliffrock01" or model_name == "elwynncliffrock02" func _normalized_model_name(relative_m2_path: String) -> String: return relative_m2_path.replace("\\", "/").to_lower().get_file().get_basename() func _resolve_waterfall_local_fall_axis(relative_m2_path: String) -> Vector3: var normalized_path := relative_m2_path.replace("\\", "/").to_lower() if normalized_path.ends_with("newwaterfall.m2") or normalized_path.ends_with("newwaterfall.mdx"): return Vector3(-0.138742, 0.990329, 0.0).normalized() if ( normalized_path.ends_with("elwynntallwaterfall01.m2") or normalized_path.ends_with("elwynntallwaterfall01.mdx") ): return Vector3(-0.068962, 0.997619, 0.0).normalized() return Vector3.UP func _resolve_waterfall_sheet_twist(relative_m2_path: String) -> float: var normalized_path := relative_m2_path.replace("\\", "/").to_lower() if normalized_path.ends_with("newwaterfall.m2") or normalized_path.ends_with("newwaterfall.mdx"): return PI * 0.5 if ( normalized_path.ends_with("elwynntallwaterfall01.m2") or normalized_path.ends_with("elwynntallwaterfall01.mdx") ): return -PI * 0.5 return 0.0 func _resolve_waterfall_sheet_twist_anchor(relative_m2_path: String) -> Vector3: var normalized_path := relative_m2_path.replace("\\", "/").to_lower() if ( normalized_path.ends_with("elwynntallwaterfall01.m2") or normalized_path.ends_with("elwynntallwaterfall01.mdx") ): return Vector3(-2.667799, 89.62273, 0.00129) return Vector3.ZERO