修复三维楼层自动切换与模型加载
- 优化建筑外观与馆内楼层的缩放自动切换状态 - 首次进入馆内默认加载 1F 楼层模型,后续重进保持上次楼层 - 手动楼层切换强制加载对应楼层模型,避免误用外观兜底 - 调整三维渲染色调、背景和灯光,减轻楼层模型灰蒙感 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -83,6 +83,7 @@ interface CameraFitOptions {
|
|||||||
interface LoadFloorOptions {
|
interface LoadFloorOptions {
|
||||||
preserveCurrentSceneUntilReady?: boolean
|
preserveCurrentSceneUntilReady?: boolean
|
||||||
suppressProgress?: boolean
|
suppressProgress?: boolean
|
||||||
|
allowOverviewFallback?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
interface LoadModelOptions {
|
interface LoadModelOptions {
|
||||||
@@ -190,6 +191,7 @@ const props = withDefaults(defineProps<{
|
|||||||
routePreview?: GuideRouteResult | null
|
routePreview?: GuideRouteResult | null
|
||||||
showRoute?: boolean
|
showRoute?: boolean
|
||||||
autoSwitch?: boolean
|
autoSwitch?: boolean
|
||||||
|
disableAutoExit?: boolean
|
||||||
autoSwitchThresholdLow?: number
|
autoSwitchThresholdLow?: number
|
||||||
autoSwitchThresholdHigh?: number
|
autoSwitchThresholdHigh?: number
|
||||||
autoSwitchCooldown?: number
|
autoSwitchCooldown?: number
|
||||||
@@ -205,6 +207,7 @@ const props = withDefaults(defineProps<{
|
|||||||
routePreview: null,
|
routePreview: null,
|
||||||
showRoute: false,
|
showRoute: false,
|
||||||
autoSwitch: true,
|
autoSwitch: true,
|
||||||
|
disableAutoExit: false,
|
||||||
autoSwitchThresholdLow: 1.45,
|
autoSwitchThresholdLow: 1.45,
|
||||||
autoSwitchThresholdHigh: 3.8,
|
autoSwitchThresholdHigh: 3.8,
|
||||||
autoSwitchCooldown: 2000
|
autoSwitchCooldown: 2000
|
||||||
@@ -223,7 +226,7 @@ const containerRef = ref<ContainerRef>(null)
|
|||||||
const isLoading = ref(true)
|
const isLoading = ref(true)
|
||||||
const loadError = ref(false)
|
const loadError = ref(false)
|
||||||
const loadingProgress = ref(0)
|
const loadingProgress = ref(0)
|
||||||
const loadingMessage = ref('正在读取室内导览资源...')
|
const loadingMessage = ref('正在读取馆内导览资源...')
|
||||||
const activeView = ref<ViewMode>(props.initialView)
|
const activeView = ref<ViewMode>(props.initialView)
|
||||||
const currentFloor = ref(props.initialFloorId)
|
const currentFloor = ref(props.initialFloorId)
|
||||||
const selectedPOI = ref<RenderPoi | null>(null)
|
const selectedPOI = ref<RenderPoi | null>(null)
|
||||||
@@ -264,22 +267,28 @@ let isDisposed = false
|
|||||||
let pendingTargetFocus: TargetPoiFocusRequest | null = null
|
let pendingTargetFocus: TargetPoiFocusRequest | null = null
|
||||||
let targetFocusQueue: Promise<unknown> = Promise.resolve()
|
let targetFocusQueue: Promise<unknown> = Promise.resolve()
|
||||||
let modelLoadVersion = 0
|
let modelLoadVersion = 0
|
||||||
|
let hasLoadedFloorViewOnce = false
|
||||||
let pointerDownState: { x: number; y: number } | null = null
|
let pointerDownState: { x: number; y: number } | null = null
|
||||||
let cachedOverviewModel: THREE.Object3D | null = null
|
let cachedOverviewModel: THREE.Object3D | null = null
|
||||||
let cachedSharedModelUrl = ''
|
let cachedSharedModelUrl = ''
|
||||||
let autoSwitchOverviewMaxDim = 0
|
let autoSwitchOverviewMaxDim = 0
|
||||||
|
|
||||||
// 自动切换状态
|
// 自动切换状态:建筑外观积极进入,馆内锁定后再二段退出。
|
||||||
let isAutoSwitchLocked = false
|
let isAutoSwitchLocked = false
|
||||||
let lastAutoSwitchTime = 0
|
let lastAutoSwitchTime = 0
|
||||||
let lastControlDistance = 0
|
let lastControlDistance = 0
|
||||||
let floorAutoSwitchProtectedUntil = 0
|
let activeControlGestureStartedAt = 0
|
||||||
let floorExitThresholdExceededSince = 0
|
let indoorEntryGestureLocked = false
|
||||||
let floorExitZoomOutAccumulatedDistance = 0
|
let indoorContextLockedUntil = 0
|
||||||
|
let floorExitCandidateStartedAt = 0
|
||||||
|
let floorExitCandidateBaseDistance = 0
|
||||||
|
let floorExitZoomOutAccumulatedRatio = 0
|
||||||
let autoSwitchTemporarilyDisabled = false
|
let autoSwitchTemporarilyDisabled = false
|
||||||
let autoSwitchDisableTimer: ReturnType<typeof setTimeout> | null = null
|
let autoSwitchDisableTimer: ReturnType<typeof setTimeout> | null = null
|
||||||
let suppressNextAutoSwitchCheck = false
|
let overviewEntryIntentStartedAt = 0
|
||||||
let overviewWheelZoomStartDistance = 0
|
let overviewEntryStartDistance = 0
|
||||||
|
let overviewEntryZoomInRounds = 0
|
||||||
|
let overviewWheelZoomInRounds = 0
|
||||||
let overviewWheelZoomIntentUntil = 0
|
let overviewWheelZoomIntentUntil = 0
|
||||||
let overviewWheelAutoSwitchTimer: ReturnType<typeof setTimeout> | null = null
|
let overviewWheelAutoSwitchTimer: ReturnType<typeof setTimeout> | null = null
|
||||||
let isProgrammaticCameraChange = false
|
let isProgrammaticCameraChange = false
|
||||||
@@ -293,10 +302,19 @@ const poiHitTargetScaleMultiplier = 4.8
|
|||||||
const poiHitTargetCoreScaleMultiplier = 5.4
|
const poiHitTargetCoreScaleMultiplier = 5.4
|
||||||
const poiScreenHitRadiusPx = 64
|
const poiScreenHitRadiusPx = 64
|
||||||
const poiScreenHitRadiusCorePx = 76
|
const poiScreenHitRadiusCorePx = 76
|
||||||
const floorAutoSwitchProtectionMs = 1600
|
const indoorContextLockMs = 5000
|
||||||
const floorExitZoomOutIntentMinDeltaRatio = 0.01
|
const indoorGestureExitGraceMs = 900
|
||||||
const floorExitZoomOutAccumulatedRatio = 0.12
|
const overviewEntryIntentWindowMs = 1200
|
||||||
const floorExitThresholdHoldMs = 650
|
const overviewEntryMinZoomInRatio = 0.04
|
||||||
|
const overviewEntryAccumulatedRatio = 0.2
|
||||||
|
const overviewEntryMinRounds = 2
|
||||||
|
const overviewEntryMaxRounds = 4
|
||||||
|
const overviewWheelEntryDelayMs = 180
|
||||||
|
const floorExitCandidateThresholdMultiplier = 1.12
|
||||||
|
const floorExitConfirmThresholdMultiplier = 1.34
|
||||||
|
const floorExitZoomOutIntentMinDeltaRatio = 0.015
|
||||||
|
const floorExitAccumulatedZoomOutRatio = 0.26
|
||||||
|
const floorExitCandidateHoldMs = 900
|
||||||
|
|
||||||
const syncControlInteractionOptions = () => {
|
const syncControlInteractionOptions = () => {
|
||||||
if (!controls) return
|
if (!controls) return
|
||||||
@@ -359,7 +377,7 @@ const floorExteriorNameKeywords = [
|
|||||||
'玻璃幕墙',
|
'玻璃幕墙',
|
||||||
'楼顶',
|
'楼顶',
|
||||||
'屋顶',
|
'屋顶',
|
||||||
'室外'
|
'馆外'
|
||||||
]
|
]
|
||||||
|
|
||||||
const overviewBuildingSubjectNameKeywords = [
|
const overviewBuildingSubjectNameKeywords = [
|
||||||
@@ -727,11 +745,6 @@ const refreshPoiVisibilityByDistance = () => {
|
|||||||
|
|
||||||
const handleControlChange = () => {
|
const handleControlChange = () => {
|
||||||
updatePoiVisibilityByDistance()
|
updatePoiVisibilityByDistance()
|
||||||
if (suppressNextAutoSwitchCheck) {
|
|
||||||
suppressNextAutoSwitchCheck = false
|
|
||||||
updateLastControlDistance()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
checkAutoSwitch()
|
checkAutoSwitch()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -862,9 +875,19 @@ const updatePoiTapFeedback = (now: number) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleControlStart = () => {
|
const handleControlStart = () => {
|
||||||
|
activeControlGestureStartedAt = Date.now()
|
||||||
cancelCameraTween({ manual: true })
|
cancelCameraTween({ manual: true })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleControlEnd = () => {
|
||||||
|
if (indoorEntryGestureLocked) {
|
||||||
|
indoorContextLockedUntil = Math.max(indoorContextLockedUntil, Date.now() + indoorGestureExitGraceMs)
|
||||||
|
indoorEntryGestureLocked = false
|
||||||
|
}
|
||||||
|
activeControlGestureStartedAt = 0
|
||||||
|
updateLastControlDistance()
|
||||||
|
}
|
||||||
|
|
||||||
const setProgress = (progress: number, message: string) => {
|
const setProgress = (progress: number, message: string) => {
|
||||||
loadingProgress.value = Math.min(100, Math.max(0, progress))
|
loadingProgress.value = Math.min(100, Math.max(0, progress))
|
||||||
loadingMessage.value = message
|
loadingMessage.value = message
|
||||||
@@ -923,7 +946,7 @@ const initThree = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
scene = new THREE.Scene()
|
scene = new THREE.Scene()
|
||||||
scene.background = new THREE.Color(0xf1f3ee)
|
scene.background = new THREE.Color(0xf7f8f4)
|
||||||
|
|
||||||
const width = Math.max(1, container.clientWidth)
|
const width = Math.max(1, container.clientWidth)
|
||||||
const height = Math.max(1, container.clientHeight)
|
const height = Math.max(1, container.clientHeight)
|
||||||
@@ -938,8 +961,8 @@ const initThree = async () => {
|
|||||||
renderer.setSize(width, height)
|
renderer.setSize(width, height)
|
||||||
renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 2))
|
renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 2))
|
||||||
renderer.outputColorSpace = THREE.SRGBColorSpace
|
renderer.outputColorSpace = THREE.SRGBColorSpace
|
||||||
renderer.toneMapping = THREE.ACESFilmicToneMapping
|
renderer.toneMapping = THREE.NeutralToneMapping
|
||||||
renderer.toneMappingExposure = 1.05
|
renderer.toneMappingExposure = 1.0
|
||||||
container.appendChild(renderer.domElement)
|
container.appendChild(renderer.domElement)
|
||||||
|
|
||||||
controls = new OrbitControls(camera, renderer.domElement)
|
controls = new OrbitControls(camera, renderer.domElement)
|
||||||
@@ -964,14 +987,14 @@ const initThree = async () => {
|
|||||||
routeGroup.name = 'GuideModelRoute'
|
routeGroup.name = 'GuideModelRoute'
|
||||||
scene.add(routeGroup)
|
scene.add(routeGroup)
|
||||||
|
|
||||||
const ambientLight = new THREE.AmbientLight(0xffffff, 1.6)
|
const ambientLight = new THREE.AmbientLight(0xffffff, 0.9)
|
||||||
scene.add(ambientLight)
|
scene.add(ambientLight)
|
||||||
|
|
||||||
const keyLight = new THREE.DirectionalLight(0xffffff, 2.2)
|
const keyLight = new THREE.DirectionalLight(0xffffff, 2.0)
|
||||||
keyLight.position.set(80, 120, 60)
|
keyLight.position.set(80, 120, 60)
|
||||||
scene.add(keyLight)
|
scene.add(keyLight)
|
||||||
|
|
||||||
const fillLight = new THREE.DirectionalLight(0xdfeaff, 0.9)
|
const fillLight = new THREE.DirectionalLight(0xdfeaff, 0.45)
|
||||||
fillLight.position.set(-80, 60, -80)
|
fillLight.position.set(-80, 60, -80)
|
||||||
scene.add(fillLight)
|
scene.add(fillLight)
|
||||||
|
|
||||||
@@ -979,11 +1002,12 @@ const initThree = async () => {
|
|||||||
resizeObserver.observe(container)
|
resizeObserver.observe(container)
|
||||||
container.addEventListener('pointerdown', handlePointerDown)
|
container.addEventListener('pointerdown', handlePointerDown)
|
||||||
container.addEventListener('pointerup', handlePointerUp)
|
container.addEventListener('pointerup', handlePointerUp)
|
||||||
container.addEventListener('wheel', handleWheelIntent, { passive: true })
|
renderer.domElement.addEventListener('wheel', handleWheelIntent, { passive: true, capture: true })
|
||||||
|
|
||||||
// 监听控制器变化,用于自动切换和点位可见层级更新
|
// 监听控制器变化,用于自动切换和点位可见层级更新
|
||||||
if (controls) {
|
if (controls) {
|
||||||
controls.addEventListener('start', handleControlStart)
|
controls.addEventListener('start', handleControlStart)
|
||||||
|
controls.addEventListener('end', handleControlEnd)
|
||||||
controls.addEventListener('change', handleControlChange)
|
controls.addEventListener('change', handleControlChange)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1743,11 +1767,27 @@ const canAttachCachedSharedModel = (modelUrl: string) => (
|
|||||||
Boolean(cachedOverviewModel && cachedSharedModelUrl === modelUrl && scene)
|
Boolean(cachedOverviewModel && cachedSharedModelUrl === modelUrl && scene)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const canAttachCachedOverviewModel = () => Boolean(cachedOverviewModel && scene)
|
||||||
|
|
||||||
const canLoadFloorSilently = (floorId: string) => {
|
const canLoadFloorSilently = (floorId: string) => {
|
||||||
const floor = floorIndex.value.find((item) => item.floorId === floorId)
|
const floor = floorIndex.value.find((item) => item.floorId === floorId)
|
||||||
return Boolean(floor && canAttachCachedSharedModel(floor.modelUrl))
|
return Boolean(floor && canAttachCachedSharedModel(floor.modelUrl))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getDefaultFloorId = () => (
|
||||||
|
floorIndex.value.find((floor) => floor.floorId === props.initialFloorId)?.floorId
|
||||||
|
|| floorIndex.value.find((floor) => floor.floorId === 'L1')?.floorId
|
||||||
|
|| floorIndex.value[0]?.floorId
|
||||||
|
|| props.initialFloorId
|
||||||
|
|| 'L1'
|
||||||
|
)
|
||||||
|
|
||||||
|
const getAutoEntryTargetFloorId = () => (
|
||||||
|
hasLoadedFloorViewOnce
|
||||||
|
? currentFloor.value || getDefaultFloorId()
|
||||||
|
: getDefaultFloorId()
|
||||||
|
)
|
||||||
|
|
||||||
const hasRenderableSceneForFloorTransition = () => Boolean(scene && activeModel)
|
const hasRenderableSceneForFloorTransition = () => Boolean(scene && activeModel)
|
||||||
|
|
||||||
const createMultiFloorItemFromSharedModel = (sourceModel: THREE.Object3D, floor: FloorIndexItem) => {
|
const createMultiFloorItemFromSharedModel = (sourceModel: THREE.Object3D, floor: FloorIndexItem) => {
|
||||||
@@ -1784,19 +1824,34 @@ const updateLastControlDistance = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const resetOverviewEntryIntent = () => {
|
||||||
|
overviewEntryIntentStartedAt = 0
|
||||||
|
overviewEntryStartDistance = 0
|
||||||
|
overviewEntryZoomInRounds = 0
|
||||||
|
overviewWheelZoomInRounds = 0
|
||||||
|
overviewWheelZoomIntentUntil = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
const resetFloorExitCandidate = () => {
|
||||||
|
floorExitCandidateStartedAt = 0
|
||||||
|
floorExitCandidateBaseDistance = 0
|
||||||
|
floorExitZoomOutAccumulatedRatio = 0
|
||||||
|
}
|
||||||
|
|
||||||
const markFloorAutoSwitchEntry = () => {
|
const markFloorAutoSwitchEntry = () => {
|
||||||
floorAutoSwitchProtectedUntil = Date.now() + floorAutoSwitchProtectionMs
|
indoorContextLockedUntil = Date.now() + indoorContextLockMs
|
||||||
floorExitThresholdExceededSince = 0
|
indoorEntryGestureLocked = activeControlGestureStartedAt > 0
|
||||||
floorExitZoomOutAccumulatedDistance = 0
|
resetOverviewEntryIntent()
|
||||||
|
resetFloorExitCandidate()
|
||||||
updateLastControlDistance()
|
updateLastControlDistance()
|
||||||
}
|
}
|
||||||
|
|
||||||
const resetAutoSwitchDistanceTracking = () => {
|
const resetAutoSwitchDistanceTracking = () => {
|
||||||
floorAutoSwitchProtectedUntil = 0
|
activeControlGestureStartedAt = 0
|
||||||
floorExitThresholdExceededSince = 0
|
indoorEntryGestureLocked = false
|
||||||
floorExitZoomOutAccumulatedDistance = 0
|
indoorContextLockedUntil = 0
|
||||||
overviewWheelZoomStartDistance = 0
|
resetOverviewEntryIntent()
|
||||||
overviewWheelZoomIntentUntil = 0
|
resetFloorExitCandidate()
|
||||||
updateLastControlDistance()
|
updateLastControlDistance()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1807,6 +1862,25 @@ const clearOverviewWheelAutoSwitchTimer = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const hasOverviewWheelEntryRounds = () => (
|
||||||
|
activeView.value === 'overview'
|
||||||
|
&& overviewWheelZoomInRounds >= overviewEntryMinRounds
|
||||||
|
)
|
||||||
|
|
||||||
|
const hasPendingOverviewWheelEntryIntent = (now: number) => (
|
||||||
|
hasOverviewWheelEntryRounds()
|
||||||
|
&& isOverviewEntryIntentActive(now)
|
||||||
|
)
|
||||||
|
|
||||||
|
const canRunPendingOverviewEntry = (now: number) => (
|
||||||
|
props.autoSwitch
|
||||||
|
&& !autoSwitchTemporarilyDisabled
|
||||||
|
&& !isLoading.value
|
||||||
|
&& !isAutoSwitchLocked
|
||||||
|
&& now - lastAutoSwitchTime >= props.autoSwitchCooldown
|
||||||
|
&& Boolean(controls && activeModel)
|
||||||
|
)
|
||||||
|
|
||||||
const handleWheelIntent = (event: WheelEvent) => {
|
const handleWheelIntent = (event: WheelEvent) => {
|
||||||
if (!controls || activeView.value !== 'overview' || event.deltaY >= 0) return
|
if (!controls || activeView.value !== 'overview' || event.deltaY >= 0) return
|
||||||
|
|
||||||
@@ -1814,47 +1888,125 @@ const handleWheelIntent = (event: WheelEvent) => {
|
|||||||
if (!Number.isFinite(distance)) return
|
if (!Number.isFinite(distance)) return
|
||||||
|
|
||||||
const now = Date.now()
|
const now = Date.now()
|
||||||
if (!overviewWheelZoomStartDistance || now > overviewWheelZoomIntentUntil) {
|
if (!isOverviewEntryIntentActive(now)) {
|
||||||
overviewWheelZoomStartDistance = distance
|
overviewEntryIntentStartedAt = now
|
||||||
|
overviewEntryStartDistance = distance
|
||||||
|
overviewEntryZoomInRounds = 0
|
||||||
|
overviewWheelZoomInRounds = 0
|
||||||
}
|
}
|
||||||
|
overviewEntryIntentStartedAt = now
|
||||||
|
overviewEntryZoomInRounds += 1
|
||||||
|
overviewWheelZoomInRounds += 1
|
||||||
overviewWheelZoomIntentUntil = now + 900
|
overviewWheelZoomIntentUntil = now + 900
|
||||||
|
|
||||||
clearOverviewWheelAutoSwitchTimer()
|
clearOverviewWheelAutoSwitchTimer()
|
||||||
overviewWheelAutoSwitchTimer = setTimeout(() => {
|
overviewWheelAutoSwitchTimer = setTimeout(() => {
|
||||||
|
const triggerNow = Date.now()
|
||||||
overviewWheelAutoSwitchTimer = null
|
overviewWheelAutoSwitchTimer = null
|
||||||
|
if (hasPendingOverviewWheelEntryIntent(triggerNow) && canRunPendingOverviewEntry(triggerNow)) {
|
||||||
checkAutoSwitch({ forceOverviewEntry: true })
|
checkAutoSwitch({ forceOverviewEntry: true })
|
||||||
}, 180)
|
}
|
||||||
|
}, overviewWheelEntryDelayMs)
|
||||||
}
|
}
|
||||||
|
|
||||||
const shouldForceOverviewEntryFromWheel = (distance: number, now: number) => (
|
const isOverviewEntryIntentActive = (now: number) => (
|
||||||
now <= overviewWheelZoomIntentUntil
|
overviewEntryIntentStartedAt > 0
|
||||||
&& overviewWheelZoomStartDistance > 0
|
&& (
|
||||||
&& distance <= overviewWheelZoomStartDistance * 0.86
|
now - overviewEntryIntentStartedAt <= overviewEntryIntentWindowMs
|
||||||
|
|| now <= overviewWheelZoomIntentUntil
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
const checkAutoSwitch = (options: { forceOverviewEntry?: boolean } = {}) => {
|
const getOverviewEntryAccumulatedRatio = (distance: number) => {
|
||||||
// 检查是否启用自动切换
|
if (!overviewEntryStartDistance || !Number.isFinite(distance)) return 0
|
||||||
if (!props.autoSwitch || autoSwitchTemporarilyDisabled || isProgrammaticCameraChange || isLoading.value) {
|
|
||||||
|
return Math.max(0, 1 - distance / overviewEntryStartDistance)
|
||||||
|
}
|
||||||
|
|
||||||
|
const trackOverviewEntryIntent = (distance: number, previousDistance: number, now: number) => {
|
||||||
|
if (!Number.isFinite(distance) || !Number.isFinite(previousDistance) || previousDistance <= 0) return
|
||||||
|
|
||||||
|
const zoomInRatio = (previousDistance - distance) / previousDistance
|
||||||
|
const hasClearZoomIn = zoomInRatio >= overviewEntryMinZoomInRatio
|
||||||
|
|
||||||
|
if (!hasClearZoomIn) {
|
||||||
|
if (!isOverviewEntryIntentActive(now)) {
|
||||||
|
resetOverviewEntryIntent()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isOverviewEntryIntentActive(now) || !overviewEntryStartDistance || distance > overviewEntryStartDistance) {
|
||||||
|
overviewEntryIntentStartedAt = now
|
||||||
|
overviewEntryStartDistance = previousDistance
|
||||||
|
overviewEntryZoomInRounds = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
overviewEntryIntentStartedAt = now
|
||||||
|
overviewEntryZoomInRounds += 1
|
||||||
|
|
||||||
|
const accumulatedRatio = getOverviewEntryAccumulatedRatio(distance)
|
||||||
|
overviewEntryZoomInRounds = Math.max(
|
||||||
|
overviewEntryZoomInRounds,
|
||||||
|
Math.floor(accumulatedRatio / overviewEntryMinZoomInRatio)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const shouldEnterFloorFromOverview = (
|
||||||
|
distance: number,
|
||||||
|
maxDim: number,
|
||||||
|
now: number,
|
||||||
|
hasForcedOverviewEntryIntent = false
|
||||||
|
) => {
|
||||||
|
const enterFloorAt = maxDim * props.autoSwitchThresholdLow
|
||||||
|
if (distance <= enterFloorAt) return true
|
||||||
|
|
||||||
|
if (hasForcedOverviewEntryIntent) return true
|
||||||
|
if (!isOverviewEntryIntentActive(now)) return false
|
||||||
|
|
||||||
|
const accumulatedRatio = getOverviewEntryAccumulatedRatio(distance)
|
||||||
|
const hasRepeatedZoomIn = overviewEntryZoomInRounds >= overviewEntryMinRounds
|
||||||
|
&& accumulatedRatio >= overviewEntryAccumulatedRatio
|
||||||
|
const hasSustainedZoomIn = overviewEntryZoomInRounds >= overviewEntryMaxRounds
|
||||||
|
&& accumulatedRatio >= overviewEntryAccumulatedRatio * 0.68
|
||||||
|
|
||||||
|
return hasRepeatedZoomIn || hasSustainedZoomIn
|
||||||
|
}
|
||||||
|
|
||||||
|
const isFloorAutoExitLocked = (now: number) => (
|
||||||
|
props.disableAutoExit
|
||||||
|
|| Boolean(props.targetFocus)
|
||||||
|
|| indoorEntryGestureLocked
|
||||||
|
|| now < indoorContextLockedUntil
|
||||||
|
)
|
||||||
|
|
||||||
|
const checkAutoSwitch = (options: { forceOverviewEntry?: boolean } = {}) => {
|
||||||
|
const now = Date.now()
|
||||||
|
const hasForcedOverviewEntryIntent = options.forceOverviewEntry === true
|
||||||
|
&& hasOverviewWheelEntryRounds()
|
||||||
|
&& canRunPendingOverviewEntry(now)
|
||||||
|
|
||||||
|
if (
|
||||||
|
!props.autoSwitch
|
||||||
|
|| autoSwitchTemporarilyDisabled
|
||||||
|
|| isLoading.value
|
||||||
|
|| (!hasForcedOverviewEntryIntent && isProgrammaticCameraChange)
|
||||||
|
) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查加载锁
|
|
||||||
if (isAutoSwitchLocked) {
|
if (isAutoSwitchLocked) {
|
||||||
emit('autoSwitchBlocked', { reason: 'loading-locked' })
|
emit('autoSwitchBlocked', { reason: 'loading-locked' })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查冷却时间
|
|
||||||
const now = Date.now()
|
|
||||||
if (now - lastAutoSwitchTime < props.autoSwitchCooldown) {
|
if (now - lastAutoSwitchTime < props.autoSwitchCooldown) {
|
||||||
emit('autoSwitchBlocked', { reason: 'cooldown' })
|
emit('autoSwitchBlocked', { reason: 'cooldown' })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查必要条件
|
|
||||||
if (!controls || !activeModel || activeView.value === 'multi') return
|
if (!controls || !activeModel || activeView.value === 'multi') return
|
||||||
|
|
||||||
// 获取当前距离
|
|
||||||
const distance = controls.getDistance()
|
const distance = controls.getDistance()
|
||||||
const previousDistance = lastControlDistance || distance
|
const previousDistance = lastControlDistance || distance
|
||||||
const distanceDelta = distance - previousDistance
|
const distanceDelta = distance - previousDistance
|
||||||
@@ -1863,20 +2015,21 @@ const checkAutoSwitch = (options: { forceOverviewEntry?: boolean } = {}) => {
|
|||||||
// 自动切换使用全馆稳定尺寸,避免单楼层可见性过滤压低 floor -> overview 阈值。
|
// 自动切换使用全馆稳定尺寸,避免单楼层可见性过滤压低 floor -> overview 阈值。
|
||||||
const maxDim = autoSwitchOverviewMaxDim || getObjectMaxDim(activeModel)
|
const maxDim = autoSwitchOverviewMaxDim || getObjectMaxDim(activeModel)
|
||||||
|
|
||||||
const enterFloorAt = maxDim * props.autoSwitchThresholdLow
|
if (activeView.value === 'overview') {
|
||||||
const exitFloorAt = maxDim * props.autoSwitchThresholdHigh
|
if (!hasForcedOverviewEntryIntent) {
|
||||||
|
trackOverviewEntryIntent(distance, previousDistance, now)
|
||||||
|
}
|
||||||
|
|
||||||
// 判断是否需要切换
|
if (!shouldEnterFloorFromOverview(distance, maxDim, now, hasForcedOverviewEntryIntent)) {
|
||||||
if (activeView.value === 'overview' && (distance < enterFloorAt || (options.forceOverviewEntry && shouldForceOverviewEntryFromWheel(distance, now)))) {
|
return
|
||||||
// 从建筑外观自动切换到单楼层
|
}
|
||||||
const targetFloorId = currentFloor.value || props.initialFloorId || 'L1'
|
|
||||||
|
const targetFloorId = getAutoEntryTargetFloorId()
|
||||||
const hasSceneToKeep = hasRenderableSceneForFloorTransition()
|
const hasSceneToKeep = hasRenderableSceneForFloorTransition()
|
||||||
isAutoSwitchLocked = true
|
isAutoSwitchLocked = true
|
||||||
lastAutoSwitchTime = now
|
lastAutoSwitchTime = now
|
||||||
floorExitThresholdExceededSince = 0
|
resetFloorExitCandidate()
|
||||||
floorExitZoomOutAccumulatedDistance = 0
|
resetOverviewEntryIntent()
|
||||||
overviewWheelZoomStartDistance = 0
|
|
||||||
overviewWheelZoomIntentUntil = 0
|
|
||||||
clearOverviewWheelAutoSwitchTimer()
|
clearOverviewWheelAutoSwitchTimer()
|
||||||
|
|
||||||
void runAutoSwitchLoad(
|
void runAutoSwitchLoad(
|
||||||
@@ -1888,7 +2041,8 @@ const checkAutoSwitch = (options: { forceOverviewEntry?: boolean } = {}) => {
|
|||||||
},
|
},
|
||||||
() => loadFloor(targetFloorId, {
|
() => loadFloor(targetFloorId, {
|
||||||
preserveCurrentSceneUntilReady: hasSceneToKeep,
|
preserveCurrentSceneUntilReady: hasSceneToKeep,
|
||||||
suppressProgress: hasSceneToKeep
|
suppressProgress: hasSceneToKeep,
|
||||||
|
allowOverviewFallback: true
|
||||||
}),
|
}),
|
||||||
'单楼层模型加载失败',
|
'单楼层模型加载失败',
|
||||||
{ showLoading: !hasSceneToKeep && !canLoadFloorSilently(targetFloorId) }
|
{ showLoading: !hasSceneToKeep && !canLoadFloorSilently(targetFloorId) }
|
||||||
@@ -1896,49 +2050,53 @@ const checkAutoSwitch = (options: { forceOverviewEntry?: boolean } = {}) => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (activeView.value !== 'floor' || props.targetFocus) {
|
if (activeView.value !== 'floor') {
|
||||||
floorExitThresholdExceededSince = 0
|
resetFloorExitCandidate()
|
||||||
floorExitZoomOutAccumulatedDistance = 0
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (now < floorAutoSwitchProtectedUntil) {
|
if (isFloorAutoExitLocked(now)) {
|
||||||
floorExitThresholdExceededSince = 0
|
resetFloorExitCandidate()
|
||||||
floorExitZoomOutAccumulatedDistance = 0
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const zoomOutIntentDelta = Math.max(maxDim * floorExitZoomOutIntentMinDeltaRatio, 1)
|
const baseExitThreshold = maxDim * props.autoSwitchThresholdHigh
|
||||||
const isClearZoomOutIntent = distanceDelta > zoomOutIntentDelta
|
const candidateExitAt = baseExitThreshold * floorExitCandidateThresholdMultiplier
|
||||||
const isBeyondExitThreshold = distance > exitFloorAt
|
const confirmExitAt = baseExitThreshold * floorExitConfirmThresholdMultiplier
|
||||||
|
const isBeyondCandidateThreshold = distance >= candidateExitAt
|
||||||
|
|
||||||
if (!isBeyondExitThreshold) {
|
if (!isBeyondCandidateThreshold) {
|
||||||
floorExitThresholdExceededSince = 0
|
resetFloorExitCandidate()
|
||||||
floorExitZoomOutAccumulatedDistance = 0
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isClearZoomOutIntent) {
|
if (!floorExitCandidateStartedAt) {
|
||||||
floorExitZoomOutAccumulatedDistance += distanceDelta
|
floorExitCandidateStartedAt = now
|
||||||
if (!floorExitThresholdExceededSince) {
|
floorExitCandidateBaseDistance = Math.max(previousDistance, distance, 1)
|
||||||
floorExitThresholdExceededSince = now
|
floorExitZoomOutAccumulatedRatio = 0
|
||||||
}
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const requiredZoomOutDistance = Math.max(maxDim * floorExitZoomOutAccumulatedRatio, 8)
|
const zoomOutRatio = previousDistance > 0 ? distanceDelta / previousDistance : 0
|
||||||
if (
|
const isClearZoomOutIntent = zoomOutRatio >= floorExitZoomOutIntentMinDeltaRatio
|
||||||
!floorExitThresholdExceededSince
|
if (isClearZoomOutIntent || distance > floorExitCandidateBaseDistance) {
|
||||||
|| floorExitZoomOutAccumulatedDistance < requiredZoomOutDistance
|
floorExitZoomOutAccumulatedRatio = Math.max(
|
||||||
) {
|
floorExitZoomOutAccumulatedRatio,
|
||||||
|
distance / Math.max(floorExitCandidateBaseDistance, 1) - 1
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasConfirmedScale = distance >= confirmExitAt
|
||||||
|
&& floorExitZoomOutAccumulatedRatio >= floorExitAccumulatedZoomOutRatio
|
||||||
|
const hasHeldCandidate = now - floorExitCandidateStartedAt >= floorExitCandidateHoldMs
|
||||||
|
|
||||||
|
if (!hasConfirmedScale || !hasHeldCandidate) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (now - floorExitThresholdExceededSince >= floorExitThresholdHoldMs) {
|
|
||||||
// 从单楼层自动切换到完整外围模型
|
|
||||||
isAutoSwitchLocked = true
|
isAutoSwitchLocked = true
|
||||||
lastAutoSwitchTime = now
|
lastAutoSwitchTime = now
|
||||||
floorExitThresholdExceededSince = 0
|
resetFloorExitCandidate()
|
||||||
floorExitZoomOutAccumulatedDistance = 0
|
|
||||||
|
|
||||||
void runAutoSwitchLoad(
|
void runAutoSwitchLoad(
|
||||||
{
|
{
|
||||||
@@ -1951,7 +2109,6 @@ const checkAutoSwitch = (options: { forceOverviewEntry?: boolean } = {}) => {
|
|||||||
'建筑外观模型加载失败'
|
'建筑外观模型加载失败'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
const runAutoSwitchLoad = async (
|
const runAutoSwitchLoad = async (
|
||||||
event: { from: 'overview' | 'floor'; to: 'overview' | 'floor'; trigger: 'zoom-in' | 'zoom-out'; distance: number },
|
event: { from: 'overview' | 'floor'; to: 'overview' | 'floor'; trigger: 'zoom-in' | 'zoom-out'; distance: number },
|
||||||
@@ -1974,7 +2131,7 @@ const runAutoSwitchLoad = async (
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (isStaleModelLoadError(error)) return
|
if (isStaleModelLoadError(error)) return
|
||||||
|
|
||||||
console.error('室内 3D 自动视角切换失败:', error)
|
console.error('馆内 3D 自动视角切换失败:', error)
|
||||||
loadError.value = true
|
loadError.value = true
|
||||||
isLoading.value = false
|
isLoading.value = false
|
||||||
setProgress(0, error instanceof Error ? error.message : fallbackMessage)
|
setProgress(0, error instanceof Error ? error.message : fallbackMessage)
|
||||||
@@ -1996,10 +2153,6 @@ const disableAutoSwitchTemporarily = (durationMs: number) => {
|
|||||||
}, durationMs)
|
}, durationMs)
|
||||||
}
|
}
|
||||||
|
|
||||||
const suppressAutoSwitchOnce = () => {
|
|
||||||
suppressNextAutoSwitchCheck = true
|
|
||||||
}
|
|
||||||
|
|
||||||
const setCameraView = (
|
const setCameraView = (
|
||||||
center: THREE.Vector3,
|
center: THREE.Vector3,
|
||||||
maxDim: number,
|
maxDim: number,
|
||||||
@@ -2093,22 +2246,16 @@ const zoomCamera = (direction: 'in' | 'out', options: { source?: ZoomCameraSourc
|
|||||||
|
|
||||||
if (!Number.isFinite(nextDistance) || nextDistance <= 0) return
|
if (!Number.isFinite(nextDistance) || nextDistance <= 0) return
|
||||||
|
|
||||||
const shouldSuppressAutoSwitch = options.source === 'button'
|
const shouldTrackUserZoom = options.source === 'button'
|
||||||
if (shouldSuppressAutoSwitch) {
|
|
||||||
suppressAutoSwitchOnce()
|
|
||||||
}
|
|
||||||
|
|
||||||
offset.setLength(nextDistance)
|
offset.setLength(nextDistance)
|
||||||
const nextPosition = controls.target.clone().add(offset)
|
const nextPosition = controls.target.clone().add(offset)
|
||||||
moveCameraTo(nextPosition, controls.target, {
|
moveCameraTo(nextPosition, controls.target, {
|
||||||
onComplete: shouldSuppressAutoSwitch
|
onComplete: handleControlChange
|
||||||
? () => {
|
|
||||||
suppressAutoSwitchOnce()
|
|
||||||
handleControlChange()
|
|
||||||
}
|
|
||||||
: handleControlChange
|
|
||||||
})
|
})
|
||||||
lastControlDistance = nextDistance
|
if (shouldTrackUserZoom) {
|
||||||
|
lastControlDistance = currentDistance
|
||||||
|
}
|
||||||
|
|
||||||
handleControlChange()
|
handleControlChange()
|
||||||
}
|
}
|
||||||
@@ -2166,9 +2313,10 @@ const attachCachedSharedModel = (
|
|||||||
modelUrl: string,
|
modelUrl: string,
|
||||||
name: string,
|
name: string,
|
||||||
loadToken: number,
|
loadToken: number,
|
||||||
floorId?: string
|
floorId?: string,
|
||||||
|
options: { allowAnyCachedOverview?: boolean } = {}
|
||||||
) => {
|
) => {
|
||||||
if (!cachedOverviewModel || cachedSharedModelUrl !== modelUrl || !scene) return false
|
if (!cachedOverviewModel || (!options.allowAnyCachedOverview && cachedSharedModelUrl !== modelUrl) || !scene) return false
|
||||||
|
|
||||||
const targetScene = scene
|
const targetScene = scene
|
||||||
activeModel = cachedOverviewModel
|
activeModel = cachedOverviewModel
|
||||||
@@ -2186,8 +2334,12 @@ const loadFloor = async (floorId: string, options: LoadFloorOptions = {}) => {
|
|||||||
if (!floor || !scene) return
|
if (!floor || !scene) return
|
||||||
|
|
||||||
const loadToken = startModelLoad()
|
const loadToken = startModelLoad()
|
||||||
const canUseCachedModel = canAttachCachedSharedModel(floor.modelUrl)
|
const canUseExactCachedModel = canAttachCachedSharedModel(floor.modelUrl)
|
||||||
const preserveCurrentSceneUntilReady = options.preserveCurrentSceneUntilReady && !canUseCachedModel
|
const canUseOverviewFallback = !canUseExactCachedModel
|
||||||
|
&& Boolean(options.allowOverviewFallback)
|
||||||
|
&& Boolean(options.preserveCurrentSceneUntilReady)
|
||||||
|
&& canAttachCachedOverviewModel()
|
||||||
|
const preserveCurrentSceneUntilReady = options.preserveCurrentSceneUntilReady && !canUseExactCachedModel
|
||||||
let nextModel: THREE.Object3D | null = null
|
let nextModel: THREE.Object3D | null = null
|
||||||
|
|
||||||
if (!preserveCurrentSceneUntilReady) {
|
if (!preserveCurrentSceneUntilReady) {
|
||||||
@@ -2196,15 +2348,23 @@ const loadFloor = async (floorId: string, options: LoadFloorOptions = {}) => {
|
|||||||
clearSceneData()
|
clearSceneData()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (canUseCachedModel) {
|
if (canUseExactCachedModel) {
|
||||||
activeView.value = 'floor'
|
activeView.value = 'floor'
|
||||||
currentFloor.value = floor.floorId
|
currentFloor.value = floor.floorId
|
||||||
attachCachedSharedModel(floor.modelUrl, `GuideFloorModel_${floor.floorId}`, loadToken, floor.floorId)
|
attachCachedSharedModel(
|
||||||
|
floor.modelUrl,
|
||||||
|
`GuideFloorModel_${floor.floorId}`,
|
||||||
|
loadToken,
|
||||||
|
floor.floorId
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
|
let gltf: GLTF | null = null
|
||||||
|
|
||||||
|
try {
|
||||||
if (!options.suppressProgress) {
|
if (!options.suppressProgress) {
|
||||||
setProgress(18, `正在加载 ${formatFloorLabel(floor.floorId)} 模型...`)
|
setProgress(18, `正在加载 ${formatFloorLabel(floor.floorId)} 模型...`)
|
||||||
}
|
}
|
||||||
const gltf = await loadModel(
|
gltf = await loadModel(
|
||||||
floor.modelUrl,
|
floor.modelUrl,
|
||||||
`正在加载 ${formatFloorLabel(floor.floorId)} 模型`,
|
`正在加载 ${formatFloorLabel(floor.floorId)} 模型`,
|
||||||
loadToken,
|
loadToken,
|
||||||
@@ -2216,7 +2376,24 @@ const loadFloor = async (floorId: string, options: LoadFloorOptions = {}) => {
|
|||||||
disposeObject(gltf.scene)
|
disposeObject(gltf.scene)
|
||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
|
} catch (error) {
|
||||||
|
if (isStaleModelLoadError(error) || !canUseOverviewFallback) {
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
|
||||||
|
activeView.value = 'floor'
|
||||||
|
currentFloor.value = floor.floorId
|
||||||
|
clearSceneData()
|
||||||
|
attachCachedSharedModel(
|
||||||
|
floor.modelUrl,
|
||||||
|
`GuideFloorModel_${floor.floorId}`,
|
||||||
|
loadToken,
|
||||||
|
floor.floorId,
|
||||||
|
{ allowAnyCachedOverview: true }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (gltf) {
|
||||||
const targetScene = scene
|
const targetScene = scene
|
||||||
if (!targetScene) {
|
if (!targetScene) {
|
||||||
disposeObject(gltf.scene)
|
disposeObject(gltf.scene)
|
||||||
@@ -2251,8 +2428,10 @@ const loadFloor = async (floorId: string, options: LoadFloorOptions = {}) => {
|
|||||||
}
|
}
|
||||||
targetScene.add(activeModel)
|
targetScene.add(activeModel)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!activeModel) throw createStaleModelLoadError()
|
if (!activeModel) throw createStaleModelLoadError()
|
||||||
|
hasLoadedFloorViewOnce = true
|
||||||
fitCameraToObject(activeModel)
|
fitCameraToObject(activeModel)
|
||||||
markFloorAutoSwitchEntry()
|
markFloorAutoSwitchEntry()
|
||||||
|
|
||||||
@@ -3142,7 +3321,7 @@ const queueTargetFocus = (request: TargetPoiFocusRequest | null) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const loadModelPackage = async () => {
|
const loadModelPackage = async () => {
|
||||||
setProgress(8, '正在读取室内导览资源...')
|
setProgress(8, '正在读取馆内导览资源...')
|
||||||
const packageData = await props.modelSource.loadPackage()
|
const packageData = await props.modelSource.loadPackage()
|
||||||
renderPackage.value = packageData
|
renderPackage.value = packageData
|
||||||
|
|
||||||
@@ -3176,18 +3355,19 @@ const init3DScene = async () => {
|
|||||||
isLoading.value = true
|
isLoading.value = true
|
||||||
loadError.value = false
|
loadError.value = false
|
||||||
selectedPOI.value = null
|
selectedPOI.value = null
|
||||||
|
hasLoadedFloorViewOnce = false
|
||||||
setProgress(0, '正在初始化三维场景...')
|
setProgress(0, '正在初始化三维场景...')
|
||||||
|
|
||||||
await initThree()
|
await initThree()
|
||||||
await loadModelPackage()
|
await loadModelPackage()
|
||||||
|
|
||||||
setProgress(100, '室内三维模型加载完成')
|
setProgress(100, '馆内三维模型加载完成')
|
||||||
isLoading.value = false
|
isLoading.value = false
|
||||||
queueTargetFocus(pendingTargetFocus || props.targetFocus || null)
|
queueTargetFocus(pendingTargetFocus || props.targetFocus || null)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (isStaleModelLoadError(error)) return
|
if (isStaleModelLoadError(error)) return
|
||||||
|
|
||||||
console.error('室内 3D 模型资源加载失败:', error)
|
console.error('馆内 3D 模型资源加载失败:', error)
|
||||||
loadError.value = true
|
loadError.value = true
|
||||||
isLoading.value = false
|
isLoading.value = false
|
||||||
setProgress(0, error instanceof Error ? error.message : '请检查模型资源或网络状态后重试')
|
setProgress(0, error instanceof Error ? error.message : '请检查模型资源或网络状态后重试')
|
||||||
@@ -3269,7 +3449,7 @@ const disposeScene = () => {
|
|||||||
const container = getContainerElement()
|
const container = getContainerElement()
|
||||||
container?.removeEventListener('pointerdown', handlePointerDown)
|
container?.removeEventListener('pointerdown', handlePointerDown)
|
||||||
container?.removeEventListener('pointerup', handlePointerUp)
|
container?.removeEventListener('pointerup', handlePointerUp)
|
||||||
container?.removeEventListener('wheel', handleWheelIntent)
|
renderer?.domElement.removeEventListener('wheel', handleWheelIntent, true)
|
||||||
|
|
||||||
resizeObserver?.disconnect()
|
resizeObserver?.disconnect()
|
||||||
resizeObserver = null
|
resizeObserver = null
|
||||||
@@ -3277,6 +3457,7 @@ const disposeScene = () => {
|
|||||||
// 清理自动切换相关资源
|
// 清理自动切换相关资源
|
||||||
if (controls) {
|
if (controls) {
|
||||||
controls.removeEventListener('start', handleControlStart)
|
controls.removeEventListener('start', handleControlStart)
|
||||||
|
controls.removeEventListener('end', handleControlEnd)
|
||||||
controls.removeEventListener('change', handleControlChange)
|
controls.removeEventListener('change', handleControlChange)
|
||||||
controls.dispose()
|
controls.dispose()
|
||||||
}
|
}
|
||||||
@@ -3292,10 +3473,11 @@ const disposeScene = () => {
|
|||||||
cameraTween = null
|
cameraTween = null
|
||||||
isProgrammaticCameraChange = false
|
isProgrammaticCameraChange = false
|
||||||
lastControlDistance = 0
|
lastControlDistance = 0
|
||||||
suppressNextAutoSwitchCheck = false
|
activeControlGestureStartedAt = 0
|
||||||
floorAutoSwitchProtectedUntil = 0
|
indoorEntryGestureLocked = false
|
||||||
floorExitThresholdExceededSince = 0
|
indoorContextLockedUntil = 0
|
||||||
floorExitZoomOutAccumulatedDistance = 0
|
resetOverviewEntryIntent()
|
||||||
|
resetFloorExitCandidate()
|
||||||
|
|
||||||
clearSceneData()
|
clearSceneData()
|
||||||
disposePoiMarkerCache()
|
disposePoiMarkerCache()
|
||||||
@@ -3393,7 +3575,7 @@ onUnmounted(() => {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
background: #f1f3ee;
|
background: #f7f8f4;
|
||||||
}
|
}
|
||||||
|
|
||||||
.three-canvas-wrapper {
|
.three-canvas-wrapper {
|
||||||
@@ -3416,7 +3598,7 @@ onUnmounted(() => {
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
background: rgba(241, 243, 238, 0.9);
|
background: rgba(247, 248, 244, 0.9);
|
||||||
z-index: 20;
|
z-index: 20;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user