2024-08-26 15:05:33 -04:00
|
|
|
|
import { cameraMouseDragGuards, MouseGuard } from 'lib/cameraControls'
|
2024-02-26 19:53:44 +11:00
|
|
|
|
import {
|
|
|
|
|
Euler,
|
|
|
|
|
MathUtils,
|
|
|
|
|
Matrix4,
|
|
|
|
|
OrthographicCamera,
|
|
|
|
|
PerspectiveCamera,
|
|
|
|
|
Quaternion,
|
|
|
|
|
Spherical,
|
|
|
|
|
Vector2,
|
|
|
|
|
Vector3,
|
|
|
|
|
} from 'three'
|
|
|
|
|
import {
|
|
|
|
|
DEBUG_SHOW_INTERSECTION_PLANE,
|
|
|
|
|
INTERSECTION_PLANE_LAYER,
|
|
|
|
|
SKETCH_LAYER,
|
|
|
|
|
ZOOM_MAGIC_NUMBER,
|
|
|
|
|
} from './sceneInfra'
|
2024-03-02 08:20:50 +11:00
|
|
|
|
import {
|
|
|
|
|
Subscription,
|
2024-03-22 16:55:30 +11:00
|
|
|
|
EngineCommandManager,
|
2024-05-23 22:02:25 +02:00
|
|
|
|
UnreliableSubscription,
|
2024-03-02 08:20:50 +11:00
|
|
|
|
} from 'lang/std/engineConnection'
|
2024-08-03 18:08:51 +10:00
|
|
|
|
import { EngineCommand } from 'lang/std/artifactGraph'
|
2024-09-09 18:17:45 -04:00
|
|
|
|
import { toSync, uuidv4 } from 'lib/utils'
|
2024-02-26 19:53:44 +11:00
|
|
|
|
import { deg2Rad } from 'lib/utils2d'
|
|
|
|
|
import { isReducedMotion, roundOff, throttle } from 'lib/utils'
|
|
|
|
|
import * as TWEEN from '@tweenjs/tween.js'
|
|
|
|
|
import { isQuaternionVertical } from './helpers'
|
2024-09-09 18:17:45 -04:00
|
|
|
|
import { reportRejection } from 'lib/trap'
|
2024-09-30 11:40:00 -04:00
|
|
|
|
import { CameraProjectionType } from 'wasm-lib/kcl/bindings/CameraProjectionType'
|
2024-02-26 19:53:44 +11:00
|
|
|
|
|
|
|
|
|
const ORTHOGRAPHIC_CAMERA_SIZE = 20
|
|
|
|
|
const FRAMES_TO_ANIMATE_IN = 30
|
2024-09-19 15:39:32 -04:00
|
|
|
|
const ORTHOGRAPHIC_MAGIC_FOV = 4
|
2024-02-26 19:53:44 +11:00
|
|
|
|
|
|
|
|
|
const tempQuaternion = new Quaternion() // just used for maths
|
|
|
|
|
|
2024-03-02 08:20:50 +11:00
|
|
|
|
type interactionType = 'pan' | 'rotate' | 'zoom'
|
|
|
|
|
|
2024-02-26 19:53:44 +11:00
|
|
|
|
interface ThreeCamValues {
|
|
|
|
|
position: Vector3
|
|
|
|
|
quaternion: Quaternion
|
|
|
|
|
zoom: number
|
|
|
|
|
isPerspective: boolean
|
|
|
|
|
target: Vector3
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export type ReactCameraProperties =
|
|
|
|
|
| {
|
|
|
|
|
type: 'perspective'
|
|
|
|
|
fov?: number
|
|
|
|
|
position: [number, number, number]
|
2024-06-05 14:43:12 +02:00
|
|
|
|
target: [number, number, number]
|
2024-02-26 19:53:44 +11:00
|
|
|
|
quaternion: [number, number, number, number]
|
|
|
|
|
}
|
|
|
|
|
| {
|
|
|
|
|
type: 'orthographic'
|
|
|
|
|
zoom?: number
|
|
|
|
|
position: [number, number, number]
|
2024-06-05 14:43:12 +02:00
|
|
|
|
target: [number, number, number]
|
2024-02-26 19:53:44 +11:00
|
|
|
|
quaternion: [number, number, number, number]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const lastCmdDelay = 50
|
|
|
|
|
|
|
|
|
|
export class CameraControls {
|
2024-03-22 16:55:30 +11:00
|
|
|
|
engineCommandManager: EngineCommandManager
|
2024-03-02 08:20:50 +11:00
|
|
|
|
syncDirection: 'clientToEngine' | 'engineToClient' = 'engineToClient'
|
2024-02-26 19:53:44 +11:00
|
|
|
|
camera: PerspectiveCamera | OrthographicCamera
|
|
|
|
|
target: Vector3
|
|
|
|
|
domElement: HTMLCanvasElement
|
|
|
|
|
isDragging: boolean
|
|
|
|
|
mouseDownPosition: Vector2
|
|
|
|
|
mouseNewPosition: Vector2
|
|
|
|
|
rotationSpeed = 0.3
|
|
|
|
|
enableRotate = true
|
|
|
|
|
enablePan = true
|
|
|
|
|
enableZoom = true
|
2024-07-01 15:22:31 -04:00
|
|
|
|
zoomDataFromLastFrame?: number = undefined
|
|
|
|
|
// holds coordinates, and interaction
|
|
|
|
|
moveDataFromLastFrame?: [number, number, string] = undefined
|
2024-02-26 19:53:44 +11:00
|
|
|
|
lastPerspectiveFov: number = 45
|
|
|
|
|
pendingZoom: number | null = null
|
|
|
|
|
pendingRotation: Vector2 | null = null
|
|
|
|
|
pendingPan: Vector2 | null = null
|
2024-08-26 15:05:33 -04:00
|
|
|
|
interactionGuards: MouseGuard = cameraMouseDragGuards.KittyCAD
|
2024-02-26 19:53:44 +11:00
|
|
|
|
isFovAnimationInProgress = false
|
2024-09-19 15:39:32 -04:00
|
|
|
|
perspectiveFovBeforeOrtho = 45
|
2024-02-26 19:53:44 +11:00
|
|
|
|
get isPerspective() {
|
|
|
|
|
return this.camera instanceof PerspectiveCamera
|
|
|
|
|
}
|
2024-02-29 19:25:48 +11:00
|
|
|
|
|
2024-09-30 11:40:00 -04:00
|
|
|
|
setEngineCameraProjection(projection: CameraProjectionType) {
|
|
|
|
|
if (projection === 'orthographic') {
|
|
|
|
|
this.useOrthographicCamera()
|
|
|
|
|
} else {
|
|
|
|
|
this.usePerspectiveCamera(true).catch(reportRejection)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-29 19:25:48 +11:00
|
|
|
|
handleStart = () => {
|
|
|
|
|
this._isCamMovingCallback(true, false)
|
|
|
|
|
}
|
|
|
|
|
handleEnd = () => {
|
2024-07-01 15:22:31 -04:00
|
|
|
|
this._isCamMovingCallback(false, false)
|
2024-02-29 19:25:48 +11:00
|
|
|
|
}
|
2024-02-26 19:53:44 +11:00
|
|
|
|
|
|
|
|
|
setCam = (camProps: ReactCameraProperties) => {
|
|
|
|
|
if (
|
|
|
|
|
camProps.type === 'perspective' &&
|
|
|
|
|
this.camera instanceof OrthographicCamera
|
|
|
|
|
) {
|
2024-09-09 18:17:45 -04:00
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
2024-02-26 19:53:44 +11:00
|
|
|
|
this.usePerspectiveCamera()
|
|
|
|
|
} else if (
|
|
|
|
|
camProps.type === 'orthographic' &&
|
|
|
|
|
this.camera instanceof PerspectiveCamera
|
|
|
|
|
) {
|
|
|
|
|
this.useOrthographicCamera()
|
|
|
|
|
}
|
|
|
|
|
this.camera.position.set(...camProps.position)
|
|
|
|
|
this.camera.quaternion.set(...camProps.quaternion)
|
|
|
|
|
if (
|
|
|
|
|
camProps.type === 'perspective' &&
|
|
|
|
|
this.camera instanceof PerspectiveCamera
|
|
|
|
|
) {
|
|
|
|
|
// not sure what to do here, calling dollyZoom here is buggy because it updates the position
|
|
|
|
|
// at the same time
|
|
|
|
|
} else if (
|
|
|
|
|
camProps.type === 'orthographic' &&
|
|
|
|
|
this.camera instanceof OrthographicCamera
|
|
|
|
|
) {
|
|
|
|
|
this.camera.zoom = camProps.zoom || 1
|
|
|
|
|
}
|
|
|
|
|
this.camera.updateProjectionMatrix()
|
2024-03-04 08:14:37 +11:00
|
|
|
|
console.log('doing this thing', camProps)
|
2024-02-26 19:53:44 +11:00
|
|
|
|
this.update(true)
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-22 16:55:30 +11:00
|
|
|
|
throttledEngCmd = throttle((cmd: EngineCommand) => {
|
2024-09-09 18:17:45 -04:00
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
2024-03-22 16:55:30 +11:00
|
|
|
|
this.engineCommandManager.sendSceneCommand(cmd)
|
|
|
|
|
}, 1000 / 30)
|
|
|
|
|
|
|
|
|
|
throttledUpdateEngineCamera = throttle((threeValues: ThreeCamValues) => {
|
|
|
|
|
const cmd: EngineCommand = {
|
|
|
|
|
type: 'modeling_cmd_req',
|
|
|
|
|
cmd_id: uuidv4(),
|
|
|
|
|
cmd: {
|
|
|
|
|
type: 'default_camera_look_at',
|
|
|
|
|
...convertThreeCamValuesToEngineCam(threeValues),
|
|
|
|
|
},
|
|
|
|
|
}
|
2024-09-09 18:17:45 -04:00
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
2024-03-22 16:55:30 +11:00
|
|
|
|
this.engineCommandManager.sendSceneCommand(cmd)
|
|
|
|
|
}, 1000 / 15)
|
|
|
|
|
|
|
|
|
|
lastPerspectiveCmd: EngineCommand | null = null
|
|
|
|
|
lastPerspectiveCmdTime: number = Date.now()
|
|
|
|
|
lastPerspectiveCmdTimeoutId: number | null = null
|
|
|
|
|
|
|
|
|
|
sendLastPerspectiveReliableChannel = () => {
|
|
|
|
|
if (
|
|
|
|
|
this.lastPerspectiveCmd &&
|
|
|
|
|
Date.now() - this.lastPerspectiveCmdTime >= lastCmdDelay
|
|
|
|
|
) {
|
2024-09-09 18:17:45 -04:00
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
2024-03-22 16:55:30 +11:00
|
|
|
|
this.engineCommandManager.sendSceneCommand(this.lastPerspectiveCmd, true)
|
|
|
|
|
this.lastPerspectiveCmdTime = Date.now()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
constructor(
|
|
|
|
|
isOrtho = false,
|
|
|
|
|
domElement: HTMLCanvasElement,
|
|
|
|
|
engineCommandManager: EngineCommandManager
|
|
|
|
|
) {
|
|
|
|
|
this.engineCommandManager = engineCommandManager
|
2024-02-26 19:53:44 +11:00
|
|
|
|
this.camera = isOrtho ? new OrthographicCamera() : new PerspectiveCamera()
|
|
|
|
|
this.camera.up.set(0, 0, 1)
|
|
|
|
|
this.camera.far = 20000
|
|
|
|
|
this.target = new Vector3()
|
|
|
|
|
this.domElement = domElement
|
|
|
|
|
this.isDragging = false
|
|
|
|
|
this.mouseDownPosition = new Vector2()
|
|
|
|
|
this.mouseNewPosition = new Vector2()
|
|
|
|
|
|
|
|
|
|
this.domElement.addEventListener('pointerdown', this.onMouseDown)
|
|
|
|
|
this.domElement.addEventListener('pointermove', this.onMouseMove)
|
|
|
|
|
this.domElement.addEventListener('pointerup', this.onMouseUp)
|
|
|
|
|
this.domElement.addEventListener('wheel', this.onMouseWheel)
|
|
|
|
|
|
|
|
|
|
window.addEventListener('resize', this.onWindowResize)
|
|
|
|
|
this.onWindowResize()
|
|
|
|
|
|
|
|
|
|
this.update()
|
2024-03-01 06:55:49 +11:00
|
|
|
|
this._usePerspectiveCamera()
|
2024-03-02 08:20:50 +11:00
|
|
|
|
|
2024-05-23 22:02:25 +02:00
|
|
|
|
type CallBackParam = Parameters<
|
|
|
|
|
(
|
|
|
|
|
| Subscription<
|
|
|
|
|
| 'default_camera_zoom'
|
|
|
|
|
| 'camera_drag_end'
|
|
|
|
|
| 'default_camera_get_settings'
|
2024-05-23 17:05:54 -07:00
|
|
|
|
| 'zoom_to_fit'
|
2024-05-23 22:02:25 +02:00
|
|
|
|
>
|
|
|
|
|
| UnreliableSubscription<'camera_drag_move'>
|
|
|
|
|
)['callback']
|
|
|
|
|
>[0]
|
|
|
|
|
|
|
|
|
|
const cb = ({ data, type }: CallBackParam) => {
|
2024-03-02 08:20:50 +11:00
|
|
|
|
const camSettings = data.settings
|
|
|
|
|
this.camera.position.set(
|
|
|
|
|
camSettings.pos.x,
|
|
|
|
|
camSettings.pos.y,
|
|
|
|
|
camSettings.pos.z
|
|
|
|
|
)
|
|
|
|
|
this.target.set(
|
|
|
|
|
camSettings.center.x,
|
|
|
|
|
camSettings.center.y,
|
|
|
|
|
camSettings.center.z
|
|
|
|
|
)
|
2024-05-23 22:02:25 +02:00
|
|
|
|
const quat = new Quaternion(
|
|
|
|
|
camSettings.orientation.x,
|
|
|
|
|
camSettings.orientation.y,
|
|
|
|
|
camSettings.orientation.z,
|
|
|
|
|
camSettings.orientation.w
|
|
|
|
|
).invert()
|
2024-07-01 15:22:31 -04:00
|
|
|
|
|
2024-05-23 22:02:25 +02:00
|
|
|
|
this.camera.up.copy(new Vector3(0, 1, 0).applyQuaternion(quat))
|
2024-04-22 20:14:06 +10:00
|
|
|
|
if (this.camera instanceof PerspectiveCamera && camSettings.ortho) {
|
|
|
|
|
this.useOrthographicCamera()
|
|
|
|
|
}
|
|
|
|
|
if (this.camera instanceof OrthographicCamera && !camSettings.ortho) {
|
2024-09-09 18:17:45 -04:00
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
2024-04-22 20:14:06 +10:00
|
|
|
|
this.usePerspectiveCamera()
|
|
|
|
|
}
|
2024-03-02 08:20:50 +11:00
|
|
|
|
if (this.camera instanceof PerspectiveCamera && camSettings.fov_y) {
|
|
|
|
|
this.camera.fov = camSettings.fov_y
|
|
|
|
|
} else if (
|
|
|
|
|
this.camera instanceof OrthographicCamera &&
|
|
|
|
|
camSettings.ortho_scale
|
|
|
|
|
) {
|
2024-04-22 20:14:06 +10:00
|
|
|
|
const distanceToTarget = new Vector3(
|
|
|
|
|
camSettings.pos.x,
|
|
|
|
|
camSettings.pos.y,
|
|
|
|
|
camSettings.pos.z
|
|
|
|
|
).distanceTo(
|
|
|
|
|
new Vector3(
|
|
|
|
|
camSettings.center.x,
|
|
|
|
|
camSettings.center.y,
|
|
|
|
|
camSettings.center.z
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
this.camera.zoom = (camSettings.ortho_scale * 40) / distanceToTarget
|
2024-03-02 08:20:50 +11:00
|
|
|
|
}
|
|
|
|
|
this.onCameraChange()
|
|
|
|
|
}
|
2024-07-01 15:22:31 -04:00
|
|
|
|
|
|
|
|
|
// Our stream is never more than 60fps.
|
|
|
|
|
// We can get away with capping our "virtual fps" to 60 then.
|
|
|
|
|
const FPS_VIRTUAL = 60
|
|
|
|
|
|
|
|
|
|
const doZoom = () => {
|
|
|
|
|
if (this.zoomDataFromLastFrame !== undefined) {
|
|
|
|
|
this.handleStart()
|
2024-09-09 18:17:45 -04:00
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
2024-07-01 15:22:31 -04:00
|
|
|
|
this.engineCommandManager.sendSceneCommand({
|
|
|
|
|
type: 'modeling_cmd_req',
|
|
|
|
|
cmd: {
|
|
|
|
|
type: 'default_camera_zoom',
|
|
|
|
|
magnitude:
|
|
|
|
|
(-1 * this.zoomDataFromLastFrame) / window.devicePixelRatio,
|
|
|
|
|
},
|
|
|
|
|
cmd_id: uuidv4(),
|
|
|
|
|
})
|
|
|
|
|
this.handleEnd()
|
|
|
|
|
}
|
|
|
|
|
this.zoomDataFromLastFrame = undefined
|
|
|
|
|
}
|
|
|
|
|
setInterval(doZoom, 1000 / FPS_VIRTUAL)
|
|
|
|
|
|
|
|
|
|
const doMove = () => {
|
|
|
|
|
if (this.moveDataFromLastFrame !== undefined) {
|
2024-09-09 18:17:45 -04:00
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
2024-07-01 15:22:31 -04:00
|
|
|
|
this.engineCommandManager.sendSceneCommand({
|
|
|
|
|
type: 'modeling_cmd_req',
|
|
|
|
|
cmd: {
|
|
|
|
|
type: 'camera_drag_move',
|
|
|
|
|
interaction: this.moveDataFromLastFrame[2] as any,
|
|
|
|
|
window: {
|
|
|
|
|
x: this.moveDataFromLastFrame[0],
|
|
|
|
|
y: this.moveDataFromLastFrame[1],
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
cmd_id: uuidv4(),
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
this.moveDataFromLastFrame = undefined
|
|
|
|
|
}
|
|
|
|
|
setInterval(doMove, 1000 / FPS_VIRTUAL)
|
|
|
|
|
|
2024-03-02 08:20:50 +11:00
|
|
|
|
setTimeout(() => {
|
2024-03-22 09:35:07 -04:00
|
|
|
|
this.engineCommandManager.subscribeTo({
|
2024-03-02 08:20:50 +11:00
|
|
|
|
event: 'camera_drag_end',
|
|
|
|
|
callback: cb,
|
|
|
|
|
})
|
2024-03-22 09:35:07 -04:00
|
|
|
|
this.engineCommandManager.subscribeTo({
|
2024-03-02 08:20:50 +11:00
|
|
|
|
event: 'default_camera_zoom',
|
|
|
|
|
callback: cb,
|
|
|
|
|
})
|
2024-03-22 09:35:07 -04:00
|
|
|
|
this.engineCommandManager.subscribeTo({
|
2024-03-02 08:20:50 +11:00
|
|
|
|
event: 'default_camera_get_settings',
|
|
|
|
|
callback: cb,
|
|
|
|
|
})
|
2024-05-23 17:05:54 -07:00
|
|
|
|
this.engineCommandManager.subscribeTo({
|
|
|
|
|
event: 'zoom_to_fit',
|
|
|
|
|
callback: cb,
|
|
|
|
|
})
|
2024-05-23 22:02:25 +02:00
|
|
|
|
this.engineCommandManager.subscribeToUnreliable({
|
|
|
|
|
event: 'camera_drag_move',
|
|
|
|
|
callback: cb,
|
|
|
|
|
})
|
2024-03-02 08:20:50 +11:00
|
|
|
|
})
|
2024-02-26 19:53:44 +11:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private _isCamMovingCallback: (isMoving: boolean, isTween: boolean) => void =
|
|
|
|
|
() => {}
|
|
|
|
|
setIsCamMovingCallback(cb: (isMoving: boolean, isTween: boolean) => void) {
|
|
|
|
|
this._isCamMovingCallback = cb
|
|
|
|
|
}
|
|
|
|
|
private _camChangeCallbacks: { [key: string]: () => void } = {}
|
|
|
|
|
subscribeToCamChange(cb: () => void) {
|
|
|
|
|
const cbId = uuidv4()
|
|
|
|
|
this._camChangeCallbacks[cbId] = cb
|
|
|
|
|
const unsubscribe = () => {
|
|
|
|
|
delete this._camChangeCallbacks[cbId]
|
|
|
|
|
}
|
|
|
|
|
return unsubscribe
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
onWindowResize = () => {
|
|
|
|
|
if (this.camera instanceof PerspectiveCamera) {
|
|
|
|
|
this.camera.aspect = window.innerWidth / window.innerHeight
|
|
|
|
|
} else if (this.camera instanceof OrthographicCamera) {
|
|
|
|
|
const aspect = window.innerWidth / window.innerHeight
|
|
|
|
|
this.camera.left = -ORTHOGRAPHIC_CAMERA_SIZE * aspect
|
|
|
|
|
this.camera.right = ORTHOGRAPHIC_CAMERA_SIZE * aspect
|
|
|
|
|
this.camera.top = ORTHOGRAPHIC_CAMERA_SIZE
|
|
|
|
|
this.camera.bottom = -ORTHOGRAPHIC_CAMERA_SIZE
|
|
|
|
|
}
|
|
|
|
|
this.camera.updateProjectionMatrix()
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-12 22:06:50 -04:00
|
|
|
|
onMouseDown = (event: PointerEvent) => {
|
|
|
|
|
this.domElement.setPointerCapture(event.pointerId)
|
2024-02-26 19:53:44 +11:00
|
|
|
|
this.isDragging = true
|
|
|
|
|
this.mouseDownPosition.set(event.clientX, event.clientY)
|
2024-03-02 08:20:50 +11:00
|
|
|
|
let interaction = this.getInteractionType(event)
|
|
|
|
|
if (interaction === 'none') return
|
2024-02-29 19:25:48 +11:00
|
|
|
|
this.handleStart()
|
2024-03-02 08:20:50 +11:00
|
|
|
|
|
|
|
|
|
if (this.syncDirection === 'engineToClient') {
|
2024-03-22 16:55:30 +11:00
|
|
|
|
void this.engineCommandManager.sendSceneCommand({
|
2024-03-02 08:20:50 +11:00
|
|
|
|
type: 'modeling_cmd_req',
|
|
|
|
|
cmd: {
|
|
|
|
|
type: 'camera_drag_start',
|
|
|
|
|
interaction,
|
|
|
|
|
window: { x: event.clientX, y: event.clientY },
|
|
|
|
|
},
|
|
|
|
|
cmd_id: uuidv4(),
|
|
|
|
|
})
|
|
|
|
|
}
|
2024-02-26 19:53:44 +11:00
|
|
|
|
}
|
|
|
|
|
|
2024-09-12 22:06:50 -04:00
|
|
|
|
onMouseMove = (event: PointerEvent) => {
|
2024-02-26 19:53:44 +11:00
|
|
|
|
if (this.isDragging) {
|
|
|
|
|
this.mouseNewPosition.set(event.clientX, event.clientY)
|
|
|
|
|
const deltaMove = this.mouseNewPosition
|
|
|
|
|
.clone()
|
|
|
|
|
.sub(this.mouseDownPosition)
|
|
|
|
|
this.mouseDownPosition.copy(this.mouseNewPosition)
|
|
|
|
|
|
2024-03-02 08:20:50 +11:00
|
|
|
|
const interaction = this.getInteractionType(event)
|
|
|
|
|
if (interaction === 'none') return
|
|
|
|
|
|
|
|
|
|
if (this.syncDirection === 'engineToClient') {
|
2024-07-01 15:22:31 -04:00
|
|
|
|
this.moveDataFromLastFrame = [event.clientX, event.clientY, interaction]
|
2024-02-26 19:53:44 +11:00
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Implement camera movement logic here based on deltaMove
|
|
|
|
|
// For example, for rotating the camera around the target:
|
2024-03-02 08:20:50 +11:00
|
|
|
|
if (interaction === 'rotate') {
|
2024-02-26 19:53:44 +11:00
|
|
|
|
this.pendingRotation = this.pendingRotation
|
|
|
|
|
? this.pendingRotation
|
|
|
|
|
: new Vector2()
|
|
|
|
|
this.pendingRotation.x += deltaMove.x
|
|
|
|
|
this.pendingRotation.y += deltaMove.y
|
2024-03-02 08:20:50 +11:00
|
|
|
|
} else if (interaction === 'zoom') {
|
2024-02-26 19:53:44 +11:00
|
|
|
|
this.pendingZoom = this.pendingZoom ? this.pendingZoom : 1
|
|
|
|
|
this.pendingZoom *= 1 + deltaMove.y * 0.01
|
2024-03-02 08:20:50 +11:00
|
|
|
|
} else if (interaction === 'pan') {
|
2024-02-26 19:53:44 +11:00
|
|
|
|
this.pendingPan = this.pendingPan ? this.pendingPan : new Vector2()
|
|
|
|
|
let distance = this.camera.position.distanceTo(this.target)
|
|
|
|
|
if (this.camera instanceof OrthographicCamera) {
|
|
|
|
|
const zoomFudgeFactor = 2280
|
|
|
|
|
distance = zoomFudgeFactor / (this.camera.zoom * 45)
|
|
|
|
|
}
|
2024-09-19 15:39:32 -04:00
|
|
|
|
const panSpeed = (distance / 1000 / 45) * this.perspectiveFovBeforeOrtho
|
2024-02-26 19:53:44 +11:00
|
|
|
|
this.pendingPan.x += -deltaMove.x * panSpeed
|
|
|
|
|
this.pendingPan.y += deltaMove.y * panSpeed
|
|
|
|
|
}
|
2024-09-12 22:06:50 -04:00
|
|
|
|
} else {
|
|
|
|
|
/**
|
|
|
|
|
* If we're not in sketch mode and not dragging, we can highlight entities
|
|
|
|
|
* under the cursor. This recently moved from being handled in App.tsx.
|
|
|
|
|
* This might not be the right spot, but it is more consolidated.
|
|
|
|
|
*/
|
|
|
|
|
if (this.syncDirection === 'engineToClient') {
|
|
|
|
|
const newCmdId = uuidv4()
|
|
|
|
|
|
|
|
|
|
this.throttledEngCmd({
|
|
|
|
|
type: 'modeling_cmd_req',
|
|
|
|
|
cmd: {
|
|
|
|
|
type: 'highlight_set_entity',
|
|
|
|
|
selected_at_window: { x: event.clientX, y: event.clientY },
|
|
|
|
|
},
|
|
|
|
|
cmd_id: newCmdId,
|
|
|
|
|
})
|
|
|
|
|
}
|
2024-02-26 19:53:44 +11:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-12 22:06:50 -04:00
|
|
|
|
onMouseUp = (event: PointerEvent) => {
|
|
|
|
|
this.domElement.releasePointerCapture(event.pointerId)
|
2024-02-26 19:53:44 +11:00
|
|
|
|
this.isDragging = false
|
2024-02-29 19:25:48 +11:00
|
|
|
|
this.handleEnd()
|
2024-03-02 08:20:50 +11:00
|
|
|
|
if (this.syncDirection === 'engineToClient') {
|
|
|
|
|
const interaction = this.getInteractionType(event)
|
|
|
|
|
if (interaction === 'none') return
|
2024-03-22 16:55:30 +11:00
|
|
|
|
void this.engineCommandManager.sendSceneCommand({
|
2024-03-02 08:20:50 +11:00
|
|
|
|
type: 'modeling_cmd_req',
|
|
|
|
|
cmd: {
|
|
|
|
|
type: 'camera_drag_end',
|
|
|
|
|
interaction,
|
|
|
|
|
window: { x: event.clientX, y: event.clientY },
|
|
|
|
|
},
|
|
|
|
|
cmd_id: uuidv4(),
|
|
|
|
|
})
|
|
|
|
|
}
|
2024-02-26 19:53:44 +11:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
onMouseWheel = (event: WheelEvent) => {
|
2024-09-19 17:26:27 -04:00
|
|
|
|
const interaction = this.getInteractionType(event)
|
|
|
|
|
if (interaction === 'none') return
|
|
|
|
|
event.preventDefault()
|
|
|
|
|
|
2024-03-02 08:20:50 +11:00
|
|
|
|
if (this.syncDirection === 'engineToClient') {
|
2024-09-19 17:26:27 -04:00
|
|
|
|
if (interaction === 'zoom') {
|
|
|
|
|
this.zoomDataFromLastFrame = event.deltaY
|
|
|
|
|
} else {
|
|
|
|
|
// This case will get handled when we add pan and rotate using Apple trackpad.
|
|
|
|
|
console.error(
|
|
|
|
|
`Unexpected interaction type for engineToClient wheel event: ${interaction}`
|
|
|
|
|
)
|
|
|
|
|
}
|
2024-03-02 08:20:50 +11:00
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2024-07-01 15:22:31 -04:00
|
|
|
|
// else "clientToEngine" (Sketch Mode) or forceUpdate
|
2024-02-26 19:53:44 +11:00
|
|
|
|
|
2024-07-01 15:22:31 -04:00
|
|
|
|
// We need to simulate similar behavior as when we send
|
|
|
|
|
// zoom commands to engine. This means dropping some zoom
|
|
|
|
|
// commands too.
|
2024-06-06 16:48:54 -07:00
|
|
|
|
// From onMouseMove zoom handling which seems to be really smooth
|
2024-09-19 17:26:27 -04:00
|
|
|
|
|
2024-07-01 15:22:31 -04:00
|
|
|
|
this.handleStart()
|
2024-09-19 17:26:27 -04:00
|
|
|
|
if (interaction === 'zoom') {
|
|
|
|
|
this.pendingZoom = 1 + (event.deltaY / window.devicePixelRatio) * 0.001
|
|
|
|
|
} else {
|
|
|
|
|
// This case will get handled when we add pan and rotate using Apple trackpad.
|
|
|
|
|
console.error(
|
|
|
|
|
`Unexpected interaction type for wheel event: ${interaction}`
|
|
|
|
|
)
|
|
|
|
|
}
|
2024-02-29 19:25:48 +11:00
|
|
|
|
this.handleEnd()
|
2024-02-26 19:53:44 +11:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
useOrthographicCamera = () => {
|
|
|
|
|
if (this.camera instanceof OrthographicCamera) return
|
|
|
|
|
const { x: px, y: py, z: pz } = this.camera.position
|
|
|
|
|
const { x: qx, y: qy, z: qz, w: qw } = this.camera.quaternion
|
2024-03-22 10:23:04 +11:00
|
|
|
|
const oldCamUp = this.camera.up.clone()
|
2024-02-26 19:53:44 +11:00
|
|
|
|
const aspect = window.innerWidth / window.innerHeight
|
|
|
|
|
this.lastPerspectiveFov = this.camera.fov
|
|
|
|
|
const { z_near, z_far } = calculateNearFarFromFOV(this.lastPerspectiveFov)
|
|
|
|
|
this.camera = new OrthographicCamera(
|
|
|
|
|
-ORTHOGRAPHIC_CAMERA_SIZE * aspect,
|
|
|
|
|
ORTHOGRAPHIC_CAMERA_SIZE * aspect,
|
|
|
|
|
ORTHOGRAPHIC_CAMERA_SIZE,
|
|
|
|
|
-ORTHOGRAPHIC_CAMERA_SIZE,
|
|
|
|
|
z_near,
|
|
|
|
|
z_far
|
|
|
|
|
)
|
2024-03-22 10:23:04 +11:00
|
|
|
|
|
|
|
|
|
this.camera.up.copy(oldCamUp)
|
2024-02-26 19:53:44 +11:00
|
|
|
|
this.camera.layers.enable(SKETCH_LAYER)
|
|
|
|
|
if (DEBUG_SHOW_INTERSECTION_PLANE)
|
|
|
|
|
this.camera.layers.enable(INTERSECTION_PLANE_LAYER)
|
|
|
|
|
this.camera.position.set(px, py, pz)
|
|
|
|
|
const distance = this.camera.position.distanceTo(this.target.clone())
|
|
|
|
|
const fovFactor = 45 / this.lastPerspectiveFov
|
|
|
|
|
this.camera.zoom = (ZOOM_MAGIC_NUMBER * fovFactor * 0.8) / distance
|
|
|
|
|
|
|
|
|
|
this.camera.quaternion.set(qx, qy, qz, qw)
|
|
|
|
|
this.camera.updateProjectionMatrix()
|
2024-09-09 18:17:45 -04:00
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
2024-03-22 16:55:30 +11:00
|
|
|
|
this.engineCommandManager.sendSceneCommand({
|
2024-02-26 19:53:44 +11:00
|
|
|
|
type: 'modeling_cmd_req',
|
|
|
|
|
cmd_id: uuidv4(),
|
|
|
|
|
cmd: {
|
|
|
|
|
type: 'default_camera_set_orthographic',
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
this.onCameraChange()
|
|
|
|
|
}
|
|
|
|
|
private createPerspectiveCamera = () => {
|
|
|
|
|
const { z_near, z_far } = calculateNearFarFromFOV(this.lastPerspectiveFov)
|
2024-03-22 10:23:04 +11:00
|
|
|
|
const previousCamUp = this.camera.up.clone()
|
2024-02-26 19:53:44 +11:00
|
|
|
|
this.camera = new PerspectiveCamera(
|
|
|
|
|
this.lastPerspectiveFov,
|
|
|
|
|
window.innerWidth / window.innerHeight,
|
|
|
|
|
z_near,
|
|
|
|
|
z_far
|
|
|
|
|
)
|
2024-03-22 10:23:04 +11:00
|
|
|
|
this.camera.up.copy(previousCamUp)
|
2024-02-26 19:53:44 +11:00
|
|
|
|
this.camera.layers.enable(SKETCH_LAYER)
|
|
|
|
|
if (DEBUG_SHOW_INTERSECTION_PLANE)
|
|
|
|
|
this.camera.layers.enable(INTERSECTION_PLANE_LAYER)
|
|
|
|
|
|
|
|
|
|
return this.camera
|
|
|
|
|
}
|
2024-03-01 06:55:49 +11:00
|
|
|
|
_usePerspectiveCamera = () => {
|
2024-02-26 19:53:44 +11:00
|
|
|
|
const { x: px, y: py, z: pz } = this.camera.position
|
|
|
|
|
const { x: qx, y: qy, z: qz, w: qw } = this.camera.quaternion
|
|
|
|
|
this.camera = this.createPerspectiveCamera()
|
|
|
|
|
|
|
|
|
|
this.camera.position.set(px, py, pz)
|
|
|
|
|
this.camera.quaternion.set(qx, qy, qz, qw)
|
|
|
|
|
const direction = new Vector3().subVectors(
|
|
|
|
|
this.camera.position,
|
|
|
|
|
this.target
|
|
|
|
|
)
|
|
|
|
|
direction.normalize()
|
2024-03-01 06:55:49 +11:00
|
|
|
|
}
|
2024-07-10 17:50:25 -04:00
|
|
|
|
usePerspectiveCamera = async (forceSend = false) => {
|
2024-03-01 06:55:49 +11:00
|
|
|
|
this._usePerspectiveCamera()
|
2024-07-10 17:50:25 -04:00
|
|
|
|
if (forceSend || this.syncDirection === 'clientToEngine') {
|
2024-06-18 16:08:41 +10:00
|
|
|
|
await this.engineCommandManager.sendSceneCommand({
|
|
|
|
|
type: 'modeling_cmd_req',
|
|
|
|
|
cmd_id: uuidv4(),
|
|
|
|
|
cmd: {
|
|
|
|
|
type: 'default_camera_set_perspective',
|
|
|
|
|
parameters: {
|
|
|
|
|
fov_y:
|
|
|
|
|
this.camera instanceof PerspectiveCamera ? this.camera.fov : 45,
|
|
|
|
|
},
|
2024-02-26 19:53:44 +11:00
|
|
|
|
},
|
2024-06-18 16:08:41 +10:00
|
|
|
|
})
|
|
|
|
|
}
|
2024-02-26 19:53:44 +11:00
|
|
|
|
this.onCameraChange()
|
|
|
|
|
this.update()
|
|
|
|
|
return this.camera
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-18 16:08:41 +10:00
|
|
|
|
dollyZoom = async (newFov: number, splitEngineCalls = false) => {
|
2024-02-26 19:53:44 +11:00
|
|
|
|
if (!(this.camera instanceof PerspectiveCamera)) {
|
|
|
|
|
console.warn('Dolly zoom is only applicable to perspective cameras.')
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
this.lastPerspectiveFov = newFov
|
|
|
|
|
|
|
|
|
|
// Calculate the direction vector from the camera towards the controls target
|
|
|
|
|
const direction = new Vector3()
|
|
|
|
|
.subVectors(this.target, this.camera.position)
|
|
|
|
|
.normalize()
|
|
|
|
|
|
|
|
|
|
// Calculate the distance to the controls target before changing the FOV
|
|
|
|
|
const distanceBefore = this.camera.position.distanceTo(this.target)
|
|
|
|
|
|
|
|
|
|
// Calculate the scale factor for the new FOV compared to the old one
|
|
|
|
|
// This needs to be calculated before updating the camera's FOV
|
|
|
|
|
const oldFov = this.camera.fov
|
|
|
|
|
|
|
|
|
|
const viewHeightFactor = (fov: number) => {
|
2024-09-10 11:45:32 -05:00
|
|
|
|
/* *
|
2024-02-26 19:53:44 +11:00
|
|
|
|
/|
|
|
|
|
|
/ |
|
|
|
|
|
/ |
|
|
|
|
|
/ |
|
|
|
|
|
/ | viewHeight/2
|
|
|
|
|
/ |
|
|
|
|
|
/ |
|
|
|
|
|
/↙️fov/2 |
|
|
|
|
|
/________|
|
|
|
|
|
\ |
|
|
|
|
|
\._._._.|
|
|
|
|
|
*/
|
|
|
|
|
return Math.tan(deg2Rad(fov / 2))
|
|
|
|
|
}
|
|
|
|
|
const scaleFactor = viewHeightFactor(oldFov) / viewHeightFactor(newFov)
|
|
|
|
|
|
|
|
|
|
this.camera.fov = newFov
|
|
|
|
|
this.camera.updateProjectionMatrix()
|
|
|
|
|
|
|
|
|
|
const distanceAfter = distanceBefore * scaleFactor
|
|
|
|
|
|
|
|
|
|
const newPosition = this.target
|
|
|
|
|
.clone()
|
|
|
|
|
.add(direction.multiplyScalar(-distanceAfter))
|
|
|
|
|
this.camera.position.copy(newPosition)
|
|
|
|
|
|
2024-06-18 16:08:41 +10:00
|
|
|
|
if (splitEngineCalls) {
|
|
|
|
|
await this.engineCommandManager.sendSceneCommand({
|
|
|
|
|
type: 'modeling_cmd_req',
|
|
|
|
|
cmd_id: uuidv4(),
|
|
|
|
|
cmd: {
|
|
|
|
|
type: 'default_camera_look_at',
|
|
|
|
|
...convertThreeCamValuesToEngineCam({
|
|
|
|
|
isPerspective: true,
|
|
|
|
|
position: newPosition,
|
|
|
|
|
quaternion: this.camera.quaternion,
|
|
|
|
|
zoom: this.camera.zoom,
|
|
|
|
|
target: this.target,
|
|
|
|
|
}),
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
await this.engineCommandManager.sendSceneCommand({
|
|
|
|
|
type: 'modeling_cmd_req',
|
|
|
|
|
cmd_id: uuidv4(),
|
|
|
|
|
cmd: {
|
|
|
|
|
type: 'default_camera_set_perspective',
|
|
|
|
|
parameters: {
|
|
|
|
|
fov_y: newFov,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
} else {
|
|
|
|
|
await this.engineCommandManager.sendSceneCommand({
|
|
|
|
|
type: 'modeling_cmd_req',
|
|
|
|
|
cmd_id: uuidv4(),
|
|
|
|
|
cmd: {
|
|
|
|
|
type: 'default_camera_perspective_settings',
|
|
|
|
|
...convertThreeCamValuesToEngineCam({
|
|
|
|
|
isPerspective: true,
|
|
|
|
|
position: newPosition,
|
|
|
|
|
quaternion: this.camera.quaternion,
|
|
|
|
|
zoom: this.camera.zoom,
|
|
|
|
|
target: this.target,
|
|
|
|
|
}),
|
|
|
|
|
fov_y: newFov,
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
}
|
2024-02-26 19:53:44 +11:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
update = (forceUpdate = false) => {
|
|
|
|
|
// If there are any changes that need to be applied to the camera, apply them here.
|
|
|
|
|
|
2024-03-04 08:14:37 +11:00
|
|
|
|
let didChange = false
|
2024-02-26 19:53:44 +11:00
|
|
|
|
if (this.pendingRotation) {
|
|
|
|
|
this.rotateCamera(this.pendingRotation.x, this.pendingRotation.y)
|
|
|
|
|
this.pendingRotation = null // Clear the pending rotation after applying it
|
|
|
|
|
didChange = true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (this.pendingZoom) {
|
|
|
|
|
if (this.camera instanceof PerspectiveCamera) {
|
|
|
|
|
// move camera towards or away from the target
|
|
|
|
|
const distance = this.camera.position.distanceTo(this.target)
|
|
|
|
|
const newDistance = distance * this.pendingZoom
|
|
|
|
|
const direction = this.camera.position
|
|
|
|
|
.clone()
|
|
|
|
|
.sub(this.target)
|
|
|
|
|
.normalize()
|
|
|
|
|
const newPosition = this.target
|
|
|
|
|
.clone()
|
|
|
|
|
.add(direction.multiplyScalar(newDistance))
|
|
|
|
|
this.camera.position.copy(newPosition)
|
|
|
|
|
|
|
|
|
|
this.camera.updateProjectionMatrix()
|
|
|
|
|
this.pendingZoom = null // Clear the pending zoom after applying it
|
|
|
|
|
} else {
|
|
|
|
|
// TODO change ortho zoom
|
|
|
|
|
this.camera.zoom = this.camera.zoom / this.pendingZoom
|
|
|
|
|
this.pendingZoom = null
|
|
|
|
|
}
|
|
|
|
|
didChange = true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (this.pendingPan) {
|
|
|
|
|
// move camera left/right and up/down
|
|
|
|
|
const offset = this.camera.position.clone().sub(this.target)
|
|
|
|
|
const direction = offset.clone().normalize()
|
|
|
|
|
const cameraQuaternion = this.camera.quaternion
|
|
|
|
|
const up = new Vector3(0, 1, 0).applyQuaternion(cameraQuaternion)
|
|
|
|
|
const right = new Vector3().crossVectors(up, direction)
|
|
|
|
|
right.multiplyScalar(this.pendingPan.x)
|
|
|
|
|
up.multiplyScalar(this.pendingPan.y)
|
|
|
|
|
const newPosition = this.camera.position.clone().add(right).add(up)
|
|
|
|
|
this.target.add(right)
|
|
|
|
|
this.target.add(up)
|
|
|
|
|
this.camera.position.copy(newPosition)
|
|
|
|
|
this.pendingPan = null
|
|
|
|
|
didChange = true
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-22 10:23:04 +11:00
|
|
|
|
this.safeLookAtTarget(this.camera.up)
|
2024-02-26 19:53:44 +11:00
|
|
|
|
|
|
|
|
|
// Update the camera's matrices
|
|
|
|
|
this.camera.updateMatrixWorld()
|
2024-03-04 08:14:37 +11:00
|
|
|
|
if (didChange || forceUpdate) {
|
|
|
|
|
this.onCameraChange(forceUpdate)
|
2024-02-26 19:53:44 +11:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// damping would be implemented here in update if we choose to add it.
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
rotateCamera = (deltaX: number, deltaY: number) => {
|
|
|
|
|
const quat = new Quaternion().setFromUnitVectors(
|
|
|
|
|
new Vector3(0, 0, 1),
|
|
|
|
|
new Vector3(0, 1, 0)
|
|
|
|
|
)
|
|
|
|
|
const quatInverse = quat.clone().invert()
|
|
|
|
|
|
|
|
|
|
const angleX = deltaX * this.rotationSpeed // rotationSpeed is a constant that defines how fast the camera rotates
|
|
|
|
|
const angleY = deltaY * this.rotationSpeed
|
|
|
|
|
|
|
|
|
|
// Convert angles to radians
|
|
|
|
|
const radianX = MathUtils.degToRad(angleX)
|
|
|
|
|
const radianY = MathUtils.degToRad(angleY)
|
|
|
|
|
|
|
|
|
|
// Get the offset from the camera to the target
|
|
|
|
|
const offset = new Vector3().subVectors(this.camera.position, this.target)
|
|
|
|
|
|
|
|
|
|
// spherical is a y-up paradigm, need to conform to that for now
|
|
|
|
|
offset.applyQuaternion(quat)
|
|
|
|
|
|
|
|
|
|
// Convert offset to spherical coordinates
|
|
|
|
|
const spherical = new Spherical().setFromVector3(offset)
|
|
|
|
|
|
|
|
|
|
// Apply the rotations
|
|
|
|
|
spherical.theta -= radianX
|
|
|
|
|
spherical.phi -= radianY
|
|
|
|
|
|
|
|
|
|
// Restrict the phi angle to avoid the camera flipping at the poles
|
|
|
|
|
spherical.phi = Math.max(0.1, Math.min(Math.PI - 0.1, spherical.phi))
|
|
|
|
|
|
|
|
|
|
// Convert back to Cartesian coordinates
|
|
|
|
|
offset.setFromSpherical(spherical)
|
|
|
|
|
|
|
|
|
|
// put the offset back into the z-up paradigm
|
|
|
|
|
offset.applyQuaternion(quatInverse)
|
|
|
|
|
|
|
|
|
|
// Update the camera's position
|
|
|
|
|
this.camera.position.copy(this.target).add(offset)
|
|
|
|
|
|
|
|
|
|
// Look at the target
|
|
|
|
|
this.camera.updateMatrixWorld()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
safeLookAtTarget(up = new Vector3(0, 0, 1)) {
|
|
|
|
|
const quaternion = _lookAt(this.camera.position, this.target, up)
|
|
|
|
|
this.camera.quaternion.copy(quaternion)
|
|
|
|
|
this.camera.updateMatrixWorld()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
tweenCamToNegYAxis(
|
|
|
|
|
// -90 degrees from the x axis puts the camera on the negative y axis
|
|
|
|
|
targetAngle = -Math.PI / 2,
|
|
|
|
|
duration = 500
|
|
|
|
|
): Promise<void> {
|
|
|
|
|
return new Promise((resolve) => {
|
2024-03-22 10:23:04 +11:00
|
|
|
|
// should tween the camera so that it has an xPosition of 0, and forcing it's yPosition to be negative
|
|
|
|
|
// zPosition should stay the same
|
|
|
|
|
const xyRadius = Math.sqrt(
|
|
|
|
|
(this.target.x - this.camera.position.x) ** 2 +
|
|
|
|
|
(this.target.y - this.camera.position.y) ** 2
|
|
|
|
|
)
|
|
|
|
|
const xyAngle = Math.atan2(
|
|
|
|
|
this.camera.position.y - this.target.y,
|
|
|
|
|
this.camera.position.x - this.target.x
|
|
|
|
|
)
|
|
|
|
|
const camAtTime = (obj: { angle: number }) => {
|
|
|
|
|
const x = xyRadius * Math.cos(obj.angle)
|
|
|
|
|
const y = xyRadius * Math.sin(obj.angle)
|
|
|
|
|
this.camera.position.set(
|
|
|
|
|
this.target.x + x,
|
|
|
|
|
this.target.y + y,
|
|
|
|
|
this.camera.position.z
|
|
|
|
|
)
|
|
|
|
|
this.update()
|
|
|
|
|
this.onCameraChange()
|
|
|
|
|
}
|
|
|
|
|
const onComplete = (obj: { angle: number }) => {
|
|
|
|
|
camAtTime(obj)
|
|
|
|
|
this._isCamMovingCallback(false, true)
|
|
|
|
|
|
|
|
|
|
// resolve after a couple of frames
|
|
|
|
|
requestAnimationFrame(() => {
|
|
|
|
|
requestAnimationFrame(() => resolve())
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
this._isCamMovingCallback(true, true)
|
|
|
|
|
|
|
|
|
|
if (isReducedMotion()) {
|
|
|
|
|
onComplete({ angle: targetAngle })
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-26 19:53:44 +11:00
|
|
|
|
new TWEEN.Tween({ angle: xyAngle })
|
|
|
|
|
.to({ angle: targetAngle }, duration)
|
2024-03-22 10:23:04 +11:00
|
|
|
|
.onUpdate(camAtTime)
|
|
|
|
|
.onComplete(onComplete)
|
2024-02-26 19:53:44 +11:00
|
|
|
|
.start()
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-05 14:43:12 +02:00
|
|
|
|
async updateCameraToAxis(
|
|
|
|
|
axis: 'x' | 'y' | 'z' | '-x' | '-y' | '-z'
|
|
|
|
|
): Promise<void> {
|
|
|
|
|
const distance = this.camera.position.distanceTo(this.target)
|
|
|
|
|
|
|
|
|
|
const vantage = this.target.clone()
|
|
|
|
|
let up = { x: 0, y: 0, z: 1 }
|
|
|
|
|
|
|
|
|
|
if (axis === 'x') {
|
|
|
|
|
vantage.x += distance
|
|
|
|
|
} else if (axis === 'y') {
|
|
|
|
|
vantage.y += distance
|
|
|
|
|
} else if (axis === 'z') {
|
|
|
|
|
vantage.z += distance
|
|
|
|
|
up = { x: -1, y: 0, z: 0 }
|
|
|
|
|
} else if (axis === '-x') {
|
|
|
|
|
vantage.x -= distance
|
|
|
|
|
} else if (axis === '-y') {
|
|
|
|
|
vantage.y -= distance
|
|
|
|
|
} else if (axis === '-z') {
|
|
|
|
|
vantage.z -= distance
|
|
|
|
|
up = { x: -1, y: 0, z: 0 }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await this.engineCommandManager.sendSceneCommand({
|
|
|
|
|
type: 'modeling_cmd_req',
|
|
|
|
|
cmd_id: uuidv4(),
|
|
|
|
|
cmd: {
|
|
|
|
|
type: 'default_camera_look_at',
|
|
|
|
|
center: this.target,
|
|
|
|
|
vantage: vantage,
|
|
|
|
|
up: up,
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
await this.engineCommandManager.sendSceneCommand({
|
|
|
|
|
type: 'modeling_cmd_req',
|
|
|
|
|
cmd_id: uuidv4(),
|
|
|
|
|
cmd: {
|
|
|
|
|
type: 'default_camera_get_settings',
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async resetCameraPosition(): Promise<void> {
|
|
|
|
|
await this.engineCommandManager.sendSceneCommand({
|
|
|
|
|
type: 'modeling_cmd_req',
|
|
|
|
|
cmd_id: uuidv4(),
|
|
|
|
|
cmd: {
|
|
|
|
|
type: 'default_camera_look_at',
|
|
|
|
|
center: this.target,
|
|
|
|
|
vantage: {
|
|
|
|
|
x: this.target.x,
|
|
|
|
|
y: this.target.y - 128,
|
|
|
|
|
z: this.target.z + 64,
|
|
|
|
|
},
|
|
|
|
|
up: { x: 0, y: 0, z: 1 },
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
await this.engineCommandManager.sendSceneCommand({
|
|
|
|
|
type: 'modeling_cmd_req',
|
|
|
|
|
cmd_id: uuidv4(),
|
|
|
|
|
cmd: {
|
|
|
|
|
type: 'zoom_to_fit',
|
|
|
|
|
object_ids: [], // leave empty to zoom to all objects
|
|
|
|
|
padding: 0.2, // padding around the objects
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2024-02-26 19:53:44 +11:00
|
|
|
|
async tweenCameraToQuaternion(
|
|
|
|
|
targetQuaternion: Quaternion,
|
2024-03-08 18:52:31 +11:00
|
|
|
|
targetPosition = new Vector3(),
|
2024-02-26 19:53:44 +11:00
|
|
|
|
duration = 500,
|
|
|
|
|
toOrthographic = true
|
|
|
|
|
): Promise<void> {
|
2024-03-02 08:20:50 +11:00
|
|
|
|
if (this.syncDirection === 'engineToClient')
|
|
|
|
|
console.warn(
|
|
|
|
|
'tweenCameraToQuaternion not design to work with engineToClient syncDirection.'
|
|
|
|
|
)
|
2024-02-26 19:53:44 +11:00
|
|
|
|
const isVertical = isQuaternionVertical(targetQuaternion)
|
|
|
|
|
let remainingDuration = duration
|
|
|
|
|
if (isVertical) {
|
|
|
|
|
remainingDuration = duration * 0.5
|
|
|
|
|
const orbitRotationDuration = duration * 0.65
|
|
|
|
|
let targetAngle = -Math.PI / 2
|
|
|
|
|
const v = new Vector3(0, 0, 1).applyQuaternion(targetQuaternion)
|
|
|
|
|
if (v.z < 0) targetAngle = Math.PI / 2
|
|
|
|
|
await this.tweenCamToNegYAxis(targetAngle, orbitRotationDuration)
|
|
|
|
|
}
|
|
|
|
|
await this._tweenCameraToQuaternion(
|
|
|
|
|
targetQuaternion,
|
2024-03-08 18:52:31 +11:00
|
|
|
|
targetPosition,
|
2024-02-26 19:53:44 +11:00
|
|
|
|
remainingDuration,
|
|
|
|
|
toOrthographic
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
_tweenCameraToQuaternion(
|
|
|
|
|
targetQuaternion: Quaternion,
|
2024-03-08 18:52:31 +11:00
|
|
|
|
targetPosition: Vector3,
|
2024-02-26 19:53:44 +11:00
|
|
|
|
duration = 500,
|
|
|
|
|
toOrthographic = false
|
|
|
|
|
): Promise<void> {
|
|
|
|
|
return new Promise((resolve) => {
|
|
|
|
|
const camera = this.camera
|
|
|
|
|
this._isCamMovingCallback(true, true)
|
|
|
|
|
const initialQuaternion = camera.quaternion.clone()
|
2024-03-08 18:52:31 +11:00
|
|
|
|
const initialTarget = this.target.clone()
|
2024-02-26 19:53:44 +11:00
|
|
|
|
const isVertical = isQuaternionVertical(targetQuaternion)
|
|
|
|
|
let tweenEnd = isVertical ? 0.99 : 1
|
2024-03-08 18:52:31 +11:00
|
|
|
|
const tempVec = new Vector3()
|
|
|
|
|
const initialDistance = initialTarget.distanceTo(camera.position.clone())
|
2024-02-26 19:53:44 +11:00
|
|
|
|
|
|
|
|
|
const cameraAtTime = (animationProgress: number /* 0 - 1 */) => {
|
|
|
|
|
const currentQ = tempQuaternion.slerpQuaternions(
|
|
|
|
|
initialQuaternion,
|
|
|
|
|
targetQuaternion,
|
|
|
|
|
animationProgress
|
|
|
|
|
)
|
2024-03-22 10:23:04 +11:00
|
|
|
|
const up = new Vector3(0, 0, 1).applyQuaternion(currentQ)
|
|
|
|
|
this.camera.up.copy(up)
|
2024-03-08 18:52:31 +11:00
|
|
|
|
const currentTarget = tempVec.lerpVectors(
|
|
|
|
|
initialTarget,
|
|
|
|
|
targetPosition,
|
|
|
|
|
animationProgress
|
|
|
|
|
)
|
2024-02-26 19:53:44 +11:00
|
|
|
|
if (this.camera instanceof PerspectiveCamera)
|
|
|
|
|
// changing the camera position back when it's orthographic doesn't do anything
|
|
|
|
|
// and it messes up animating back to perspective later
|
|
|
|
|
this.camera.position
|
|
|
|
|
.set(0, 0, 1)
|
|
|
|
|
.applyQuaternion(currentQ)
|
|
|
|
|
.multiplyScalar(initialDistance)
|
2024-03-08 18:52:31 +11:00
|
|
|
|
.add(currentTarget)
|
2024-02-26 19:53:44 +11:00
|
|
|
|
|
|
|
|
|
this.camera.up.set(0, 1, 0).applyQuaternion(currentQ).normalize()
|
|
|
|
|
this.camera.quaternion.copy(currentQ)
|
2024-03-08 18:52:31 +11:00
|
|
|
|
this.target.copy(currentTarget)
|
2024-02-26 19:53:44 +11:00
|
|
|
|
this.camera.updateProjectionMatrix()
|
|
|
|
|
this.update()
|
|
|
|
|
this.onCameraChange()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const onComplete = async () => {
|
|
|
|
|
if (isReducedMotion() && toOrthographic) {
|
2024-03-22 10:23:04 +11:00
|
|
|
|
cameraAtTime(0.9999)
|
2024-02-26 19:53:44 +11:00
|
|
|
|
this.useOrthographicCamera()
|
|
|
|
|
} else if (toOrthographic) {
|
|
|
|
|
await this.animateToOrthographic()
|
|
|
|
|
}
|
|
|
|
|
this.enableRotate = false
|
|
|
|
|
this._isCamMovingCallback(false, true)
|
|
|
|
|
resolve()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (isReducedMotion()) {
|
2024-09-09 18:17:45 -04:00
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
2024-02-26 19:53:44 +11:00
|
|
|
|
onComplete()
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
new TWEEN.Tween({ t: 0 })
|
|
|
|
|
.to({ t: tweenEnd }, duration)
|
|
|
|
|
.easing(TWEEN.Easing.Quadratic.InOut)
|
|
|
|
|
.onUpdate(({ t }) => cameraAtTime(t))
|
2024-09-09 18:17:45 -04:00
|
|
|
|
.onComplete(toSync(onComplete, reportRejection))
|
2024-02-26 19:53:44 +11:00
|
|
|
|
.start()
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
animateToOrthographic = () =>
|
|
|
|
|
new Promise((resolve) => {
|
2024-03-02 08:20:50 +11:00
|
|
|
|
if (this.syncDirection === 'engineToClient')
|
|
|
|
|
console.warn(
|
|
|
|
|
'animate To Orthographic not design to work with engineToClient syncDirection.'
|
|
|
|
|
)
|
2024-02-26 19:53:44 +11:00
|
|
|
|
this.isFovAnimationInProgress = true
|
|
|
|
|
let currentFov = this.lastPerspectiveFov
|
2024-09-19 15:39:32 -04:00
|
|
|
|
this.perspectiveFovBeforeOrtho = currentFov
|
2024-02-26 19:53:44 +11:00
|
|
|
|
|
2024-09-19 15:39:32 -04:00
|
|
|
|
const targetFov = ORTHOGRAPHIC_MAGIC_FOV
|
2024-02-26 19:53:44 +11:00
|
|
|
|
const fovAnimationStep = (currentFov - targetFov) / FRAMES_TO_ANIMATE_IN
|
|
|
|
|
let frameWaitOnFinish = 10
|
|
|
|
|
|
|
|
|
|
const animateFovChange = () => {
|
|
|
|
|
if (this.camera instanceof PerspectiveCamera) {
|
|
|
|
|
if (this.camera.fov > targetFov) {
|
|
|
|
|
// Decrease the FOV
|
|
|
|
|
currentFov = Math.max(currentFov - fovAnimationStep, targetFov)
|
|
|
|
|
this.camera.updateProjectionMatrix()
|
2024-09-09 18:17:45 -04:00
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
2024-02-26 19:53:44 +11:00
|
|
|
|
this.dollyZoom(currentFov)
|
|
|
|
|
requestAnimationFrame(animateFovChange) // Continue the animation
|
|
|
|
|
} else if (frameWaitOnFinish > 0) {
|
|
|
|
|
frameWaitOnFinish--
|
|
|
|
|
requestAnimationFrame(animateFovChange) // Continue the animation
|
|
|
|
|
} else {
|
|
|
|
|
// Once the target FOV is reached, switch to the orthographic camera
|
|
|
|
|
// Needs to wait a couple frames after the FOV animation is complete
|
|
|
|
|
this.useOrthographicCamera()
|
|
|
|
|
this.isFovAnimationInProgress = false
|
|
|
|
|
resolve(true)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
animateFovChange() // Start the animation
|
|
|
|
|
})
|
2024-03-22 10:23:04 +11:00
|
|
|
|
animateToPerspective = (targetCamUp = new Vector3(0, 0, 1)) =>
|
2024-02-26 19:53:44 +11:00
|
|
|
|
new Promise((resolve) => {
|
2024-03-22 10:23:04 +11:00
|
|
|
|
if (this.syncDirection === 'engineToClient') {
|
2024-03-02 08:20:50 +11:00
|
|
|
|
console.warn(
|
|
|
|
|
'animate To Perspective not design to work with engineToClient syncDirection.'
|
|
|
|
|
)
|
2024-03-22 10:23:04 +11:00
|
|
|
|
}
|
2024-02-26 19:53:44 +11:00
|
|
|
|
this.isFovAnimationInProgress = true
|
2024-09-19 15:39:32 -04:00
|
|
|
|
const targetFov = this.perspectiveFovBeforeOrtho // Target FOV for perspective
|
|
|
|
|
this.lastPerspectiveFov = ORTHOGRAPHIC_MAGIC_FOV
|
|
|
|
|
let currentFov = ORTHOGRAPHIC_MAGIC_FOV
|
2024-03-22 10:23:04 +11:00
|
|
|
|
const initialCameraUp = this.camera.up.clone()
|
2024-09-09 18:17:45 -04:00
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
2024-02-26 19:53:44 +11:00
|
|
|
|
this.usePerspectiveCamera()
|
2024-03-22 10:23:04 +11:00
|
|
|
|
const tempVec = new Vector3()
|
2024-02-26 19:53:44 +11:00
|
|
|
|
|
2024-03-22 10:23:04 +11:00
|
|
|
|
const cameraAtTime = (t: number) => {
|
|
|
|
|
currentFov =
|
|
|
|
|
this.lastPerspectiveFov + (targetFov - this.lastPerspectiveFov) * t
|
|
|
|
|
const currentUp = tempVec.lerpVectors(initialCameraUp, targetCamUp, t)
|
|
|
|
|
this.camera.up.copy(currentUp)
|
2024-09-09 18:17:45 -04:00
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
2024-03-22 10:23:04 +11:00
|
|
|
|
this.dollyZoom(currentFov)
|
2024-02-26 19:53:44 +11:00
|
|
|
|
}
|
2024-03-22 10:23:04 +11:00
|
|
|
|
|
|
|
|
|
const onComplete = () => {
|
|
|
|
|
this.isFovAnimationInProgress = false
|
|
|
|
|
resolve(true)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
new TWEEN.Tween({ t: 0 })
|
|
|
|
|
.to({ t: 1 }, isReducedMotion() ? 50 : FRAMES_TO_ANIMATE_IN * 16) // Assuming 60fps, hence 16ms per frame
|
|
|
|
|
.easing(TWEEN.Easing.Quadratic.InOut)
|
|
|
|
|
.onUpdate(({ t }) => cameraAtTime(t))
|
|
|
|
|
.onComplete(onComplete)
|
|
|
|
|
.start()
|
2024-02-26 19:53:44 +11:00
|
|
|
|
})
|
2024-06-18 16:08:41 +10:00
|
|
|
|
snapToPerspectiveBeforeHandingBackControlToEngine = async (
|
|
|
|
|
targetCamUp = new Vector3(0, 0, 1)
|
|
|
|
|
) => {
|
|
|
|
|
if (this.syncDirection === 'engineToClient') {
|
|
|
|
|
console.warn(
|
|
|
|
|
'animate To Perspective not design to work with engineToClient syncDirection.'
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
this.isFovAnimationInProgress = true
|
2024-09-19 15:39:32 -04:00
|
|
|
|
const targetFov = this.perspectiveFovBeforeOrtho // Target FOV for perspective
|
|
|
|
|
let currentFov = ORTHOGRAPHIC_MAGIC_FOV
|
2024-06-18 16:08:41 +10:00
|
|
|
|
const initialCameraUp = this.camera.up.clone()
|
2024-09-09 18:17:45 -04:00
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
2024-06-18 16:08:41 +10:00
|
|
|
|
this.usePerspectiveCamera()
|
|
|
|
|
const tempVec = new Vector3()
|
|
|
|
|
|
|
|
|
|
currentFov = this.lastPerspectiveFov + (targetFov - this.lastPerspectiveFov)
|
|
|
|
|
const currentUp = tempVec.lerpVectors(initialCameraUp, targetCamUp, 1)
|
|
|
|
|
this.camera.up.copy(currentUp)
|
|
|
|
|
await this.dollyZoom(currentFov, true)
|
|
|
|
|
|
|
|
|
|
this.isFovAnimationInProgress = false
|
|
|
|
|
}
|
2024-02-26 19:53:44 +11:00
|
|
|
|
|
2024-04-15 12:04:17 -04:00
|
|
|
|
get reactCameraProperties(): ReactCameraProperties {
|
|
|
|
|
return {
|
|
|
|
|
type: this.isPerspective ? 'perspective' : 'orthographic',
|
|
|
|
|
[this.isPerspective ? 'fov' : 'zoom']:
|
|
|
|
|
this.camera instanceof PerspectiveCamera
|
|
|
|
|
? this.camera.fov
|
|
|
|
|
: this.camera.zoom,
|
|
|
|
|
position: [
|
|
|
|
|
roundOff(this.camera.position.x, 2),
|
|
|
|
|
roundOff(this.camera.position.y, 2),
|
|
|
|
|
roundOff(this.camera.position.z, 2),
|
|
|
|
|
],
|
2024-06-05 14:43:12 +02:00
|
|
|
|
target: [
|
|
|
|
|
roundOff(this.target.x, 2),
|
|
|
|
|
roundOff(this.target.y, 2),
|
|
|
|
|
roundOff(this.target.z, 2),
|
|
|
|
|
],
|
2024-04-15 12:04:17 -04:00
|
|
|
|
quaternion: [
|
|
|
|
|
roundOff(this.camera.quaternion.x, 2),
|
|
|
|
|
roundOff(this.camera.quaternion.y, 2),
|
|
|
|
|
roundOff(this.camera.quaternion.z, 2),
|
|
|
|
|
roundOff(this.camera.quaternion.w, 2),
|
|
|
|
|
],
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-02-26 19:53:44 +11:00
|
|
|
|
reactCameraPropertiesCallback: (a: ReactCameraProperties) => void = () => {}
|
|
|
|
|
setReactCameraPropertiesCallback = (
|
|
|
|
|
cb: (a: ReactCameraProperties) => void
|
|
|
|
|
) => {
|
|
|
|
|
this.reactCameraPropertiesCallback = cb
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
deferReactUpdate = throttle((a: ReactCameraProperties) => {
|
|
|
|
|
this.reactCameraPropertiesCallback(a)
|
|
|
|
|
}, 200)
|
|
|
|
|
|
2024-03-04 08:14:37 +11:00
|
|
|
|
onCameraChange = (forceUpdate = false) => {
|
2024-02-26 19:53:44 +11:00
|
|
|
|
const distance = this.target.distanceTo(this.camera.position)
|
|
|
|
|
if (this.camera.far / 2.1 < distance || this.camera.far / 1.9 > distance) {
|
|
|
|
|
this.camera.far = distance * 2
|
|
|
|
|
this.camera.near = distance / 10
|
|
|
|
|
this.camera.updateProjectionMatrix()
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-04 08:14:37 +11:00
|
|
|
|
if (this.syncDirection === 'clientToEngine' || forceUpdate)
|
2024-03-22 16:55:30 +11:00
|
|
|
|
this.throttledUpdateEngineCamera({
|
2024-03-02 08:20:50 +11:00
|
|
|
|
quaternion: this.camera.quaternion,
|
|
|
|
|
position: this.camera.position,
|
|
|
|
|
zoom: this.camera.zoom,
|
|
|
|
|
isPerspective: this.isPerspective,
|
|
|
|
|
target: this.target,
|
|
|
|
|
})
|
2024-04-15 12:04:17 -04:00
|
|
|
|
this.deferReactUpdate(this.reactCameraProperties)
|
2024-02-26 19:53:44 +11:00
|
|
|
|
Object.values(this._camChangeCallbacks).forEach((cb) => cb())
|
|
|
|
|
}
|
2024-09-19 17:26:27 -04:00
|
|
|
|
getInteractionType = (event: MouseEvent) =>
|
2024-03-02 08:20:50 +11:00
|
|
|
|
_getInteractionType(
|
|
|
|
|
this.interactionGuards,
|
|
|
|
|
event,
|
|
|
|
|
this.enablePan,
|
|
|
|
|
this.enableRotate,
|
|
|
|
|
this.enableZoom
|
|
|
|
|
)
|
2024-02-26 19:53:44 +11:00
|
|
|
|
}
|
|
|
|
|
|
2024-03-02 08:20:50 +11:00
|
|
|
|
// Pure function helpers
|
|
|
|
|
|
2024-02-26 19:53:44 +11:00
|
|
|
|
function calculateNearFarFromFOV(fov: number) {
|
2024-04-22 20:14:06 +10:00
|
|
|
|
// const nearFarRatio = (fov - 3) / (45 - 3)
|
2024-02-26 19:53:44 +11:00
|
|
|
|
// const z_near = 0.1 + nearFarRatio * (5 - 0.1)
|
2024-04-22 20:14:06 +10:00
|
|
|
|
// const z_far = 1000 + nearFarRatio * (100000 - 1000)
|
2024-06-18 16:08:41 +10:00
|
|
|
|
return { z_near: 0.01, z_far: 1000 }
|
2024-02-26 19:53:44 +11:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function convertThreeCamValuesToEngineCam({
|
|
|
|
|
target,
|
|
|
|
|
position,
|
|
|
|
|
quaternion,
|
|
|
|
|
zoom,
|
|
|
|
|
isPerspective,
|
|
|
|
|
}: ThreeCamValues): {
|
|
|
|
|
center: Vector3
|
|
|
|
|
up: Vector3
|
|
|
|
|
vantage: Vector3
|
|
|
|
|
} {
|
|
|
|
|
const euler = new Euler().setFromQuaternion(quaternion, 'XYZ')
|
|
|
|
|
|
|
|
|
|
const upVector = new Vector3(0, 1, 0).applyEuler(euler).normalize()
|
|
|
|
|
if (isPerspective) {
|
|
|
|
|
return {
|
|
|
|
|
center: target,
|
|
|
|
|
up: upVector,
|
|
|
|
|
vantage: position,
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-07-26 13:43:43 +10:00
|
|
|
|
|
|
|
|
|
// re-implementing stuff here, though this is a bunch of Mike's code
|
|
|
|
|
// if we need to pull him in again, at least it will be familiar to him
|
|
|
|
|
// and it's all simple functions.
|
|
|
|
|
interface Coord3d {
|
|
|
|
|
x: number
|
|
|
|
|
y: number
|
|
|
|
|
z: number
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function buildLookAt(distance: number, center: Coord3d, eye: Coord3d) {
|
|
|
|
|
const eyeVector = normalized(sub(eye, center))
|
|
|
|
|
return { center: center, eye: add(center, mult(eyeVector, distance)) }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function mult(vecA: Coord3d, sc: number): Coord3d {
|
|
|
|
|
return { x: vecA.x * sc, y: vecA.y * sc, z: vecA.z * sc }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function add(vecA: Coord3d, vecB: Coord3d): Coord3d {
|
|
|
|
|
return { x: vecA.x + vecB.x, y: vecA.y + vecB.y, z: vecA.z + vecB.z }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function sub(vecA: Coord3d, vecB: Coord3d): Coord3d {
|
|
|
|
|
return { x: vecA.x - vecB.x, y: vecA.y - vecB.y, z: vecA.z - vecB.z }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function dot(vecA: Coord3d, vecB: Coord3d) {
|
|
|
|
|
return vecA.x * vecB.x + vecA.y * vecB.y + vecA.z * vecB.z
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function length(vecA: Coord3d) {
|
|
|
|
|
return Math.sqrt(dot(vecA, vecA))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function normalized(vecA: Coord3d) {
|
|
|
|
|
return mult(vecA, 1.0 / length(vecA))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const lookAt = buildLookAt(64 / zoom, target, position)
|
2024-02-26 19:53:44 +11:00
|
|
|
|
return {
|
2024-07-26 13:43:43 +10:00
|
|
|
|
center: new Vector3(lookAt.center.x, lookAt.center.y, lookAt.center.z),
|
2024-09-10 11:45:32 -05:00
|
|
|
|
up: new Vector3(upVector.x, upVector.y, upVector.z),
|
2024-07-26 13:43:43 +10:00
|
|
|
|
vantage: new Vector3(lookAt.eye.x, lookAt.eye.y, lookAt.eye.z),
|
2024-02-26 19:53:44 +11:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function _lookAt(position: Vector3, target: Vector3, up: Vector3): Quaternion {
|
|
|
|
|
// Direction from position to target, normalized.
|
|
|
|
|
let direction = new Vector3().subVectors(target, position).normalize()
|
|
|
|
|
|
|
|
|
|
// Calculate a new "effective" up vector that is orthogonal to the direction.
|
|
|
|
|
// This step ensures that the up vector does not affect the direction the camera is looking.
|
|
|
|
|
let right = new Vector3().crossVectors(direction, up).normalize()
|
|
|
|
|
let orthogonalUp = new Vector3().crossVectors(right, direction).normalize()
|
|
|
|
|
|
|
|
|
|
// Create a lookAt matrix using the position, and the recalculated orthogonal up vector.
|
|
|
|
|
let lookAtMatrix = new Matrix4()
|
|
|
|
|
lookAtMatrix.lookAt(position, target, orthogonalUp)
|
|
|
|
|
|
|
|
|
|
// Create a quaternion from the lookAt matrix.
|
|
|
|
|
let quaternion = new Quaternion().setFromRotationMatrix(lookAtMatrix)
|
|
|
|
|
|
|
|
|
|
return quaternion
|
|
|
|
|
}
|
2024-03-02 08:20:50 +11:00
|
|
|
|
|
|
|
|
|
function _getInteractionType(
|
|
|
|
|
interactionGuards: MouseGuard,
|
2024-09-19 17:26:27 -04:00
|
|
|
|
event: MouseEvent | WheelEvent,
|
2024-03-02 08:20:50 +11:00
|
|
|
|
enablePan: boolean,
|
|
|
|
|
enableRotate: boolean,
|
|
|
|
|
enableZoom: boolean
|
|
|
|
|
): interactionType | 'none' {
|
2024-09-19 17:26:27 -04:00
|
|
|
|
if (event instanceof WheelEvent) {
|
|
|
|
|
if (enableZoom && interactionGuards.zoom.scrollCallback(event))
|
|
|
|
|
return 'zoom'
|
|
|
|
|
} else {
|
|
|
|
|
if (enablePan && interactionGuards.pan.callback(event)) return 'pan'
|
|
|
|
|
if (enableRotate && interactionGuards.rotate.callback(event))
|
|
|
|
|
return 'rotate'
|
|
|
|
|
if (enableZoom && interactionGuards.zoom.dragCallback(event)) return 'zoom'
|
|
|
|
|
}
|
|
|
|
|
return 'none'
|
2024-03-02 08:20:50 +11:00
|
|
|
|
}
|
2024-04-22 20:14:06 +10:00
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Tells the engine to fire it's animation waits for it to finish and then requests camera settings
|
|
|
|
|
* to ensure the client-side camera is synchronized with the engine's camera state.
|
|
|
|
|
*
|
|
|
|
|
* @param engineCommandManager Our websocket singleton
|
|
|
|
|
* @param entityId - The ID of face or sketchPlane.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
export async function letEngineAnimateAndSyncCamAfter(
|
|
|
|
|
engineCommandManager: EngineCommandManager,
|
|
|
|
|
entityId: string
|
|
|
|
|
) {
|
|
|
|
|
await engineCommandManager.sendSceneCommand({
|
|
|
|
|
type: 'modeling_cmd_req',
|
|
|
|
|
cmd_id: uuidv4(),
|
|
|
|
|
cmd: {
|
|
|
|
|
type: 'enable_sketch_mode',
|
|
|
|
|
adjust_camera: true,
|
|
|
|
|
animated: !isReducedMotion(),
|
2024-07-26 13:43:43 +10:00
|
|
|
|
ortho: true,
|
2024-04-22 20:14:06 +10:00
|
|
|
|
entity_id: entityId,
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
// wait 600ms (animation takes 500, + 100 for safety)
|
|
|
|
|
await new Promise((resolve) =>
|
|
|
|
|
setTimeout(resolve, isReducedMotion() ? 100 : 600)
|
|
|
|
|
)
|
|
|
|
|
await engineCommandManager.sendSceneCommand({
|
|
|
|
|
// CameraControls subscribes to default_camera_get_settings response events
|
|
|
|
|
// firing this at connection ensure the camera's are synced initially
|
|
|
|
|
type: 'modeling_cmd_req',
|
|
|
|
|
cmd_id: uuidv4(),
|
|
|
|
|
cmd: {
|
|
|
|
|
type: 'default_camera_get_settings',
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
}
|