UNPKG

mylingo3d

Version:

Lingo3D is a React/Vue 3d game development framework that ships with a complete visual editor

165 lines (142 loc) 5.19 kB
import { Reactive } from "@lincode/reactivity" import { Object3D } from "three" import Cylinder from "./primitives/Cylinder" import Sphere from "./primitives/Sphere" import getActualScale from "./utils/getActualScale" import getWorldPosition from "./utils/getWorldPosition" import { scaleDown } from "../engine/constants" import { timer } from "../engine/eventLoop" import mainCamera from "../engine/mainCamera" import scene from "../engine/scene" import { emitSelectionTarget, onSelectionTarget } from "../events/onSelectionTarget" import ITrigger, { triggerDefaults, triggerSchema } from "../interface/ITrigger" import { appendableRoot } from "../api/core/Appendable" import PositionedItem from "../api/core/PositionedItem" import { getCameraRendered } from "../states/useCameraRendered" import StaticObjectManager, { idMap } from "./core/StaticObjectManager" const getTargets = (id: string) => idMap.get(id) ?? [] export default class Trigger extends PositionedItem implements ITrigger { public static componentName = "trigger" public static defaults = triggerDefaults public static schema = triggerSchema private refresh = new Reactive({}) public onEnter: ((target: StaticObjectManager) => void) | undefined public onExit: (() => void) | undefined private _pad = false public get pad() { return this._pad } public set pad(val) { this._pad = val this.refresh.set({}) } private _radius = 50 public get radius() { return this._radius } public set radius(val) { this._radius = val this.refresh.set({}) } private _interval = 300 public get interval() { return this._interval } public set interval(val) { this._interval = val this.refresh.set({}) } private _helper = true public get helper() { return this._helper } public set helper(val) { this._helper = val this.refresh.set({}) } private _targetIds?: string | Array<string> public get targetIds() { return this._targetIds } public set targetIds(val) { this._targetIds = val this.refresh.set({}) } public constructor() { const outerObject3d = new Object3D() super(outerObject3d) scene.add(outerObject3d) let helper: Cylinder | Sphere | undefined this.createEffect(() => { const { _radius, _interval, _targetIds, _pad } = this if (!_targetIds) return const r = _radius * scaleDown const pr = r * 0.2 let hitOld = false const handle = timer(_interval, -1, () => { const { x, y, z } = getWorldPosition(outerObject3d) const targets = typeof _targetIds === "string" ? getTargets(_targetIds) : _targetIds.map((id) => [...getTargets(id)]).flat() let hit = false let targetHit: StaticObjectManager | undefined for (const target of targets) { const { x: tx, y: ty, z: tz } = getWorldPosition(target.nativeObject3d) if (_pad) { const { y: sy } = getActualScale(target) hit = Math.abs(x - tx) < r && Math.abs(y - (ty - sy * 0.5)) < pr && Math.abs(z - tz) < r } else hit = Math.abs(x - tx) < r && Math.abs(y - ty) < r && Math.abs(z - tz) < r if (hit) { targetHit = target break } } if (hitOld !== hit) if (hit && targetHit) { this.onEnter?.(targetHit) helper && (helper.color = "blue") } else { this.onExit?.() helper && (helper.color = "white") } hitOld = hit }) return () => { handle.cancel() } }, [this.refresh.get]) this.createEffect(() => { const { _radius, _helper, _pad } = this if (!_helper || getCameraRendered() !== mainCamera) return const h = (helper = _pad ? new Cylinder() : new Sphere()) appendableRoot.delete(h) outerObject3d.add(h.outerObject3d) h.scale = _radius * scaleDown * 2 h.opacity = 0.5 h.height = _pad ? 10 : 100 const handle = onSelectionTarget( ({ target }) => target === h && emitSelectionTarget(this) ) return () => { h.dispose() helper = undefined handle.cancel() } }, [this.refresh.get, getCameraRendered]) } }