mylingo3d
Version:
Lingo3D is a React/Vue 3d game development framework that ships with a complete visual editor
383 lines (336 loc) • 11.4 kB
text/typescript
import {
rad2Deg,
deg2Rad,
distance3d,
Point3d,
vertexAngle,
Point,
rotatePoint,
quadrant
} from "@lincode/math"
import { Object3D, Vector3 } from "three"
import { vector3 } from "../../utils/reusables"
import { scaleDown, scaleUp } from "../../../engine/constants"
import { point2Vec } from "../../utils/vec2Point"
import ISimpleObjectManager, {
OnIntersectValue
} from "../../../interface/ISimpleObjectManager"
import getCenter from "../../utils/getCenter"
import PositionedItem from "../../../api/core/PositionedItem"
import StaticObjectManager, { idMap } from "../StaticObjectManager"
import { applyMixins } from "@lincode/utils"
import { Reactive } from "@lincode/reactivity"
import { Cancellable } from "@lincode/promiselikes"
import MeshItem from "../MeshItem"
import { onBeforeRender } from "../../../events/onBeforeRender"
import getWorldPosition from "../../utils/getWorldPosition"
import getWorldQuaternion from "../../utils/getWorldQuaternion"
import { getCentripetal } from "../../../states/useCentripetal"
import AnimatedObjectManager from "../AnimatedObjectManager"
import Nullable from "../../../interface/utils/Nullable"
import SpawnPoint from "../../SpawnPoint"
import getActualScale from "../../utils/getActualScale"
import { fpsRatio } from "../../../engine/eventLoop"
import fpsAlpha from "../../utils/fpsAlpha"
const ptDistCache = new WeakMap<Point3d, number>()
const distance3dCached = (pt: Point3d, vecSelf: Vector3) => {
const cached = ptDistCache.get(pt)
if (cached) return cached
const result = distance3d(
pt.x,
pt.y,
pt.z,
vecSelf.x * scaleUp,
vecSelf.y * scaleUp,
vecSelf.z * scaleUp
)
ptDistCache.set(pt, result)
return result
}
class SimpleObjectManager<T extends Object3D = Object3D>
extends AnimatedObjectManager<T>
implements ISimpleObjectManager
{
public getRayIntersectionsAt(id: string, maxDistance?: number) {
const result: Array<[StaticObjectManager, Point3d]> = []
for (const target of idMap.get(id) ?? []) {
if (target === this) continue
const pt = this.rayIntersectsAt(target, maxDistance)
pt && result.push([target, pt])
}
const vec = getWorldPosition(this.nativeObject3d)
return result.sort((a, b) => {
return distance3dCached(a[1], vec) - distance3dCached(b[1], vec)
})
}
public getRayIntersections(id: string, maxDistance?: number) {
return this.getRayIntersectionsAt(id, maxDistance).map(
(result) => result[0]
)
}
public listenToRayIntersection(
id: string,
cb: (target: StaticObjectManager, pt: Point3d) => void,
maxDistance?: number
) {
return this.beforeRender(() => {
for (const [target, pt] of this.getRayIntersectionsAt(
id,
maxDistance
))
cb(target, pt)
})
}
public getIntersections(id: string) {
const result: Array<StaticObjectManager> = []
for (const target of idMap.get(id) ?? []) {
if (target === this) continue
this.intersects(target) && result.push(target)
}
return result
}
public listenToIntersection(
id: string,
cb?: OnIntersectValue,
cbOut?: OnIntersectValue
) {
let intersectionsOld: Array<StaticObjectManager> = []
return this.beforeRender(() => {
const intersections = this.getIntersections(id)
if (cb)
for (const target of intersections)
if (!intersectionsOld.includes(target)) cb(target)
if (cbOut)
for (const target of intersectionsOld)
if (!intersections.includes(target)) cbOut(target)
intersectionsOld = intersections
})
}
private onIntersectState?: Reactive<OnIntersectValue | undefined>
private onIntersectOutState?: Reactive<OnIntersectValue | undefined>
private intersectIdsState?: Reactive<Array<string> | undefined>
private initIntersect() {
if (this.onIntersectState) return
this.onIntersectState = new Reactive<OnIntersectValue | undefined>(
undefined
)
this.onIntersectOutState = new Reactive<OnIntersectValue | undefined>(
undefined
)
this.intersectIdsState = new Reactive<Array<string> | undefined>(
undefined
)
this.createEffect(() => {
const { onIntersect, onIntersectOut, intersectIds } = this
if (!intersectIds || (!onIntersect && !onIntersectOut)) return
const handles: Array<Cancellable> = []
for (const id of intersectIds)
handles.push(
this.listenToIntersection(id, onIntersect, onIntersectOut)
)
return () => {
for (const handle of handles) handle.cancel()
}
}, [
this.onIntersectState.get,
this.onIntersectOutState.get,
this.intersectIdsState.get
])
}
public get onIntersect() {
return this.onIntersectState?.get()
}
public set onIntersect(val) {
this.initIntersect()
this.onIntersectState?.set(val)
}
public get onIntersectOut() {
return this.onIntersectOutState?.get()
}
public set onIntersectOut(val) {
this.initIntersect()
this.onIntersectOutState?.set(val)
}
public get intersectIds() {
return this.intersectIdsState?.get()
}
public set intersectIds(val) {
this.initIntersect()
this.intersectIdsState?.set(val)
}
public get scaleX() {
return this.outerObject3d.scale.x
}
public set scaleX(val) {
this.outerObject3d.scale.x = val
}
public get scaleY() {
return this.outerObject3d.scale.y
}
public set scaleY(val) {
this.outerObject3d.scale.y = val
}
public get scaleZ() {
return this.outerObject3d.scale.z
}
public set scaleZ(val) {
this.outerObject3d.scale.z = val
}
public get scale() {
return this.scaleX
}
public set scale(val) {
this.scaleX = val
this.scaleY = val
this.scaleZ = val
}
public get rotationX() {
return this.outerObject3d.rotation.x * rad2Deg
}
public set rotationX(val) {
this.outerObject3d.rotation.x = val * deg2Rad
}
public get rotationY() {
return this.outerObject3d.rotation.y * rad2Deg
}
public set rotationY(val) {
this.outerObject3d.rotation.y = val * deg2Rad
}
public get rotationZ() {
return this.outerObject3d.rotation.z * rad2Deg
}
public set rotationZ(val) {
this.outerObject3d.rotation.z = val * deg2Rad
}
public get rotation() {
return this.rotationZ
}
public set rotation(val) {
this.rotationZ = val
}
public translateX(val: number) {
this.outerObject3d.translateX(val * scaleDown * fpsRatio[0])
}
public translateY(val: number) {
this.outerObject3d.translateY(val * scaleDown * fpsRatio[0])
}
public translateZ(val: number) {
this.outerObject3d.translateZ(val * scaleDown * fpsRatio[0])
}
public placeAt(object: MeshItem | Point3d | SpawnPoint | string) {
if (typeof object === "string") {
const [found] = idMap.get(object) ?? [undefined]
if (!found) return
object = found
}
if ("outerObject3d" in object) {
if ("isSpawnPoint" in object)
object.object3d.position.y = getActualScale(this).y * 0.5
this.outerObject3d.position.copy(getCenter(object.nativeObject3d))
this.outerObject3d.quaternion.copy(
getWorldQuaternion(object.outerObject3d)
)
return
}
this.outerObject3d.position.copy(point2Vec(object))
}
public moveForward(distance: number) {
if (getCentripetal()) this.translateZ(-distance)
else {
vector3.setFromMatrixColumn(this.outerObject3d.matrix, 0)
vector3.crossVectors(this.outerObject3d.up, vector3)
this.outerObject3d.position.addScaledVector(
vector3,
distance * scaleDown * fpsRatio[0]
)
}
}
public moveRight(distance: number) {
if (getCentripetal()) this.translateX(distance)
else {
vector3.setFromMatrixColumn(this.outerObject3d.matrix, 0)
this.outerObject3d.position.addScaledVector(
vector3,
distance * scaleDown * fpsRatio[0]
)
}
}
public onMoveToEnd: Nullable<() => void>
public lerpTo(
x: number,
y: number,
z: number,
alpha: number,
onFrame?: () => void
) {
const from = new Vector3(this.x, this.y, this.z)
const to = new Vector3(x, y, z)
this.cancelHandle("lerpTo", () =>
onBeforeRender(() => {
const { x, y, z } = from.lerp(to, fpsAlpha(alpha))
if (
Math.abs(this.x - x) < 0.1 &&
Math.abs(this.y - y) < 0.1 &&
Math.abs(this.z - z) < 0.1
) {
this.cancelHandle("lerpTo", undefined)
this.onMoveToEnd?.()
}
this.x = x
this.y = y
this.z = z
onFrame?.()
})
)
}
public moveTo(
x: number,
y: number | undefined,
z: number,
speed: number,
onFrame?: (y?: number) => void
) {
if (x === this.x) x += 0.01
if (z === this.z) z += 0.01
const {
x: rx,
y: ry,
z: rz
} = new Vector3(
x - this.x,
y === undefined ? 0 : y - this.y,
z - this.z
).normalize()
const sx = speed * rx
const sy = speed * ry
const sz = speed * rz
const quad = quadrant(x, z, this.x, this.z)
this.cancelHandle("lerpTo", () =>
onBeforeRender(() => {
this.x += sx * fpsRatio[0]
y !== undefined && (this.y += sy * fpsRatio[0])
this.z += sz * fpsRatio[0]
const angle = vertexAngle(
new Point(this.x, this.z),
new Point(x, z),
new Point(this.x, z)
)
const rotated = rotatePoint(
new Point(x, z),
new Point(this.x, this.z),
quad === 1 || quad === 4 ? angle : -angle
)
if (z > rotated.y) {
this.cancelHandle("lerpTo", undefined)
this.onMoveToEnd?.()
}
onFrame?.(y)
})
)
}
}
interface SimpleObjectManager<T extends Object3D = Object3D>
extends AnimatedObjectManager<T>,
PositionedItem<T> {}
applyMixins(SimpleObjectManager, [PositionedItem])
export default SimpleObjectManager