117 lines
3.1 KiB
TypeScript
117 lines
3.1 KiB
TypeScript
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<TriggerRecord>();
|
|
private static playerBody: Body | null = null;
|
|
|
|
static onTriggerEnter = new EasyEvent<TriggerEventPayload>();
|
|
static onTriggerExit = new EasyEvent<TriggerEventPayload>();
|
|
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
}
|