mylingo3d
Version:
Lingo3D is a React/Vue 3d game development framework that ships with a complete visual editor
212 lines (184 loc) • 7.39 kB
text/typescript
import { Reactive } from "@lincode/reactivity"
import { container } from "../../engine/renderLoop/renderSetup"
import IOrbitCamera, {
orbitCameraDefaults,
orbitCameraSchema
} from "../../interface/IOrbitCamera"
import { getTransformControlsDragging } from "../../states/useTransformControlsDragging"
import { onKeyClear } from "../../events/onKeyClear"
import { onSceneGraphChange } from "../../events/onSceneGraphChange"
import { getCameraRendered } from "../../states/useCameraRendered"
import { onBeforeRender } from "../../events/onBeforeRender"
import { Cancellable } from "@lincode/promiselikes"
import { idMap } from "../core/StaticObjectManager"
import { PerspectiveCamera } from "three"
import OrbitCameraBase from "../core/OrbitCameraBase"
import { vec2Point } from "../utils/vec2Point"
import getWorldPosition from "../utils/getWorldPosition"
import getCenter from "../utils/getCenter"
import { FAR, NEAR } from "../../globals"
export default class OrbitCamera
extends OrbitCameraBase
implements IOrbitCamera
{
public static componentName = "orbitCamera"
public static defaults = orbitCameraDefaults
public static schema = orbitCameraSchema
public constructor(camera = new PerspectiveCamera(75, 1, NEAR, FAR)) {
super(camera)
this.innerZ = 500
this.orbitMode = true
this.mouseControl = "drag"
this.camera.rotation.y = 0
this.createEffect(() => {
const targetId = this.targetIdState.get()
if (!targetId) return
const handle = new Cancellable()
const timeout = setTimeout(() => {
const find = () => {
const [found] = idMap.get(targetId) ?? [undefined]
if (found) {
this.manualTarget = found
this.targetState.set(found)
}
return found
}
if (find()) return
handle.watch(
onSceneGraphChange(() =>
setTimeout(() => find() && handle.cancel())
)
)
})
return () => {
clearTimeout(timeout)
handle.cancel()
}
}, [this.targetIdState.get])
this.createEffect(() => {
const target = this.targetState.get()
if (!target) return
const handle = onBeforeRender(() => {
this.placeAt(vec2Point(getCenter(target.nativeObject3d)))
})
return () => {
handle.cancel()
}
}, [this.targetState.get])
this.createEffect(() => {
const autoRotate = this.autoRotateState.get()
if (getCameraRendered() !== camera || !autoRotate) return
const speed = typeof autoRotate === "number" ? autoRotate : 2
const handle = onBeforeRender(() => {
this.gyrate(speed, 0, true)
})
return () => {
handle.cancel()
}
}, [getCameraRendered, this.autoRotateState.get])
this.createEffect(() => {
if (
getTransformControlsDragging() ||
getCameraRendered() !== camera ||
!this.mouseControlState.get()
)
return
const handle = new Cancellable()
if (this.enableZoomState.get()) {
const cb = (e: WheelEvent) => {
e.preventDefault()
this.innerZ += e.deltaY
if (this.innerZ < 0) this.innerZ = 0
}
container.addEventListener("wheel", cb)
handle.then(() => container.removeEventListener("wheel", cb))
}
if (this.enableFlyState.get()) {
const downSet = new Set<string>()
handle.watch(
onBeforeRender(() => {
if (downSet.has("Meta") || downSet.has("Control"))
return
const speed = downSet.has("Shift") ? 50 : 10
if (downSet.has("w")) this.translateZ(-speed)
else if (downSet.has("s")) this.translateZ(speed)
if (downSet.has("a") || downSet.has("ArrowLeft"))
this.moveRight(-speed)
else if (downSet.has("d") || downSet.has("ArrowRight"))
this.moveRight(speed)
if (
downSet.has("w") ||
downSet.has("s") ||
downSet.has("a") ||
downSet.has("d")
) {
const worldPos = vec2Point(
getWorldPosition(this.object3d)
)
this.innerZ = 0
this.placeAt(worldPos)
}
if (downSet.has("Meta") || downSet.has("Control"))
return
if (downSet.has("ArrowDown")) this.y -= speed
else if (downSet.has("ArrowUp")) this.y += speed
})
)
const handleKeyDown = (e: KeyboardEvent) => {
downSet.add(
e.key.length === 1 ? e.key.toLocaleLowerCase() : e.key
)
}
const handleKeyUp = (e: KeyboardEvent) => {
downSet.delete(
e.key.length === 1 ? e.key.toLocaleLowerCase() : e.key
)
}
document.addEventListener("keydown", handleKeyDown)
document.addEventListener("keyup", handleKeyUp)
handle.watch(onKeyClear(() => downSet.clear()))
handle.then(() => {
document.removeEventListener("keydown", handleKeyDown)
document.removeEventListener("keyup", handleKeyUp)
})
}
return () => {
handle.cancel()
}
}, [
getCameraRendered,
getTransformControlsDragging,
this.enableZoomState.get,
this.enableFlyState.get,
this.mouseControlState.get
])
}
private targetIdState = new Reactive<string | undefined>(undefined)
public get targetId() {
return this.targetIdState.get()
}
public set targetId(val) {
this.targetIdState.set(val)
}
private enableZoomState = new Reactive(false)
public get enableZoom() {
return this.enableZoomState.get()
}
public set enableZoom(val) {
this.enableZoomState.set(val)
}
private enableFlyState = new Reactive(false)
public get enableFly() {
return this.enableFlyState.get()
}
public set enableFly(val) {
this.enableFlyState.set(val)
}
private autoRotateState = new Reactive<boolean | number>(false)
public get autoRotate() {
return this.autoRotateState.get()
}
public set autoRotate(val) {
this.autoRotateState.set(val)
}
}