mylingo3d
Version:
Lingo3D is a React/Vue 3d game development framework that ships with a complete visual editor
170 lines (141 loc) • 6.05 kB
text/typescript
import { Object3D, PerspectiveCamera, Quaternion } from "three"
import scene from "../../../engine/scene"
import { onBeforeRender } from "../../../events/onBeforeRender"
import ICharacterCamera, {
characterCameraDefaults,
characterCameraSchema,
LockTargetRotationValue
} from "../../../interface/ICharacterCamera"
import { getSelectionTarget } from "../../../states/useSelectionTarget"
import { getTransformControlsDragging } from "../../../states/useTransformControlsDragging"
import OrbitCameraBase from "../OrbitCameraBase"
import { euler, quaternion } from "../../utils/reusables"
import MeshItem from "../MeshItem"
import { getLoadedObject } from "../Loaded"
import getWorldQuaternion from "../../utils/getWorldQuaternion"
import { getEditorModeComputed } from "../../../states/useEditorModeComputed"
import characterCameraPlaced from "./characterCameraPlaced"
import { FAR, NEAR } from "../../../globals"
import { getCentripetal } from "../../../states/useCentripetal"
import applyCentripetalQuaternion from "../../utils/applyCentripetalQuaternion"
import fpsAlpha from "../../utils/fpsAlpha"
export default class CharacterCamera
extends OrbitCameraBase
implements ICharacterCamera
{
public static defaults = characterCameraDefaults
public static schema = characterCameraSchema
public constructor() {
super(new PerspectiveCamera(75, 1, NEAR, FAR))
const midObject3d = (this.midObject3d = new Object3D())
this.outerObject3d.add(midObject3d)
midObject3d.add(this.object3d)
const cam = this.camera
scene.attach(cam)
this.then(() => scene.remove(cam))
this.createEffect(() => {
const target = this.targetState.get()
if (!target) return
if ("frustumCulled" in target) target.frustumCulled = false
}, [this.targetState.get])
const followTargetRotation = (target: MeshItem, slerp: boolean) => {
euler.setFromQuaternion(target.outerObject3d.quaternion)
euler.y += Math.PI
if (slerp) {
quaternion.setFromEuler(euler)
midObject3d.quaternion.slerp(quaternion, fpsAlpha(0.1))
} else midObject3d.setRotationFromEuler(euler)
this.updateAngle()
}
const lockTargetRotation = (
target: MeshItem,
slerp: boolean,
quat: Quaternion | undefined
) => {
euler.setFromQuaternion(this.midObject3d.quaternion)
euler.x = 0
euler.z = 0
euler.y += Math.PI
if (quat) {
const innerObject = getLoadedObject(target)
quaternion.copy(target.outerObject3d.quaternion)
const innerRotationY = innerObject.rotation.y
target.outerObject3d.quaternion.copy(quat)
innerObject.rotation.y = euler.y
euler.setFromQuaternion(getWorldQuaternion(innerObject))
innerObject.rotation.y = innerRotationY
target.outerObject3d.quaternion.copy(quaternion)
}
const placed = characterCameraPlaced.has(target)
if (slerp && !placed) {
quaternion.setFromEuler(euler)
target.outerObject3d.quaternion.slerp(quaternion, fpsAlpha(0.1))
return
}
target.outerObject3d.setRotationFromEuler(euler)
quat && placed && characterCameraPlaced.delete(target)
}
let transformControlRotating = false
this.createEffect(() => {
const target = this.targetState.get()
if (!target) return
followTargetRotation(target, false)
let [xOld, yOld, zOld] = [0, 0, 0]
const targetMoved = () => {
const { x, y, z } = target.outerObject3d.position
const result = x !== xOld || y !== yOld || z !== zOld
;[xOld, yOld, zOld] = [x, y, z]
return result
}
const centripetal = getCentripetal()
const handle = onBeforeRender(() => {
this.outerObject3d.position.copy(target.outerObject3d.position)
const quat = centripetal
? applyCentripetalQuaternion(this)
: undefined
if (!this.lockTargetRotation) return
if (
this.lockTargetRotation === "follow" ||
transformControlRotating
) {
followTargetRotation(target, false)
return
}
if (this.lockTargetRotation === "dynamic-lock") {
targetMoved() && lockTargetRotation(target, true, quat)
return
}
if (this.lockTargetRotation === "dynamic-follow") {
targetMoved() && followTargetRotation(target, true)
return
}
lockTargetRotation(target, false, quat)
})
return () => {
handle.cancel()
}
}, [this.targetState.get, getCentripetal])
this.createEffect(() => {
const target = this.targetState.get()
const selectionTarget = getSelectionTarget()
const dragging = getTransformControlsDragging()
const mode = getEditorModeComputed()
const rotating =
target &&
target === selectionTarget &&
dragging &&
mode === "rotate"
if (!rotating) return
transformControlRotating = true
return () => {
transformControlRotating = false
}
}, [
this.targetState.get,
getSelectionTarget,
getTransformControlsDragging,
getEditorModeComputed
])
}
public lockTargetRotation: LockTargetRotationValue = true
}