import { Box3, BoxGeometry, Mesh, Object3D, Vector3 } from "three"; import { ThreeC } from "../core/ThreeC"; import { PhysicsBody, PhysicsLayer } from "../core/PhysicsC"; import { PhysicsTriggerC } from "./PhysicsTriggerC"; import { PropC } from "../Props/PropC"; import { findDamageStateLayers } from "../Props/PropDamageLayers"; import { resolvePropType } from "../Props/PropDropTable"; import { InteractiveZoneC } from "./InteractiveZoneC"; import { ToolZoneC } from "./ToolZoneC"; const MAP_PHYSICS_LAYERS = ["Colliders", "Lootable"]; const GATHER_TRIGGER_PADDING = 0.15; export class Map { static init() { const mapObject = ThreeC.getObject("map"); if (!mapObject) { console.warn("Map model resource not found: map"); return; } mapObject.position.set(0, 0, 0); ThreeC.setShadowsStateForChildren(mapObject, true, true); ThreeC.addToScene(mapObject); this.hideReferenceHpUi(mapObject); PhysicsTriggerC.init(); this.buildPhysics(mapObject); InteractiveZoneC.init(mapObject); ToolZoneC.init(mapObject); } private static hideReferenceHpUi(mapObject: Object3D) { const ui = mapObject.getObjectByName("UI"); if (ui) ui.visible = false; const woodUi = mapObject.getObjectByName("UI_Wood001"); if (woodUi) woodUi.visible = false; } private static buildPhysics(mapObject: Object3D) { for (const layerName of MAP_PHYSICS_LAYERS) { const layer = mapObject.getObjectByName(layerName); if (!layer) { console.warn(`Map physics layer not found: ${layerName}`); continue; } if (layerName === "Colliders") { // Floor-only layer (BoxCollider): hide in scene, no physics needed. layer.visible = false; continue; } layer.children.forEach((lootableObject) => { this.setupLootableObject(lootableObject); }); } } private static setupLootableObject(lootableObject: Object3D) { const propType = resolvePropType(lootableObject.name); const damageLayers = findDamageStateLayers(lootableObject); const maxHealth = damageLayers.length > 0 ? damageLayers.length : 1; const prop = new PropC(lootableObject, propType, maxHealth); prop.initDamageLayers(damageLayers); const colliderMesh = this.findMesh(lootableObject, (name) => name.includes("collider")); const visualMeshes = this.getVisualMeshes(lootableObject); if (colliderMesh) { colliderMesh.visible = false; prop.addWallBody( new PhysicsBody(colliderMesh, false, 0, PhysicsLayer.Wall, PhysicsLayer.Player), ); } else { visualMeshes.forEach((mesh) => { prop.addWallBody( new PhysicsBody(mesh, false, 0, PhysicsLayer.Wall, PhysicsLayer.Player), ); }); } PhysicsTriggerC.register(this.createGatherTrigger(lootableObject), lootableObject); } private static findMesh( root: Object3D, matcher: (name: string) => boolean, ): Mesh | null { let found: Mesh | null = null; root.traverse((child) => { if (found) return; if ((child as Mesh).isMesh && matcher(child.name.toLowerCase())) { found = child as Mesh; } }); return found; } private static getVisualMeshes(root: Object3D): Mesh[] { const visualMeshes: Mesh[] = []; root.traverse((child) => { if (!(child as Mesh).isMesh) return; const name = child.name.toLowerCase(); if (name.includes("collider") || name.includes("trigger") || name.includes("gathertrigger")) return; visualMeshes.push(child as Mesh); }); return visualMeshes; } private static createGatherTrigger(lootableObject: Object3D): Mesh { lootableObject.updateWorldMatrix(true, true); const size = new Vector3(); new Box3().setFromObject(lootableObject).getSize(size); const triggerMesh = new Mesh( new BoxGeometry( size.x + GATHER_TRIGGER_PADDING * 2, size.y + GATHER_TRIGGER_PADDING * 2, size.z + GATHER_TRIGGER_PADDING * 2, ), ); triggerMesh.visible = false; triggerMesh.name = "GatherTrigger"; lootableObject.add(triggerMesh); return triggerMesh; } }