import { EasyEvent, UpdateController, } from "@24tools/playable_template"; import { Box3, Object3D, Vector3 } from "three"; import { Body } from "cannon-es"; import { PLAYER_COLLIDER_RADIUS } from "../core/PhysicsC"; const PLAYER_RADIUS = PLAYER_COLLIDER_RADIUS; export type TriggerEventPayload = { lootableObject: Object3D; triggerObject: Object3D; playerBody: Body; }; type TriggerRecord = { lootableObject: Object3D; triggerObject: Object3D; center: Vector3; radius: number; }; export class PhysicsTriggerC { private static inited = false; private static triggers: TriggerRecord[] = []; private static activeTriggers = new Set(); private static playerBody: Body | null = null; static onTriggerEnter = new EasyEvent(); static onTriggerExit = new EasyEvent(); static init() { if (this.inited) return; this.inited = true; UpdateController.Instance.onUpdate.addDelegate(() => this.update()); } static setPlayerBody(body: Body) { this.playerBody = body; } static register(triggerObject: Object3D, lootableObject: Object3D) { triggerObject.updateWorldMatrix(true, true); const center = new Vector3(); const size = new Vector3(); const bounds = new Box3().setFromObject(triggerObject); bounds.getCenter(center); bounds.getSize(size); this.triggers.push({ lootableObject, triggerObject, center, radius: Math.max(size.x, size.z) / 2, }); } static getActiveLootableObjects(): Object3D[] { const lootables: Object3D[] = []; for (const record of this.activeTriggers) { lootables.push(record.lootableObject); } return lootables; } static unregister(lootableObject: Object3D) { const playerBody = this.playerBody; this.triggers = this.triggers.filter((record) => { if (record.lootableObject !== lootableObject) return true; if (this.activeTriggers.has(record) && playerBody) { this.onTriggerExit.Invoke({ lootableObject: record.lootableObject, triggerObject: record.triggerObject, playerBody, }); } this.activeTriggers.delete(record); return false; }); } private static update() { const playerBody = this.playerBody; if (!playerBody) return; for (const trigger of this.triggers) { const dx = playerBody.position.x - trigger.center.x; const dz = playerBody.position.z - trigger.center.z; const distanceSq = dx * dx + dz * dz; const isInside = distanceSq <= (trigger.radius + PLAYER_RADIUS) ** 2; const wasInside = this.activeTriggers.has(trigger); if (isInside === wasInside) continue; const payload: TriggerEventPayload = { lootableObject: trigger.lootableObject, triggerObject: trigger.triggerObject, playerBody, }; if (isInside) { this.activeTriggers.add(trigger); this.onTriggerEnter.Invoke(payload); } else { this.activeTriggers.delete(trigger); this.onTriggerExit.Invoke(payload); } } } }