@@ -158,10 +158,18 @@ const OVERVIEW_INITIAL_CAMERA_PARAMS = {
|
||||
zoom: 1
|
||||
} as const
|
||||
|
||||
// The source floor ID is only the calibration origin. This is the shared
|
||||
// visitor-facing baseline for every ordinary floor, never a per-floor override.
|
||||
const FLOOR_INITIAL_CAMERA_PARAMS = {
|
||||
position: new THREE.Vector3(315.2911, 385.8819, 234.6114),
|
||||
target: new THREE.Vector3(19.7236, -65.382, 42.1148),
|
||||
zoom: 1
|
||||
} as const
|
||||
|
||||
const getIndoorInitialCameraDirection = () => (
|
||||
OVERVIEW_INITIAL_CAMERA_PARAMS.position
|
||||
FLOOR_INITIAL_CAMERA_PARAMS.position
|
||||
.clone()
|
||||
.sub(OVERVIEW_INITIAL_CAMERA_PARAMS.target)
|
||||
.sub(FLOOR_INITIAL_CAMERA_PARAMS.target)
|
||||
.normalize()
|
||||
)
|
||||
|
||||
@@ -298,6 +306,32 @@ const referenceOverviewCameraState: CameraSnapshot = (() => {
|
||||
}
|
||||
})()
|
||||
|
||||
const referenceFloorCameraState: CameraSnapshot = (() => {
|
||||
const referenceCamera = new THREE.PerspectiveCamera(
|
||||
SGS_VISUAL_RENDER_CONFIG.camera.fov,
|
||||
1,
|
||||
SGS_VISUAL_RENDER_CONFIG.camera.near,
|
||||
SGS_VISUAL_RENDER_CONFIG.camera.far
|
||||
)
|
||||
referenceCamera.position.copy(FLOOR_INITIAL_CAMERA_PARAMS.position)
|
||||
referenceCamera.up.set(0, 1, 0)
|
||||
referenceCamera.zoom = FLOOR_INITIAL_CAMERA_PARAMS.zoom
|
||||
referenceCamera.lookAt(FLOOR_INITIAL_CAMERA_PARAMS.target)
|
||||
referenceCamera.updateProjectionMatrix()
|
||||
|
||||
return {
|
||||
position: referenceCamera.position.clone(),
|
||||
target: FLOOR_INITIAL_CAMERA_PARAMS.target.clone(),
|
||||
up: referenceCamera.up.clone(),
|
||||
quaternion: referenceCamera.quaternion.clone(),
|
||||
fov: referenceCamera.fov,
|
||||
zoom: referenceCamera.zoom,
|
||||
near: referenceCamera.near,
|
||||
far: referenceCamera.far,
|
||||
distance: referenceCamera.position.distanceTo(FLOOR_INITIAL_CAMERA_PARAMS.target)
|
||||
}
|
||||
})()
|
||||
|
||||
type FloorIndexItem = GuideModelFloorAsset
|
||||
|
||||
interface FloorOption {
|
||||
@@ -3498,44 +3532,7 @@ const getBoxSignature = (box: THREE.Box3) => [
|
||||
const createFloorViewBaseline = (floorId: string, model: THREE.Object3D, modelUrl: string): FloorViewBaseline | null => {
|
||||
if (!camera || !controls) return null
|
||||
|
||||
const overviewCamera = initialGuideState?.camera || referenceOverviewCameraState
|
||||
const box = getObjectBox(model)
|
||||
const center = box.getCenter(new THREE.Vector3())
|
||||
const size = box.getSize(new THREE.Vector3())
|
||||
const maxDim = Math.max(size.x, size.y, size.z, 1)
|
||||
const verticalFov = overviewCamera.fov * (Math.PI / 180)
|
||||
const horizontalFov = 2 * Math.atan(Math.tan(verticalFov / 2) * camera.aspect)
|
||||
const visibleFraction = FLOOR_CAMERA_DISTANCE_FACTOR
|
||||
const distance = Math.max(
|
||||
size.y / (2 * Math.tan(verticalFov / 2) * visibleFraction),
|
||||
size.x / (2 * Math.tan(horizontalFov / 2) * visibleFraction),
|
||||
size.z / (2 * Math.tan(verticalFov / 2) * visibleFraction),
|
||||
1
|
||||
)
|
||||
const direction = overviewCamera.position.clone().sub(overviewCamera.target).normalize()
|
||||
const target = center.clone()
|
||||
const position = target.clone().add(direction.multiplyScalar(distance))
|
||||
const offsetRatio = SGS_VISUAL_RENDER_CONFIG.framing.floorScreenOffsetRatio
|
||||
const viewDirection = target.clone().sub(position).normalize()
|
||||
const cameraRight = new THREE.Vector3().crossVectors(viewDirection, overviewCamera.up).normalize()
|
||||
const viewHeight = 2 * distance * Math.tan(verticalFov / 2)
|
||||
const viewWidth = viewHeight * camera.aspect
|
||||
const screenOffset = cameraRight.multiplyScalar(offsetRatio.x * viewWidth)
|
||||
.add(overviewCamera.up.clone().multiplyScalar(offsetRatio.y * viewHeight))
|
||||
position.add(screenOffset)
|
||||
target.add(screenOffset)
|
||||
|
||||
const baselineCamera = new THREE.PerspectiveCamera(
|
||||
overviewCamera.fov,
|
||||
camera.aspect,
|
||||
Math.max(SGS_VISUAL_RENDER_CONFIG.camera.near, maxDim / 1000),
|
||||
Math.max(SGS_VISUAL_RENDER_CONFIG.camera.far, maxDim * 20)
|
||||
)
|
||||
baselineCamera.position.copy(position)
|
||||
baselineCamera.up.copy(overviewCamera.up)
|
||||
baselineCamera.zoom = overviewCamera.zoom
|
||||
baselineCamera.lookAt(target)
|
||||
baselineCamera.updateProjectionMatrix()
|
||||
|
||||
return {
|
||||
floorId,
|
||||
@@ -3543,17 +3540,7 @@ const createFloorViewBaseline = (floorId: string, model: THREE.Object3D, modelUr
|
||||
packageEpoch: modelPackageEpoch,
|
||||
aspectRatio: camera.aspect,
|
||||
boundsSignature: getBoxSignature(box),
|
||||
camera: {
|
||||
position: baselineCamera.position.clone(),
|
||||
target,
|
||||
up: baselineCamera.up.clone(),
|
||||
quaternion: baselineCamera.quaternion.clone(),
|
||||
fov: baselineCamera.fov,
|
||||
zoom: baselineCamera.zoom,
|
||||
near: baselineCamera.near,
|
||||
far: baselineCamera.far,
|
||||
distance
|
||||
}
|
||||
camera: cloneCameraSnapshot(referenceFloorCameraState)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4243,7 +4230,7 @@ const commitPreparedFloorScene = (
|
||||
const floorBaseline = options.applyFloorBaseline
|
||||
? getFloorViewBaseline(expectedFloorId, activeModel, prepared.floor.modelUrl)
|
||||
: null
|
||||
const cameraSnapshot = floorBaseline?.camera || options.cameraSnapshot || referenceOverviewCameraState
|
||||
const cameraSnapshot = floorBaseline?.camera || options.cameraSnapshot || referenceFloorCameraState
|
||||
restoreCameraSnapshot(cameraSnapshot)
|
||||
if (!referenceBuildingAnchors.length) {
|
||||
updateReferenceBuildingAnchors(activeModel)
|
||||
@@ -4292,7 +4279,9 @@ const loadFloor = async (floorId: string, options: LoadFloorOptions = {}) => {
|
||||
return false
|
||||
}
|
||||
|
||||
const cameraSnapshot = cloneCameraSnapshot(options.cameraSnapshot || captureCameraSnapshot())
|
||||
const cameraSnapshot = options.cameraSnapshot
|
||||
? cloneCameraSnapshot(options.cameraSnapshot)
|
||||
: undefined
|
||||
const loadToken = startFloorContextTransaction(requestedFloorId)
|
||||
if (options.detachPoiBeforeLoad) {
|
||||
detachActivePoiLayer()
|
||||
@@ -4303,7 +4292,7 @@ const loadFloor = async (floorId: string, options: LoadFloorOptions = {}) => {
|
||||
assertCurrentFloorContextTransaction(loadToken, requestedFloorId, prepared.ownsModel ? prepared.model : undefined)
|
||||
commitPreparedFloorScene(prepared, loadToken, requestedFloorId, {
|
||||
...options,
|
||||
cameraSnapshot
|
||||
...(cameraSnapshot ? { cameraSnapshot } : {})
|
||||
})
|
||||
return true
|
||||
} catch (error) {
|
||||
|
||||
@@ -116,6 +116,20 @@ const getReport = async (page: Page) => {
|
||||
return state!.report
|
||||
}
|
||||
|
||||
const FLOOR_CAMERA_BASELINE = {
|
||||
position: [315.2911, 385.8819, 234.6114] as const,
|
||||
target: [19.7236, -65.382, 42.1148] as const,
|
||||
zoom: 1,
|
||||
distance: 572.7601
|
||||
}
|
||||
|
||||
const assertFloorCameraBaseline = (camera: CameraReport) => {
|
||||
expect(getVectorDelta(camera.position, FLOOR_CAMERA_BASELINE.position)).toBeLessThan(1e-4)
|
||||
expect(getVectorDelta(camera.target, FLOOR_CAMERA_BASELINE.target)).toBeLessThan(1e-4)
|
||||
expect(camera.zoom).toBe(FLOOR_CAMERA_BASELINE.zoom)
|
||||
expect(Math.abs(camera.distance - FLOOR_CAMERA_BASELINE.distance)).toBeLessThan(1e-3)
|
||||
}
|
||||
|
||||
const openGuide = async (page: Page) => {
|
||||
await page.goto('/')
|
||||
await page.waitForURL(/tab=guide/, { timeout: 30_000 })
|
||||
@@ -479,6 +493,9 @@ test('floor view baseline is deterministic across repeated resets', async ({ pag
|
||||
return api.getFloorBaseline(floorId)
|
||||
}, floor.floorId)
|
||||
expect(baseline, `missing floor baseline ${floor.floorId}`).not.toBeNull()
|
||||
assertFloorCameraBaseline(baseline!)
|
||||
assertFloorCameraBaseline(first.camera)
|
||||
expect(first.modelRoot!.maxError).toBeLessThan(1e-12)
|
||||
expect(getMaximumCameraDelta(baseline!, first.camera)).toBeLessThan(1e-6)
|
||||
await page.evaluate(async (floorId) => {
|
||||
const api = (window as Window & { __GUIDE_3D_VISUAL_STABILITY__?: VisualStabilityApi })
|
||||
@@ -489,6 +506,7 @@ test('floor view baseline is deterministic across repeated resets', async ({ pag
|
||||
const second = await getReport(page)
|
||||
expect(second.activeView).toBe('floor')
|
||||
expect(second.floorId).toBe(floor.floorId)
|
||||
assertFloorCameraBaseline(second.camera)
|
||||
expect(getMaximumCameraDelta(first.camera, second.camera)).toBeLessThan(1e-6)
|
||||
expect(getMaximumCameraDelta(baseline!, second.camera)).toBeLessThan(1e-6)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user