solve a couple of scene scale bugs (#1496)

* solve a couple of scene scale bugs

* Some cam fixes (#1520)

* rotate and zoom basics working

* intergrate mouse guards, and add pan

* implement orthographic camera again

* implement switch to perspective camera again

* migrate dollyzoom

* make pan robust for differnt FOV and orthographic cam

* tween to quaternion and default plane selection working with quirks

* fix pan

It the up and right was derived from the camera's up, which is a static [0,0,1] not the camera's current cameras real up, which aligns itself as best to [0,0,1] but is not that especially when looking straight up or down, and the pan felt very awkward in these vertical look sintuations

* fix raycastRing to use new camera

* fix tween to quaternion for camera lock situations

And get all playwright tests passing

* fix up CamToggle, even thought this component is not setup properly to use react properties from our scene class

* add animation to cameras back in

* first big clean up of sceneInfra

* move more cam stuff out of sceneInfra

* clean up mouse guard logic

* clean up camera change callbacks

* fix some sitations where animation to xy doesn't work great

* needs to take the target into consideration

* last bits of clean up

* more clean up

* make vitest happ

* fix up remaining interaction guards

* make scrolling less sensative for trackpads

* remove debug cube

* fix snapshot tests
This commit is contained in:
Kurt Hutten
2024-02-26 19:53:44 +11:00
committed by GitHub
parent f0c44d11b3
commit 0d6618b60a
15 changed files with 1090 additions and 1017 deletions

View File

@ -1,12 +1,10 @@
import {
AmbientLight,
Color,
Euler,
GridHelper,
LineBasicMaterial,
OrthographicCamera,
PerspectiveCamera,
Quaternion,
Scene,
Vector3,
WebGLRenderer,
@ -20,32 +18,26 @@ import {
Intersection,
Object3D,
Object3DEventMap,
BoxGeometry,
} from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
import { EngineCommand, engineCommandManager } from 'lang/std/engineConnection'
import { v4 as uuidv4 } from 'uuid'
import { isReducedMotion, roundOff, throttle } from 'lib/utils'
import { compareVec2Epsilon2 } from 'lang/std/sketch'
import { useModelingContext } from 'hooks/useModelingContext'
import { deg2Rad } from 'lib/utils2d'
import * as TWEEN from '@tweenjs/tween.js'
import { MouseGuard, cameraMouseDragGuards } from 'lib/cameraControls'
import { SourceRange } from 'lang/wasm'
import { Axis } from 'lib/selections'
import { BaseUnit, SETTINGS_PERSIST_KEY } from 'machines/settingsMachine'
import { CameraControls } from './CameraControls'
type SendType = ReturnType<typeof useModelingContext>['send']
// 63.5 is definitely a bit of a magic number, play with it until it looked right
// if it were 64, that would feel like it's something in the engine where a random
// power of 2 is used, but it's the 0.5 seems to make things look much more correct
const ZOOM_MAGIC_NUMBER = 63.5
const FRAMES_TO_ANIMATE_IN = 30
const ORTHOGRAPHIC_CAMERA_SIZE = 20
export const ZOOM_MAGIC_NUMBER = 63.5
export const INTERSECTION_PLANE_LAYER = 1
export const SKETCH_LAYER = 2
const DEBUG_SHOW_INTERSECTION_PLANE = false
export const DEBUG_SHOW_INTERSECTION_PLANE = false
export const DEBUG_SHOW_BOTH_SCENES = false
export const RAYCASTABLE_PLANE = 'raycastable-plane'
@ -57,100 +49,6 @@ export const AXIS_GROUP = 'axisGroup'
export const SKETCH_GROUP_SEGMENTS = 'sketch-group-segments'
export const ARROWHEAD = 'arrowhead'
const tempQuaternion = new Quaternion() // just used for maths
interface ThreeCamValues {
position: Vector3
quaternion: Quaternion
zoom: number
isPerspective: boolean
target: Vector3
}
const lastCmdDelay = 50
let lastCmd: EngineCommand | null = null
let lastCmdTime: number = Date.now()
let lastCmdTimeoutId: number | null = null
const sendLastReliableChannel = () => {
if (lastCmd && Date.now() - lastCmdTime >= lastCmdDelay) {
engineCommandManager.sendSceneCommand(lastCmd, true)
lastCmdTime = Date.now()
}
}
const throttledUpdateEngineCamera = throttle((threeValues: ThreeCamValues) => {
const cmd: EngineCommand = {
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'default_camera_look_at',
...convertThreeCamValuesToEngineCam(threeValues),
},
}
engineCommandManager.sendSceneCommand(cmd)
lastCmd = cmd
lastCmdTime = Date.now()
if (lastCmdTimeoutId !== null) {
clearTimeout(lastCmdTimeoutId)
}
lastCmdTimeoutId = setTimeout(
sendLastReliableChannel,
lastCmdDelay
) as any as number
}, 1000 / 30)
let lastPerspectiveCmd: EngineCommand | null = null
let lastPerspectiveCmdTime: number = Date.now()
let lastPerspectiveCmdTimeoutId: number | null = null
const sendLastPerspectiveReliableChannel = () => {
if (
lastPerspectiveCmd &&
Date.now() - lastPerspectiveCmdTime >= lastCmdDelay
) {
engineCommandManager.sendSceneCommand(lastPerspectiveCmd, true)
lastPerspectiveCmdTime = Date.now()
}
}
const throttledUpdateEngineFov = throttle(
(vals: {
position: Vector3
quaternion: Quaternion
zoom: number
fov: number
target: Vector3
}) => {
const cmd: EngineCommand = {
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'default_camera_perspective_settings',
...convertThreeCamValuesToEngineCam({
...vals,
isPerspective: true,
}),
fov_y: vals.fov,
...calculateNearFarFromFOV(vals.fov),
},
}
engineCommandManager.sendSceneCommand(cmd)
lastPerspectiveCmd = cmd
lastPerspectiveCmdTime = Date.now()
if (lastPerspectiveCmdTimeoutId !== null) {
clearTimeout(lastPerspectiveCmdTimeoutId)
}
lastPerspectiveCmdTimeoutId = setTimeout(
sendLastPerspectiveReliableChannel,
lastCmdDelay
) as any as number
},
1000 / 15
)
interface BaseCallbackArgs2 {
object: any
event: any
@ -178,34 +76,18 @@ interface onMoveCallbackArgs {
intersection: Intersection<Object3D<Object3DEventMap>>
}
export type ReactCameraProperties =
| {
type: 'perspective'
fov?: number
position: [number, number, number]
quaternion: [number, number, number, number]
}
| {
type: 'orthographic'
zoom?: number
position: [number, number, number]
quaternion: [number, number, number, number]
}
// This singleton class is responsible for all of the under the hood setup for the client side scene.
// That is the cameras and switching between them, raycasters for click mouse events and their abstractions (onClick etc), setting up controls.
// Anything that added the the scene for the user to interact with is probably in SceneEntities.ts
class SceneInfra {
static instance: SceneInfra
scene: Scene
camera: PerspectiveCamera | OrthographicCamera
renderer: WebGLRenderer
controls: OrbitControls
camControls: CameraControls
isPerspective = true
fov = 45
fovBeforeAnimate = 45
isFovAnimationInProgress = false
interactionGuards: MouseGuard = cameraMouseDragGuards.KittyCAD
onDragCallback: (arg: OnDragCallbackArgs) => void = () => {}
onMoveCallback: (arg: onMoveCallbackArgs) => void = () => {}
onClickCallback: (arg?: OnClickCallbackArgs) => void = () => {}
@ -256,55 +138,18 @@ class SceneInfra {
selectedObject: null | any = null
mouseDownVector: null | Vector2 = null
// reacts hooks into some of this singleton's properties
reactCameraProperties: ReactCameraProperties = {
type: 'perspective',
fov: 12,
position: [0, 0, 0],
quaternion: [0, 0, 0, 1],
}
reactCameraPropertiesCallback: (a: ReactCameraProperties) => void = () => {}
setReactCameraPropertiesCallback = (
cb: (a: ReactCameraProperties) => void
) => {
this.reactCameraPropertiesCallback = cb
}
setCam = (camProps: ReactCameraProperties) => {
if (
camProps.type === 'perspective' &&
this.camera instanceof OrthographicCamera
) {
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()
this.controls.update()
}
constructor() {
// SCENE
this.scene = new Scene()
this.scene.background = new Color(0x000000)
this.scene.background = null
// RENDERER
this.renderer = new WebGLRenderer({ antialias: true, alpha: true }) // Enable transparency
this.renderer.setSize(window.innerWidth, window.innerHeight)
this.renderer.setClearColor(0x000000, 0) // Set clear color to black with 0 alpha (fully transparent)
window.addEventListener('resize', this.onWindowResize)
// CAMERA
const camHeightDistanceRatio = 0.5
const baseUnit: BaseUnit =
@ -315,25 +160,19 @@ class SceneInfra {
const ang = Math.atan(camHeightDistanceRatio)
const x = Math.cos(ang) * length
const y = Math.sin(ang) * length
this.camera = this.createPerspectiveCamera()
this.camera.position.set(0, -x, y)
if (DEBUG_SHOW_INTERSECTION_PLANE)
this.camera.layers.enable(INTERSECTION_PLANE_LAYER)
// RENDERER
this.renderer = new WebGLRenderer({ antialias: true, alpha: true }) // Enable transparency
this.renderer.setSize(window.innerWidth, window.innerHeight)
this.renderer.setClearColor(0x000000, 0) // Set clear color to black with 0 alpha (fully transparent)
window.addEventListener('resize', this.onWindowResize)
this.camControls = new CameraControls(false, this.renderer.domElement)
this.camControls.subscribeToCamChange(() => this.onCameraChange())
this.camControls.camera.layers.enable(SKETCH_LAYER)
this.camControls.camera.position.set(0, -x, y)
if (DEBUG_SHOW_INTERSECTION_PLANE)
this.camControls.camera.layers.enable(INTERSECTION_PLANE_LAYER)
// RAYCASTERS
this.raycaster.layers.enable(SKETCH_LAYER)
this.raycaster.layers.disable(0)
this.planeRaycaster.layers.enable(INTERSECTION_PLANE_LAYER)
// CONTROLS
this.controls = this.setupOrbitControls()
// GRID
const size = 100
const divisions = 10
@ -353,415 +192,40 @@ class SceneInfra {
SceneInfra.instance = this
}
private _isCamMovingCallback: (isMoving: boolean, isTween: boolean) => void =
() => {}
setIsCamMovingCallback(cb: (isMoving: boolean, isTween: boolean) => void) {
this._isCamMovingCallback = cb
}
private _onCamChange: () => void = () => {}
setOnCamChange(cb: () => void) {
this._onCamChange = cb
}
setInteractionGuards = (guard: MouseGuard) => {
this.interactionGuards = guard
// setMouseGuards is oun patch-package patch to orbit controls
// see patches/three+0.160.0.patch
;(this.controls as any).setMouseGuards(guard)
}
private createPerspectiveCamera = () => {
const { z_near, z_far } = calculateNearFarFromFOV(this.fov)
this.camera = new PerspectiveCamera(
this.fov,
window.innerWidth / window.innerHeight,
z_near,
z_far
)
this.camera.up.set(0, 0, 1)
this.camera.layers.enable(SKETCH_LAYER)
if (DEBUG_SHOW_INTERSECTION_PLANE)
this.camera.layers.enable(INTERSECTION_PLANE_LAYER)
return this.camera
}
setupOrbitControls = (target?: [number, number, number]): OrbitControls => {
if (this.controls) this.controls.dispose()
this.controls = new OrbitControls(this.camera, this.renderer.domElement)
if (target) {
// if we're swapping from perspective to orthographic,
// we'll need to recreate the orbit controls
// and most likely want the target to be the same
this.controls.target.set(...target)
}
this.controls.update()
this.controls.addEventListener('change', this.onCameraChange)
// debounce is needed because the start and end events are fired too often for zoom on scroll
let debounceTimer = 0
const handleStart = () => {
if (debounceTimer) clearTimeout(debounceTimer)
this._isCamMovingCallback(true, false)
}
const handleEnd = () => {
debounceTimer = setTimeout(() => {
this._isCamMovingCallback(false, false)
}, 400) as any as number
}
this.controls.addEventListener('start', handleStart)
this.controls.addEventListener('end', handleEnd)
// setMouseGuards is oun patch-package patch to orbit controls
// see patches/three+0.160.0.patch
;(this.controls as any).setMouseGuards(this.interactionGuards)
return this.controls
}
onStreamStart = () => this.onCameraChange()
deferReactUpdate = throttle((a: ReactCameraProperties) => {
this.reactCameraPropertiesCallback(a)
}, 200)
onCameraChange = () => {
const scale = getSceneScale(this.camera, this.controls.target)
const scale = getSceneScale(
this.camControls.camera,
this.camControls.target
)
const planesGroup = this.scene.getObjectByName(DEFAULT_PLANES)
const axisGroup = this.scene
.getObjectByName(AXIS_GROUP)
?.getObjectByName('gridHelper')
planesGroup && planesGroup.scale.set(scale, scale, scale)
axisGroup?.name === 'gridHelper' && axisGroup.scale.set(scale, scale, scale)
throttledUpdateEngineCamera({
quaternion: this.camera.quaternion,
position: this.camera.position,
zoom: this.camera.zoom,
isPerspective: this.isPerspective,
target: this.controls.target,
})
this.deferReactUpdate({
type:
this.camera instanceof PerspectiveCamera
? 'perspective'
: 'orthographic',
[this.camera instanceof PerspectiveCamera ? '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),
],
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),
],
})
this._onCamChange()
}
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()
this.renderer.setSize(window.innerWidth, window.innerHeight)
}
animate = () => {
requestAnimationFrame(this.animate)
TWEEN.update() // This will update all tweens during the animation loop
if (!this.isFovAnimationInProgress)
this.renderer.render(this.scene, this.camera)
}
async tweenCameraToQuaternion(
targetQuaternion: Quaternion,
duration = 500,
toOrthographic = true
): Promise<void> {
const isVertical = isQuaternionVertical(targetQuaternion)
let _duration = duration
if (isVertical) {
_duration = duration * 0.6
await this._tweenCameraToQuaternion(new Quaternion(), _duration, false)
if (!this.isFovAnimationInProgress) {
// console.log('animation frame', this.cameraControls.camera)
this.camControls.update()
this.renderer.render(this.scene, this.camControls.camera)
}
await this._tweenCameraToQuaternion(
targetQuaternion,
_duration,
toOrthographic
)
}
_tweenCameraToQuaternion(
targetQuaternion: Quaternion,
duration = 500,
toOrthographic = false
): Promise<void> {
return new Promise((resolve) => {
const camera = this.camera
this._isCamMovingCallback(true, true)
const initialQuaternion = camera.quaternion.clone()
const isVertical = isQuaternionVertical(targetQuaternion)
let tweenEnd = isVertical ? 0.99 : 1
const controlsTarget = this.controls.target.clone()
const initialDistance = controlsTarget.distanceTo(camera.position.clone())
const cameraAtTime = (animationProgress: number /* 0 - 1 */) => {
const currentQ = tempQuaternion.slerpQuaternions(
initialQuaternion,
targetQuaternion,
animationProgress
)
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)
.add(controlsTarget)
this.camera.up.set(0, 1, 0).applyQuaternion(currentQ).normalize()
this.camera.quaternion.copy(currentQ)
this.controls.target.copy(controlsTarget)
this.controls.update()
this.camera.updateProjectionMatrix()
}
const onComplete = async () => {
if (isReducedMotion() && toOrthographic) {
cameraAtTime(0.99)
this.useOrthographicCamera()
} else if (toOrthographic) {
await this.animateToOrthographic()
}
if (isVertical) cameraAtTime(1)
this.camera.up.set(0, 0, 1)
this.controls.enableRotate = false
this._isCamMovingCallback(false, true)
resolve()
}
if (isReducedMotion()) {
onComplete()
return
}
new TWEEN.Tween({ t: 0 })
.to({ t: tweenEnd }, duration)
.easing(TWEEN.Easing.Quadratic.InOut)
.onUpdate(({ t }) => cameraAtTime(t))
.onComplete(onComplete)
.start()
})
}
animateToOrthographic = () =>
new Promise((resolve) => {
this.isFovAnimationInProgress = true
let currentFov = this.fov
this.fovBeforeAnimate = this.fov
const targetFov = 4
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()
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
})
animateToPerspective = () =>
new Promise((resolve) => {
this.isFovAnimationInProgress = true
// Immediately set the camera to perspective with a very low FOV
this.fov = 4
let currentFov = 4
this.camera.updateProjectionMatrix()
const targetFov = this.fovBeforeAnimate // Target FOV for perspective
const fovAnimationStep = (targetFov - currentFov) / FRAMES_TO_ANIMATE_IN
this.usePerspectiveCamera()
const animateFovChange = () => {
if (this.camera instanceof OrthographicCamera) return
if (this.camera.fov < targetFov) {
// Increase the FOV
currentFov = Math.min(currentFov + fovAnimationStep, targetFov)
// this.camera.fov = currentFov
this.camera.updateProjectionMatrix()
this.dollyZoom(currentFov)
requestAnimationFrame(animateFovChange) // Continue the animation
} else {
// Set the flag to false as the FOV animation is complete
this.isFovAnimationInProgress = false
resolve(true)
}
}
animateFovChange() // Start the animation
})
dispose = () => {
// Dispose of scene resources, renderer, and controls
this.renderer.dispose()
window.removeEventListener('resize', this.onWindowResize)
// Dispose of any other resources like geometries, materials, textures
}
useOrthographicCamera = () => {
this.isPerspective = false
const { x: px, y: py, z: pz } = this.camera.position
const { x: qx, y: qy, z: qz, w: qw } = this.camera.quaternion
const { x: tx, y: ty, z: tz } = this.controls.target
const aspect = window.innerWidth / window.innerHeight
const { z_near, z_far } = calculateNearFarFromFOV(this.fov)
this.camera = new OrthographicCamera(
-ORTHOGRAPHIC_CAMERA_SIZE * aspect,
ORTHOGRAPHIC_CAMERA_SIZE * aspect,
ORTHOGRAPHIC_CAMERA_SIZE,
-ORTHOGRAPHIC_CAMERA_SIZE,
z_near,
z_far
)
this.camera.up.set(0, 0, 1)
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(new Vector3(tx, ty, tz))
const fovFactor = 45 / this.fov
this.camera.zoom = (ZOOM_MAGIC_NUMBER * fovFactor * 0.8) / distance
this.setupOrbitControls([tx, ty, tz])
this.camera.quaternion.set(qx, qy, qz, qw)
this.camera.updateProjectionMatrix()
this.controls.update()
engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'default_camera_set_orthographic',
},
})
}
usePerspectiveCamera = () => {
this.isPerspective = true
const { x: px, y: py, z: pz } = this.camera.position
const { x: qx, y: qy, z: qz, w: qw } = this.camera.quaternion
const { x: tx, y: ty, z: tz } = this.controls.target
const zoom = this.camera.zoom
this.camera = this.createPerspectiveCamera()
this.camera.position.set(px, py, pz)
this.camera.quaternion.set(qx, qy, qz, qw)
const zoomFudgeFactor = 2280
const distance = zoomFudgeFactor / (zoom * this.fov)
const direction = new Vector3().subVectors(
this.camera.position,
this.controls.target
)
direction.normalize()
this.camera.position
.copy(this.controls.target)
.addScaledVector(direction, distance)
this.setupOrbitControls([tx, ty, tz])
engineCommandManager.sendSceneCommand({
type: 'modeling_cmd_req',
cmd_id: uuidv4(),
cmd: {
type: 'default_camera_set_perspective',
parameters: {
fov_y: this.camera.fov,
...calculateNearFarFromFOV(this.fov),
},
},
})
this.onCameraChange()
return this.camera
}
dollyZoom = (newFov: number) => {
if (!(this.camera instanceof PerspectiveCamera)) {
console.warn('Dolly zoom is only applicable to perspective cameras.')
return
}
this.fov = newFov
// Calculate the direction vector from the camera towards the controls target
const direction = new Vector3()
.subVectors(this.controls.target, this.camera.position)
.normalize()
// Calculate the distance to the controls target before changing the FOV
const distanceBefore = this.camera.position.distanceTo(this.controls.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) => {
/* *
/|
/ |
/ |
/ |
/ | 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.controls.target
.clone()
.add(direction.multiplyScalar(-distanceAfter))
this.camera.position.copy(newPosition)
const { z_near, z_far } = calculateNearFarFromFOV(this.fov)
this.camera.near = z_near
this.camera.far = z_far
throttledUpdateEngineFov({
fov: newFov,
position: newPosition,
quaternion: this.camera.quaternion,
zoom: this.camera.zoom,
target: this.controls.target,
})
}
getPlaneIntersectPoint = (): {
intersection2d?: Vector2
intersectPoint: Vector3
@ -769,7 +233,7 @@ class SceneInfra {
} | null => {
this.planeRaycaster.setFromCamera(
this.currentMouseVector,
sceneInfra.camera
sceneInfra.camControls.camera
)
const planeIntersects = this.planeRaycaster.intersectObjects(
this.scene.children,
@ -907,7 +371,7 @@ class SceneInfra {
}
// Check the center point
this.raycaster.setFromCamera(mouseDownVector, this.camera)
this.raycaster.setFromCamera(mouseDownVector, this.camControls.camera)
updateClosestIntersection(
this.raycaster.intersectObjects(this.scene.children, true)
)
@ -922,7 +386,7 @@ class SceneInfra {
mouseDownVector.x + offsetX,
mouseDownVector.y - offsetY
)
this.raycaster.setFromCamera(ringVector, this.camera)
this.raycaster.setFromCamera(ringVector, this.camControls.camera)
updateClosestIntersection(
this.raycaster.intersectObjects(this.scene.children, true)
)
@ -1015,7 +479,10 @@ class SceneInfra {
}
})
planesGroup.layers.enable(SKETCH_LAYER)
const sceneScale = getSceneScale(this.camera, this.controls.target)
const sceneScale = getSceneScale(
this.camControls.camera,
this.camControls.target
)
planesGroup.scale.set(sceneScale, sceneScale, sceneScale)
this.scene.add(planesGroup)
}
@ -1050,52 +517,6 @@ class SceneInfra {
export const sceneInfra = new SceneInfra()
function convertThreeCamValuesToEngineCam({
target,
position,
quaternion,
zoom,
isPerspective,
}: ThreeCamValues): {
center: Vector3
up: Vector3
vantage: Vector3
} {
// Something to consider is that the orbit controls have a target,
// we're kind of deriving the target/lookAtVector here when it might not be needed
// leaving for now since it's working but maybe revisit later
const euler = new Euler().setFromQuaternion(quaternion, 'XYZ')
const lookAtVector = new Vector3(0, 0, -1)
.applyEuler(euler)
.normalize()
.add(position)
const upVector = new Vector3(0, 1, 0).applyEuler(euler).normalize()
if (isPerspective) {
return {
center: target,
up: upVector,
vantage: position,
}
}
const zoomFactor = -ZOOM_MAGIC_NUMBER / zoom
const direction = lookAtVector.clone().sub(position).normalize()
const newVantage = position.clone().add(direction.multiplyScalar(zoomFactor))
return {
center: lookAtVector,
up: upVector,
vantage: newVantage,
}
}
function calculateNearFarFromFOV(fov: number) {
const nearFarRatio = (fov - 3) / (45 - 3)
// const z_near = 0.1 + nearFarRatio * (5 - 0.1)
const z_far = 1000 + nearFarRatio * (100000 - 1000)
return { z_near: 0.1, z_far }
}
export function getSceneScale(
camera: PerspectiveCamera | OrthographicCamera,
target: Vector3
@ -1131,12 +552,6 @@ function baseUnitTomm(baseUnit: BaseUnit) {
}
}
export function isQuaternionVertical(q: Quaternion) {
const v = new Vector3(0, 0, 1).applyQuaternion(q)
// no x or y components means it's vertical
return compareVec2Epsilon2([v.x, v.y], [0, 0])
}
export type DefaultPlane =
| 'xy-default-plane'
| 'xz-default-plane'