Fix camera floorplan navigation sync (#387)
This commit is contained in:
@@ -32,11 +32,6 @@ const tempSize = new Vector3()
|
|||||||
const tempTarget = new Vector3()
|
const tempTarget = new Vector3()
|
||||||
const syncTarget = new Vector3()
|
const syncTarget = new Vector3()
|
||||||
const syncSpherical = new Spherical()
|
const syncSpherical = new Spherical()
|
||||||
const keyboardPanPosition = new Vector3()
|
|
||||||
const keyboardPanTarget = new Vector3()
|
|
||||||
const keyboardPanScreenRight = new Vector3()
|
|
||||||
const keyboardPanScreenUp = new Vector3()
|
|
||||||
const keyboardPanDelta = new Vector3()
|
|
||||||
const keyboardPanSpherical = new Spherical()
|
const keyboardPanSpherical = new Spherical()
|
||||||
const DEFAULT_MAX_POLAR_ANGLE = Math.PI / 2 - 0.1
|
const DEFAULT_MAX_POLAR_ANGLE = Math.PI / 2 - 0.1
|
||||||
const DEBUG_MAX_POLAR_ANGLE = Math.PI - 0.05
|
const DEBUG_MAX_POLAR_ANGLE = Math.PI - 0.05
|
||||||
@@ -57,6 +52,13 @@ type NavigationCameraPoseSnapshot = {
|
|||||||
azimuth: number
|
azimuth: number
|
||||||
viewWidth: number
|
viewWidth: number
|
||||||
}
|
}
|
||||||
|
type PendingNavigationCameraPoseSnapshot = NavigationCameraPoseSnapshot & {
|
||||||
|
publishOnComplete: boolean
|
||||||
|
}
|
||||||
|
type CameraViewWidthUpdate =
|
||||||
|
| { type: 'distance'; distance: number; viewWidth: number }
|
||||||
|
| { type: 'zoom'; viewWidth: number; zoom: number }
|
||||||
|
| { type: 'none'; viewWidth: number }
|
||||||
|
|
||||||
function writeVectorTuple(tuple: [number, number, number], vector: Vector3) {
|
function writeVectorTuple(tuple: [number, number, number], vector: Vector3) {
|
||||||
tuple[0] = vector.x
|
tuple[0] = vector.x
|
||||||
@@ -168,6 +170,34 @@ function getAngleDeltaRadians(a: number, b: number) {
|
|||||||
return Math.atan2(Math.sin(a - b), Math.cos(a - b))
|
return Math.atan2(Math.sin(a - b), Math.cos(a - b))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function nearestEquivalentRadians(angle: number, reference: number) {
|
||||||
|
return reference + getAngleDeltaRadians(angle, reference)
|
||||||
|
}
|
||||||
|
|
||||||
|
function clampFinite(value: number, min: number, max: number) {
|
||||||
|
const resolvedMin = Number.isFinite(min) ? min : Number.NEGATIVE_INFINITY
|
||||||
|
const resolvedMax = Number.isFinite(max) ? max : Number.POSITIVE_INFINITY
|
||||||
|
return Math.min(Math.max(value, resolvedMin), resolvedMax)
|
||||||
|
}
|
||||||
|
|
||||||
|
function clampCameraControlDistance(control: CameraControlsImpl, distance: number) {
|
||||||
|
const bounds = control as { minDistance?: number; maxDistance?: number }
|
||||||
|
return clampFinite(
|
||||||
|
distance,
|
||||||
|
bounds.minDistance ?? Number.NEGATIVE_INFINITY,
|
||||||
|
bounds.maxDistance ?? Number.POSITIVE_INFINITY,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function clampCameraControlZoom(control: CameraControlsImpl, zoom: number) {
|
||||||
|
const bounds = control as { minZoom?: number; maxZoom?: number }
|
||||||
|
return clampFinite(
|
||||||
|
zoom,
|
||||||
|
bounds.minZoom ?? Number.NEGATIVE_INFINITY,
|
||||||
|
bounds.maxZoom ?? Number.POSITIVE_INFINITY,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
function isCameraAtNavigationPose(
|
function isCameraAtNavigationPose(
|
||||||
pose: NavigationCameraPoseSnapshot,
|
pose: NavigationCameraPoseSnapshot,
|
||||||
target: Vector3,
|
target: Vector3,
|
||||||
@@ -206,21 +236,45 @@ function getCameraZoomForViewWidth(camera: Camera, viewWidth: number) {
|
|||||||
return viewWidth > 0 ? Math.max(0.001, (camera.right - camera.left) / viewWidth) : null
|
return viewWidth > 0 ? Math.max(0.001, (camera.right - camera.left) / viewWidth) : null
|
||||||
}
|
}
|
||||||
|
|
||||||
function applyCameraViewWidth(
|
function resolveCameraViewWidthUpdate(
|
||||||
control: CameraControlsImpl,
|
control: CameraControlsImpl,
|
||||||
camera: Camera,
|
camera: Camera,
|
||||||
viewWidth: number,
|
viewWidth: number,
|
||||||
size: CameraViewportSize,
|
size: CameraViewportSize,
|
||||||
) {
|
): CameraViewWidthUpdate {
|
||||||
const nextDistance = getCameraDistanceForViewWidth(camera, viewWidth, size)
|
const nextDistance = getCameraDistanceForViewWidth(camera, viewWidth, size)
|
||||||
if (nextDistance !== null) {
|
if (nextDistance !== null) {
|
||||||
control.dollyTo(nextDistance, true)
|
const appliedDistance = clampCameraControlDistance(control, nextDistance)
|
||||||
return
|
return {
|
||||||
|
type: 'distance',
|
||||||
|
distance: appliedDistance,
|
||||||
|
viewWidth: getCameraViewWidth(camera, appliedDistance, size),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const nextZoom = getCameraZoomForViewWidth(camera, viewWidth)
|
const nextZoom = getCameraZoomForViewWidth(camera, viewWidth)
|
||||||
if (nextZoom !== null) {
|
if (nextZoom !== null) {
|
||||||
control.zoomTo(nextZoom, true)
|
const appliedZoom = clampCameraControlZoom(control, nextZoom)
|
||||||
|
if (isOrthographicCamera(camera)) {
|
||||||
|
return {
|
||||||
|
type: 'zoom',
|
||||||
|
zoom: appliedZoom,
|
||||||
|
viewWidth: Math.max(0.001, (camera.right - camera.left) / Math.max(appliedZoom, 0.001)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { type: 'none', viewWidth }
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyCameraViewWidth(control: CameraControlsImpl, update: CameraViewWidthUpdate) {
|
||||||
|
if (update.type === 'distance') {
|
||||||
|
control.dollyTo(update.distance, true)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (update.type === 'zoom') {
|
||||||
|
control.zoomTo(update.zoom, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -308,10 +362,13 @@ export const CustomCameraControls = () => {
|
|||||||
const currentLevelId = selection.levelId
|
const currentLevelId = selection.levelId
|
||||||
const firstLoad = useRef(true)
|
const firstLoad = useRef(true)
|
||||||
const lastPublishedNavigationSync = useRef<NavigationCameraPoseSnapshot | null>(null)
|
const lastPublishedNavigationSync = useRef<NavigationCameraPoseSnapshot | null>(null)
|
||||||
const pendingFloorplanNavigationPose = useRef<NavigationCameraPoseSnapshot | null>(null)
|
const pendingFloorplanNavigationPose = useRef<PendingNavigationCameraPoseSnapshot | null>(null)
|
||||||
const lastApplied2dNavigationRevision = useRef(0)
|
const lastApplied2dNavigationRevision = useRef(0)
|
||||||
const maxPolarAngle =
|
const maxPolarAngle =
|
||||||
!isPreviewMode && allowUndergroundCamera ? DEBUG_MAX_POLAR_ANGLE : DEFAULT_MAX_POLAR_ANGLE
|
!isPreviewMode && allowUndergroundCamera ? DEBUG_MAX_POLAR_ANGLE : DEFAULT_MAX_POLAR_ANGLE
|
||||||
|
const clearPendingFloorplanNavigationPose = useCallback(() => {
|
||||||
|
pendingFloorplanNavigationPose.current = null
|
||||||
|
}, [])
|
||||||
|
|
||||||
const camera = useThree((state) => state.camera)
|
const camera = useThree((state) => state.camera)
|
||||||
const gl = useThree((state) => state.gl)
|
const gl = useThree((state) => state.gl)
|
||||||
@@ -336,11 +393,19 @@ export const CustomCameraControls = () => {
|
|||||||
if (!controls.current) return
|
if (!controls.current) return
|
||||||
if (firstLoad.current) {
|
if (firstLoad.current) {
|
||||||
firstLoad.current = false
|
firstLoad.current = false
|
||||||
|
clearPendingFloorplanNavigationPose()
|
||||||
controls.current.setLookAt(20, 20, 20, 0, 0, 0, true)
|
controls.current.setLookAt(20, 20, 20, 0, 0, 0, true)
|
||||||
}
|
}
|
||||||
controls.current.getTarget(currentTarget)
|
controls.current.getTarget(currentTarget)
|
||||||
|
clearPendingFloorplanNavigationPose()
|
||||||
controls.current.moveTo(currentTarget.x, targetY, currentTarget.z, true)
|
controls.current.moveTo(currentTarget.x, targetY, currentTarget.z, true)
|
||||||
}, [currentLevelId, isPreviewMode, isFirstPersonMode, isRestoringFirstPersonPose])
|
}, [
|
||||||
|
clearPendingFloorplanNavigationPose,
|
||||||
|
currentLevelId,
|
||||||
|
isPreviewMode,
|
||||||
|
isFirstPersonMode,
|
||||||
|
isRestoringFirstPersonPose,
|
||||||
|
])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isFirstPersonMode || !controls.current) return
|
if (isFirstPersonMode || !controls.current) return
|
||||||
@@ -368,6 +433,7 @@ export const CustomCameraControls = () => {
|
|||||||
controls.current.getTarget(tempTarget)
|
controls.current.getTarget(tempTarget)
|
||||||
tempDelta.copy(tempCenter).sub(tempTarget)
|
tempDelta.copy(tempCenter).sub(tempTarget)
|
||||||
|
|
||||||
|
clearPendingFloorplanNavigationPose()
|
||||||
controls.current.setLookAt(
|
controls.current.setLookAt(
|
||||||
tempPosition.x + tempDelta.x,
|
tempPosition.x + tempDelta.x,
|
||||||
tempPosition.y + tempDelta.y,
|
tempPosition.y + tempDelta.y,
|
||||||
@@ -378,7 +444,7 @@ export const CustomCameraControls = () => {
|
|||||||
true,
|
true,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
[isPreviewMode, isFirstPersonMode],
|
[clearPendingFloorplanNavigationPose, isPreviewMode, isFirstPersonMode],
|
||||||
)
|
)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -397,14 +463,24 @@ export const CustomCameraControls = () => {
|
|||||||
if (!control) return
|
if (!control) return
|
||||||
|
|
||||||
lastApplied2dNavigationRevision.current = pose.revision
|
lastApplied2dNavigationRevision.current = pose.revision
|
||||||
|
const targetAzimuth = nearestEquivalentRadians(pose.azimuth, control.azimuthAngle)
|
||||||
|
const viewWidthUpdate = resolveCameraViewWidthUpdate(
|
||||||
|
control,
|
||||||
|
camera,
|
||||||
|
pose.viewWidth,
|
||||||
|
viewportSize,
|
||||||
|
)
|
||||||
pendingFloorplanNavigationPose.current = {
|
pendingFloorplanNavigationPose.current = {
|
||||||
target: [...pose.target],
|
target: [...pose.target],
|
||||||
azimuth: pose.azimuth,
|
azimuth: targetAzimuth,
|
||||||
viewWidth: pose.viewWidth,
|
viewWidth: viewWidthUpdate.viewWidth,
|
||||||
|
publishOnComplete:
|
||||||
|
Math.abs(viewWidthUpdate.viewWidth - pose.viewWidth) >=
|
||||||
|
NAVIGATION_SYNC_VIEW_WIDTH_EPSILON,
|
||||||
}
|
}
|
||||||
control.moveTo(pose.target[0], pose.target[1], pose.target[2], true)
|
control.moveTo(pose.target[0], pose.target[1], pose.target[2], true)
|
||||||
control.rotateTo(pose.azimuth, control.polarAngle, true)
|
control.rotateTo(targetAzimuth, control.polarAngle, true)
|
||||||
applyCameraViewWidth(control, camera, pose.viewWidth, viewportSize)
|
applyCameraViewWidth(control, viewWidthUpdate)
|
||||||
})
|
})
|
||||||
}, [camera, isFirstPersonMode, viewportSize])
|
}, [camera, isFirstPersonMode, viewportSize])
|
||||||
|
|
||||||
@@ -424,6 +500,18 @@ export const CustomCameraControls = () => {
|
|||||||
) {
|
) {
|
||||||
lastPublishedNavigationSync.current = pendingFloorplanPose
|
lastPublishedNavigationSync.current = pendingFloorplanPose
|
||||||
pendingFloorplanNavigationPose.current = null
|
pendingFloorplanNavigationPose.current = null
|
||||||
|
if (pendingFloorplanPose.publishOnComplete) {
|
||||||
|
useEditor.getState().publishNavigationSyncPose({
|
||||||
|
source: '3d',
|
||||||
|
target: [
|
||||||
|
pendingFloorplanPose.target[0],
|
||||||
|
pendingFloorplanPose.target[1],
|
||||||
|
pendingFloorplanPose.target[2],
|
||||||
|
],
|
||||||
|
azimuth: pendingFloorplanPose.azimuth,
|
||||||
|
viewWidth: pendingFloorplanPose.viewWidth,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -476,32 +564,6 @@ export const CustomCameraControls = () => {
|
|||||||
if (horizontal === 0 && vertical === 0) return
|
if (horizontal === 0 && vertical === 0) return
|
||||||
|
|
||||||
const control = controls.current
|
const control = controls.current
|
||||||
control.getPosition(keyboardPanPosition)
|
|
||||||
control.getTarget(keyboardPanTarget)
|
|
||||||
|
|
||||||
camera.updateMatrixWorld()
|
|
||||||
keyboardPanScreenRight.setFromMatrixColumn(camera.matrixWorld, 0)
|
|
||||||
keyboardPanScreenRight.y = 0
|
|
||||||
keyboardPanScreenUp.setFromMatrixColumn(camera.matrixWorld, 1)
|
|
||||||
keyboardPanScreenUp.y = 0
|
|
||||||
|
|
||||||
if (keyboardPanScreenRight.lengthSq() < 1e-6) {
|
|
||||||
keyboardPanScreenRight.set(1, 0, 0)
|
|
||||||
} else {
|
|
||||||
keyboardPanScreenRight.normalize()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (keyboardPanScreenUp.lengthSq() < 1e-6) {
|
|
||||||
keyboardPanScreenUp.copy(keyboardPanTarget).sub(keyboardPanPosition)
|
|
||||||
keyboardPanScreenUp.y = 0
|
|
||||||
if (keyboardPanScreenUp.lengthSq() < 1e-6) {
|
|
||||||
keyboardPanScreenUp.set(0, 0, -1)
|
|
||||||
} else {
|
|
||||||
keyboardPanScreenUp.normalize()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
keyboardPanScreenUp.normalize()
|
|
||||||
}
|
|
||||||
|
|
||||||
control.getSpherical(keyboardPanSpherical, false)
|
control.getSpherical(keyboardPanSpherical, false)
|
||||||
const viewWidth = getCameraViewWidth(camera, keyboardPanSpherical.radius, viewportSize)
|
const viewWidth = getCameraViewWidth(camera, keyboardPanSpherical.radius, viewportSize)
|
||||||
@@ -511,21 +573,9 @@ export const CustomCameraControls = () => {
|
|||||||
)
|
)
|
||||||
const step = (speed * Math.min(delta, 0.05)) / Math.hypot(horizontal, vertical)
|
const step = (speed * Math.min(delta, 0.05)) / Math.hypot(horizontal, vertical)
|
||||||
|
|
||||||
keyboardPanDelta
|
|
||||||
.set(0, 0, 0)
|
|
||||||
.addScaledVector(keyboardPanScreenRight, horizontal * step)
|
|
||||||
.addScaledVector(keyboardPanScreenUp, vertical * step)
|
|
||||||
|
|
||||||
pendingFloorplanNavigationPose.current = null
|
pendingFloorplanNavigationPose.current = null
|
||||||
control.setLookAt(
|
if (horizontal !== 0) control.truck(horizontal * step, 0, true)
|
||||||
keyboardPanPosition.x + keyboardPanDelta.x,
|
if (vertical !== 0) control.forward(vertical * step, true)
|
||||||
keyboardPanPosition.y,
|
|
||||||
keyboardPanPosition.z + keyboardPanDelta.z,
|
|
||||||
keyboardPanTarget.x + keyboardPanDelta.x,
|
|
||||||
keyboardPanTarget.y,
|
|
||||||
keyboardPanTarget.z + keyboardPanDelta.z,
|
|
||||||
false,
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// Configure mouse buttons based on control mode and camera mode
|
// Configure mouse buttons based on control mode and camera mode
|
||||||
@@ -991,6 +1041,7 @@ export const CustomCameraControls = () => {
|
|||||||
if (!node?.camera) return
|
if (!node?.camera) return
|
||||||
const { position, target } = node.camera
|
const { position, target } = node.camera
|
||||||
|
|
||||||
|
clearPendingFloorplanNavigationPose()
|
||||||
controls.current.setLookAt(
|
controls.current.setLookAt(
|
||||||
position[0],
|
position[0],
|
||||||
position[1],
|
position[1],
|
||||||
@@ -1011,6 +1062,7 @@ export const CustomCameraControls = () => {
|
|||||||
// Otherwise, go to top view (0°)
|
// Otherwise, go to top view (0°)
|
||||||
const targetAngle = currentPolarAngle < 0.1 ? Math.PI / 4 : 0
|
const targetAngle = currentPolarAngle < 0.1 ? Math.PI / 4 : 0
|
||||||
|
|
||||||
|
clearPendingFloorplanNavigationPose()
|
||||||
controls.current.rotatePolarTo(targetAngle, true)
|
controls.current.rotatePolarTo(targetAngle, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1023,6 +1075,7 @@ export const CustomCameraControls = () => {
|
|||||||
const rounded = Math.round(currentAzimuth / (Math.PI / 2)) * (Math.PI / 2)
|
const rounded = Math.round(currentAzimuth / (Math.PI / 2)) * (Math.PI / 2)
|
||||||
const target = rounded - Math.PI / 2
|
const target = rounded - Math.PI / 2
|
||||||
|
|
||||||
|
clearPendingFloorplanNavigationPose()
|
||||||
controls.current.rotateTo(target, currentPolar, true)
|
controls.current.rotateTo(target, currentPolar, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1035,6 +1088,7 @@ export const CustomCameraControls = () => {
|
|||||||
const rounded = Math.round(currentAzimuth / (Math.PI / 2)) * (Math.PI / 2)
|
const rounded = Math.round(currentAzimuth / (Math.PI / 2)) * (Math.PI / 2)
|
||||||
const target = rounded + Math.PI / 2
|
const target = rounded + Math.PI / 2
|
||||||
|
|
||||||
|
clearPendingFloorplanNavigationPose()
|
||||||
controls.current.rotateTo(target, currentPolar, true)
|
controls.current.rotateTo(target, currentPolar, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1046,6 +1100,7 @@ export const CustomCameraControls = () => {
|
|||||||
if (isFirstPersonMode || !controls.current || isPreviewMode) return
|
if (isFirstPersonMode || !controls.current || isPreviewMode) return
|
||||||
if (!bounds) {
|
if (!bounds) {
|
||||||
// Restore default framing pose when no bounds were computed.
|
// Restore default framing pose when no bounds were computed.
|
||||||
|
clearPendingFloorplanNavigationPose()
|
||||||
controls.current.setLookAt(20, 20, 20, 0, 0, 0, true)
|
controls.current.setLookAt(20, 20, 20, 0, 0, 0, true)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -1056,6 +1111,7 @@ export const CustomCameraControls = () => {
|
|||||||
const maxExtent = Math.max(w, d)
|
const maxExtent = Math.max(w, d)
|
||||||
const distance = Math.max(maxExtent * 1.4, 15)
|
const distance = Math.max(maxExtent * 1.4, 15)
|
||||||
const height = Math.max(maxExtent * 0.8, 10)
|
const height = Math.max(maxExtent * 0.8, 10)
|
||||||
|
clearPendingFloorplanNavigationPose()
|
||||||
controls.current.setLookAt(cx + distance * 0.7, height, cz + distance * 0.7, cx, 0, cz, true)
|
controls.current.setLookAt(cx + distance * 0.7, height, cz + distance * 0.7, cx, 0, cz, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1076,7 +1132,7 @@ export const CustomCameraControls = () => {
|
|||||||
emitter.off('camera-controls:orbit-ccw', handleOrbitCCW)
|
emitter.off('camera-controls:orbit-ccw', handleOrbitCCW)
|
||||||
emitter.off('camera-controls:fit-scene', handleFitScene)
|
emitter.off('camera-controls:fit-scene', handleFitScene)
|
||||||
}
|
}
|
||||||
}, [focusNode, isPreviewMode, isFirstPersonMode])
|
}, [clearPendingFloorplanNavigationPose, focusNode, isPreviewMode, isFirstPersonMode])
|
||||||
|
|
||||||
const onTransitionStart = useCallback(() => {
|
const onTransitionStart = useCallback(() => {
|
||||||
useViewer.getState().setCameraDragging(true)
|
useViewer.getState().setCameraDragging(true)
|
||||||
|
|||||||
@@ -231,6 +231,7 @@ type FloorplanViewport = {
|
|||||||
|
|
||||||
type FloorplanNavigationViewOptions = {
|
type FloorplanNavigationViewOptions = {
|
||||||
smooth?: boolean
|
smooth?: boolean
|
||||||
|
clampViewWidth?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
type FloorplanViewAnimationTarget = {
|
type FloorplanViewAnimationTarget = {
|
||||||
@@ -832,6 +833,26 @@ function clamp(value: number, min: number, max: number) {
|
|||||||
return Math.min(Math.max(value, min), max)
|
return Math.min(Math.max(value, min), max)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function resolveFloorplanViewWidth(
|
||||||
|
requestedWidth: number,
|
||||||
|
currentWidth: number,
|
||||||
|
fittedViewport: FloorplanViewport | null,
|
||||||
|
clampViewWidth: boolean,
|
||||||
|
) {
|
||||||
|
if (!(Number.isFinite(requestedWidth) && requestedWidth > 0)) {
|
||||||
|
return currentWidth
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!clampViewWidth || !fittedViewport) {
|
||||||
|
return Math.max(0.001, requestedWidth)
|
||||||
|
}
|
||||||
|
|
||||||
|
const minWidth = fittedViewport.width * MIN_VIEWPORT_WIDTH_RATIO
|
||||||
|
const maxWidth = fittedViewport.width * MAX_VIEWPORT_WIDTH_RATIO
|
||||||
|
|
||||||
|
return clamp(requestedWidth, Math.min(minWidth, currentWidth), Math.max(maxWidth, currentWidth))
|
||||||
|
}
|
||||||
|
|
||||||
function roundPlanMeters(value: number) {
|
function roundPlanMeters(value: number) {
|
||||||
return Math.round(value * 100) / 100
|
return Math.round(value * 100) / 100
|
||||||
}
|
}
|
||||||
@@ -6046,9 +6067,12 @@ export function FloorplanPanel() {
|
|||||||
FLOORPLAN_VIEW_ROTATION_DEG + userRotationDeg - buildingRotationDeg
|
FLOORPLAN_VIEW_ROTATION_DEG + userRotationDeg - buildingRotationDeg
|
||||||
const centerSvg = rotateSvgPoint(localCenter, nextSceneRotationDeg)
|
const centerSvg = rotateSvgPoint(localCenter, nextSceneRotationDeg)
|
||||||
const fitted = latestFittedViewportRef.current
|
const fitted = latestFittedViewportRef.current
|
||||||
const minWidth = fitted ? fitted.width * MIN_VIEWPORT_WIDTH_RATIO : 0.001
|
const nextWidth = resolveFloorplanViewWidth(
|
||||||
const maxWidth = fitted ? fitted.width * MAX_VIEWPORT_WIDTH_RATIO : Number.POSITIVE_INFINITY
|
viewWidth ?? currentViewport.width,
|
||||||
const nextWidth = clamp(viewWidth ?? currentViewport.width, minWidth, maxWidth)
|
currentViewport.width,
|
||||||
|
fitted,
|
||||||
|
options?.clampViewWidth !== false,
|
||||||
|
)
|
||||||
|
|
||||||
const nextViewport = {
|
const nextViewport = {
|
||||||
centerX: centerSvg.x,
|
centerX: centerSvg.x,
|
||||||
@@ -6101,7 +6125,9 @@ export function FloorplanPanel() {
|
|||||||
buildingRotationY,
|
buildingRotationY,
|
||||||
)
|
)
|
||||||
|
|
||||||
applyFloorplanNavigationView(localCenter, nextUserRotationDeg, pose.viewWidth)
|
applyFloorplanNavigationView(localCenter, nextUserRotationDeg, pose.viewWidth, {
|
||||||
|
clampViewWidth: false,
|
||||||
|
})
|
||||||
},
|
},
|
||||||
[applyFloorplanNavigationView, buildingPosition, buildingRotationY],
|
[applyFloorplanNavigationView, buildingPosition, buildingRotationY],
|
||||||
)
|
)
|
||||||
@@ -6387,9 +6413,6 @@ export function FloorplanPanel() {
|
|||||||
viewBox,
|
viewBox,
|
||||||
])
|
])
|
||||||
|
|
||||||
const minViewportWidth = fittedViewport.width * MIN_VIEWPORT_WIDTH_RATIO
|
|
||||||
const maxViewportWidth = fittedViewport.width * MAX_VIEWPORT_WIDTH_RATIO
|
|
||||||
|
|
||||||
const palette = useMemo(
|
const palette = useMemo(
|
||||||
() =>
|
() =>
|
||||||
isDark
|
isDark
|
||||||
@@ -7089,10 +7112,11 @@ export function FloorplanPanel() {
|
|||||||
|
|
||||||
const currentViewport = viewport ?? fittedViewport
|
const currentViewport = viewport ?? fittedViewport
|
||||||
const currentViewBox = viewBox
|
const currentViewBox = viewBox
|
||||||
const nextWidth = clamp(
|
const nextWidth = resolveFloorplanViewWidth(
|
||||||
currentViewport.width * widthFactor,
|
currentViewport.width * widthFactor,
|
||||||
minViewportWidth,
|
currentViewport.width,
|
||||||
maxViewportWidth,
|
fittedViewport,
|
||||||
|
true,
|
||||||
)
|
)
|
||||||
const nextHeight = nextWidth / svgAspectRatio
|
const nextHeight = nextWidth / svgAspectRatio
|
||||||
const normalizedX = (svgPoint.x - currentViewBox.minX) / currentViewBox.width
|
const normalizedX = (svgPoint.x - currentViewBox.minX) / currentViewBox.width
|
||||||
@@ -7121,8 +7145,6 @@ export function FloorplanPanel() {
|
|||||||
fittedViewport,
|
fittedViewport,
|
||||||
floorplanSceneRotationDeg,
|
floorplanSceneRotationDeg,
|
||||||
getSvgPointFromClientPoint,
|
getSvgPointFromClientPoint,
|
||||||
maxViewportWidth,
|
|
||||||
minViewportWidth,
|
|
||||||
publishFloorplanNavigationPose,
|
publishFloorplanNavigationPose,
|
||||||
smoothFloorplanNavigationView,
|
smoothFloorplanNavigationView,
|
||||||
svgAspectRatio,
|
svgAspectRatio,
|
||||||
|
|||||||
Reference in New Issue
Block a user