class_name M2PlacementGrouper extends RefCounted ## Stateless ADT M2 placement validation, normalization and transform grouping. const M2_PLACEMENT_TRANSFORM_RESOLVER_SCRIPT := preload( "res://src/render/m2/m2_placement_transform_resolver.gd" ) ## Groups valid placements by normalized relative M2 path while preserving ## placement order inside each group. Returned containers are caller-owned. func group_placements( tile_origin: Vector3, m2_names: PackedStringArray, m2_placements: Array ) -> Dictionary: var groups: Dictionary = {} var placement_transform_resolver := ( M2_PLACEMENT_TRANSFORM_RESOLVER_SCRIPT.new() ) for placement_variant in m2_placements: if not (placement_variant is Dictionary): continue var placement: Dictionary = placement_variant var name_id: int = int(placement.get("name_id", -1)) if name_id < 0 or name_id >= m2_names.size(): continue var normalized_relative_path := _normalize_relative_path(m2_names[name_id]) if normalized_relative_path.is_empty(): continue var world_position: Vector3 = placement.get("pos", Vector3.ZERO) var local_position := world_position - tile_origin var rotation_radians: Vector3 = placement.get("rot", Vector3.ZERO) var scale_value := float(placement.get("scale", 1.0)) var placement_basis: Basis = placement_transform_resolver.resolve_basis( rotation_radians, normalized_relative_path ) var origin_offset: Vector3 = placement_transform_resolver.resolve_origin_offset( rotation_radians, normalized_relative_path, scale_value ) var local_transform := Transform3D( placement_basis.scaled(Vector3.ONE * maxf(scale_value, 0.0001)), local_position + origin_offset ) if not groups.has(normalized_relative_path): groups[normalized_relative_path] = [] (groups[normalized_relative_path] as Array).append(local_transform) return groups func _normalize_relative_path(relative_path: String) -> String: var normalized_relative_path := relative_path.replace("\\", "/") if ( normalized_relative_path.ends_with(".mdx") or normalized_relative_path.ends_with(".mdl") ): normalized_relative_path = normalized_relative_path.get_basename() + ".m2" return normalized_relative_path