fix(editor): compass sync + fast align-to-north
- Remove CSS transition on compass needle SVG so rotation tracks exactly without rubber-band lag (was transition-transform 150ms) - Drive compass needle imperatively via ref when floorplan panel is hidden (3D-only mode), avoiding per-frame React re-renders of the full SVG while keeping the needle live - Align-to-north in 3D-only mode derives from latestNavigationSyncPose instead of stale viewport, publishes directly to the 3D camera - Temporarily lower camera-controls smoothTime from 0.25s to 0.18s during 2D-origin transitions (SmoothDamp τ≈90ms matches the 2D exponential decay), restoring on completion or cancellation Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
a477c4ade3
commit
cb5cfc7e40
@@ -369,10 +369,15 @@ export const CustomCameraControls = () => {
|
||||
const lastPublishedNavigationSync = useRef<NavigationCameraPoseSnapshot | null>(null)
|
||||
const pendingFloorplanNavigationPose = useRef<PendingNavigationCameraPoseSnapshot | null>(null)
|
||||
const lastApplied2dNavigationRevision = useRef(0)
|
||||
const savedSmoothTimeRef = useRef<number | null>(null)
|
||||
const maxPolarAngle =
|
||||
!isPreviewMode && allowUndergroundCamera ? DEBUG_MAX_POLAR_ANGLE : DEFAULT_MAX_POLAR_ANGLE
|
||||
const clearPendingFloorplanNavigationPose = useCallback(() => {
|
||||
pendingFloorplanNavigationPose.current = null
|
||||
if (savedSmoothTimeRef.current !== null && controls.current) {
|
||||
controls.current.smoothTime = savedSmoothTimeRef.current
|
||||
savedSmoothTimeRef.current = null
|
||||
}
|
||||
}, [])
|
||||
|
||||
const camera = useThree((state) => state.camera)
|
||||
@@ -478,6 +483,13 @@ export const CustomCameraControls = () => {
|
||||
Math.abs(viewWidthUpdate.viewWidth - pose.viewWidth) >=
|
||||
NAVIGATION_SYNC_VIEW_WIDTH_EPSILON,
|
||||
}
|
||||
// Match 3D settle time to 2D exponential decay (τ=90ms). SmoothDamp's
|
||||
// effective time constant is smoothTime/2, so smoothTime=0.18 gives
|
||||
// τ≈90ms and visual convergence in ~350-400ms, matching the 2D panel.
|
||||
if (savedSmoothTimeRef.current === null) {
|
||||
savedSmoothTimeRef.current = control.smoothTime
|
||||
}
|
||||
control.smoothTime = 0.18
|
||||
control.moveTo(pose.target[0], pose.target[1], pose.target[2], true)
|
||||
control.rotateTo(targetAzimuth, control.polarAngle, true)
|
||||
applyCameraViewWidth(control, viewWidthUpdate)
|
||||
@@ -500,6 +512,10 @@ export const CustomCameraControls = () => {
|
||||
) {
|
||||
lastPublishedNavigationSync.current = pendingFloorplanPose
|
||||
pendingFloorplanNavigationPose.current = null
|
||||
if (savedSmoothTimeRef.current !== null && controls.current) {
|
||||
controls.current.smoothTime = savedSmoothTimeRef.current
|
||||
savedSmoothTimeRef.current = null
|
||||
}
|
||||
if (pendingFloorplanPose.publishOnComplete) {
|
||||
useEditor.getState().publishNavigationSyncPose({
|
||||
source: '3d',
|
||||
|
||||
@@ -457,9 +457,11 @@ type GuideHandleHintAnchor = {
|
||||
function FloorplanCompassButton({
|
||||
northRotationDeg,
|
||||
onAlignNorth,
|
||||
needleRef,
|
||||
}: {
|
||||
northRotationDeg: number
|
||||
onAlignNorth: () => void
|
||||
needleRef?: React.RefObject<SVGSVGElement | null>
|
||||
}) {
|
||||
return (
|
||||
<Tooltip>
|
||||
@@ -480,7 +482,8 @@ function FloorplanCompassButton({
|
||||
<span className="relative flex h-6 w-6 items-center justify-center rounded-full bg-[#b8b8b8] shadow-inner dark:bg-neutral-700">
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="h-6 w-6 transition-transform duration-150 ease-out"
|
||||
className="h-6 w-6"
|
||||
ref={needleRef}
|
||||
style={{ transform: `rotate(${northRotationDeg}deg)` }}
|
||||
viewBox="0 0 48 48"
|
||||
>
|
||||
@@ -5078,6 +5081,7 @@ export function FloorplanPanel({
|
||||
const latestNavigationSyncPoseRef = useRef<NavigationSyncPose | null>(
|
||||
useEditor.getState().navigationSyncPose,
|
||||
)
|
||||
const compassNeedleRef = useRef<SVGSVGElement | null>(null)
|
||||
const levelId = useViewer((state) => state.selection.levelId)
|
||||
const buildingId = useViewer((state) => state.selection.buildingId)
|
||||
const selectedZoneId = useViewer((state) => state.selection.zoneId)
|
||||
@@ -6563,6 +6567,20 @@ export function FloorplanPanel({
|
||||
latestNavigationSyncPoseRef.current = pose
|
||||
|
||||
if (pose.source === '3d') {
|
||||
if (!isFloorplanOpenRef.current) {
|
||||
// Panel hidden — drive the compass needle imperatively without
|
||||
// triggering React state (setViewport) that would re-render the
|
||||
// full floorplan SVG every camera frame.
|
||||
const nextDeg = floorplanRotationFromCameraAzimuth(
|
||||
pose.azimuth,
|
||||
latestFloorplanUserRotationDegRef.current,
|
||||
)
|
||||
latestFloorplanUserRotationDegRef.current = nextDeg
|
||||
if (compassNeedleRef.current) {
|
||||
compassNeedleRef.current.style.transform = `rotate(${nextDeg}deg)`
|
||||
}
|
||||
return
|
||||
}
|
||||
syncFloorplanViewportToNavigationPose(pose)
|
||||
}
|
||||
})
|
||||
@@ -7345,6 +7363,25 @@ export function FloorplanPanel({
|
||||
)
|
||||
|
||||
const alignFloorplanViewToNorth = useCallback(() => {
|
||||
if (!isFloorplanOpenRef.current) {
|
||||
// Panel hidden — derive from the live 3D camera pose and publish
|
||||
// directly. The compass animates via the imperative subscription as
|
||||
// the 3D camera transitions.
|
||||
const pose = latestNavigationSyncPoseRef.current
|
||||
if (!pose) return
|
||||
const currentRotation = latestFloorplanUserRotationDegRef.current
|
||||
const northAzimuth = cameraAzimuthFromFloorplanRotation(
|
||||
nearestEquivalentDegrees(0, currentRotation),
|
||||
)
|
||||
useEditor.getState().publishNavigationSyncPose({
|
||||
source: '2d',
|
||||
target: [...pose.target],
|
||||
azimuth: northAzimuth,
|
||||
viewWidth: pose.viewWidth,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
const currentViewport = latestViewportRef.current ?? latestFittedViewportRef.current
|
||||
if (!currentViewport) {
|
||||
return
|
||||
@@ -10761,6 +10798,7 @@ export function FloorplanPanel({
|
||||
(compassHost ? (
|
||||
createPortal(
|
||||
<FloorplanCompassButton
|
||||
needleRef={compassNeedleRef}
|
||||
northRotationDeg={floorplanUserRotationDeg}
|
||||
onAlignNorth={alignFloorplanViewToNorth}
|
||||
/>,
|
||||
@@ -10768,6 +10806,7 @@ export function FloorplanPanel({
|
||||
)
|
||||
) : (
|
||||
<FloorplanCompassButton
|
||||
needleRef={compassNeedleRef}
|
||||
northRotationDeg={floorplanUserRotationDeg}
|
||||
onAlignNorth={alignFloorplanViewToNorth}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user