This commit is contained in:
@@ -164,6 +164,10 @@ import {
|
||||
createGuideObliqueReturnPose,
|
||||
createGuideTopViewPose
|
||||
} from '@/domain/guideTopView'
|
||||
import {
|
||||
hasReachedOutdoorExitBoundary,
|
||||
OUTDOOR_OUTWARD_INTENT_GRACE_MS
|
||||
} from '@/domain/guideSemanticZoom'
|
||||
import {
|
||||
getOverviewMapLabelDefinition,
|
||||
isOverviewMapLabelMatch,
|
||||
@@ -193,6 +197,7 @@ import {
|
||||
type PoiVisibilityTier
|
||||
} from '@/domain/poiDisplay'
|
||||
import {
|
||||
ensurePoiIconSprite,
|
||||
getPoiIconHref,
|
||||
resolvePoiIconKey,
|
||||
type PoiIconKey
|
||||
@@ -306,7 +311,9 @@ const SGS_VISUAL_RENDER_CONFIG = {
|
||||
framing: {
|
||||
overviewScreenOffsetRatio: new THREE.Vector2(-0.045, -0.095),
|
||||
overviewReferenceVerticalOffset: 44,
|
||||
overviewMaxDistanceFactor: 1.35,
|
||||
// Keep two to three zoom-out levels of the 3D building overview before
|
||||
// the semantic boundary hands the visitor to the Tencent outdoor map.
|
||||
overviewMaxDistanceFactor: 1.85,
|
||||
// The left floor rail and right zoom tools make the geometric center look
|
||||
// left-biased. Shift the floor framing right into the visible map area.
|
||||
floorScreenOffsetRatio: new THREE.Vector2(-0.05, -0.055)
|
||||
@@ -712,6 +719,7 @@ const props = withDefaults(defineProps<{
|
||||
autoSwitch?: boolean
|
||||
disableAutoExit?: boolean
|
||||
autoSwitchCooldown?: number
|
||||
semanticZoomEnabled?: boolean
|
||||
}>(), {
|
||||
assetBaseUrl: '',
|
||||
activeFloor: '',
|
||||
@@ -735,7 +743,8 @@ const props = withDefaults(defineProps<{
|
||||
sceneViewport: null,
|
||||
autoSwitch: true,
|
||||
disableAutoExit: false,
|
||||
autoSwitchCooldown: 1500
|
||||
autoSwitchCooldown: 1500,
|
||||
semanticZoomEnabled: false
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
@@ -760,6 +769,7 @@ const emit = defineEmits<{
|
||||
sceneRevision: number
|
||||
}]
|
||||
autoSwitchBlocked: [reason: { reason: 'loading-locked' | 'cooldown' | 'disabled' }]
|
||||
semanticExteriorExit: []
|
||||
initialModelProgress: [event: InitialModelProgressEvent]
|
||||
initialModelReady: [event: { view: ViewMode; floorId?: string; elapsedMs?: number }]
|
||||
initialModelFailed: [event: {
|
||||
@@ -983,6 +993,9 @@ let hasPendingManualModelAdjustment = false
|
||||
let activeAutoSwitchInputSource: GuideAutoSwitchInputSource = 'gesture'
|
||||
let isProgrammaticCameraChange = false
|
||||
let hasActiveUserCameraGesture = false
|
||||
let overviewGestureStartDistance = 0
|
||||
let semanticOutwardZoomIntentAt = 0
|
||||
let semanticExteriorExitRequested = false
|
||||
let programmaticCameraTimer: ReturnType<typeof setTimeout> | null = null
|
||||
let cameraTween: CameraTweenState | null = null
|
||||
let buttonAutoSwitchTimer: ReturnType<typeof setTimeout> | null = null
|
||||
@@ -2572,6 +2585,7 @@ const handleControlStart = () => {
|
||||
const distance = controls?.getDistance()
|
||||
if (!isProgrammaticCameraChange && typeof distance === 'number' && Number.isFinite(distance)) {
|
||||
hasActiveUserCameraGesture = true
|
||||
overviewGestureStartDistance = activeView.value === 'overview' ? distance : 0
|
||||
autoSwitchStateMachine.beginInput(distance, activeAutoSwitchInputSource)
|
||||
}
|
||||
if (!isProgrammaticCameraChange) {
|
||||
@@ -2582,7 +2596,9 @@ const handleControlStart = () => {
|
||||
}
|
||||
|
||||
const handleControlEnd = () => {
|
||||
requestSemanticExteriorExitFromGesture()
|
||||
hasActiveUserCameraGesture = false
|
||||
overviewGestureStartDistance = 0
|
||||
activeAutoSwitchInputSource = 'gesture'
|
||||
scheduleModelAdjustIdleReport()
|
||||
}
|
||||
@@ -2797,6 +2813,7 @@ const waitForContainer = async () => {
|
||||
}
|
||||
|
||||
const initThree = async () => {
|
||||
ensurePoiIconSprite()
|
||||
const container = await waitForContainer()
|
||||
if (!container) {
|
||||
throw new Error('3D 容器未就绪')
|
||||
@@ -5144,9 +5161,46 @@ const handleWheelIntent = (event: WheelEvent) => {
|
||||
if (!Number.isFinite(distance)) return
|
||||
|
||||
activeAutoSwitchInputSource = 'wheel'
|
||||
if (event.deltaY > 0) semanticOutwardZoomIntentAt = Date.now()
|
||||
autoSwitchStateMachine.beginInput(distance, 'wheel')
|
||||
}
|
||||
|
||||
const canRequestSemanticExteriorExit = () => (
|
||||
props.semanticZoomEnabled
|
||||
&& !semanticExteriorExitRequested
|
||||
&& !weakNetworkFallbackActive.value
|
||||
&& !isLoading.value
|
||||
&& !isProgrammaticCameraChange
|
||||
&& Boolean(controls)
|
||||
&& activeView.value === 'overview'
|
||||
&& !props.showRoute
|
||||
&& !props.routeNavigationActive
|
||||
&& !props.routeStartSelectionActive
|
||||
&& !props.targetFocus
|
||||
&& !selectedPOI.value
|
||||
)
|
||||
|
||||
const requestSemanticExteriorExit = () => {
|
||||
if (!canRequestSemanticExteriorExit()) return false
|
||||
semanticExteriorExitRequested = true
|
||||
emit('semanticExteriorExit')
|
||||
return true
|
||||
}
|
||||
|
||||
const requestSemanticExteriorExitFromGesture = () => {
|
||||
if (!canRequestSemanticExteriorExit() || !controls || !overviewGestureStartDistance) return
|
||||
|
||||
const currentDistance = controls.getDistance()
|
||||
const maxDistance = controls.maxDistance
|
||||
const outwardGesture = currentDistance > overviewGestureStartDistance * 1.025
|
||||
const atExteriorBoundary = hasReachedOutdoorExitBoundary(currentDistance, maxDistance)
|
||||
const recentWheelIntent = Date.now() - semanticOutwardZoomIntentAt < OUTDOOR_OUTWARD_INTENT_GRACE_MS
|
||||
|
||||
if (atExteriorBoundary && (outwardGesture || recentWheelIntent)) {
|
||||
requestSemanticExteriorExit()
|
||||
}
|
||||
}
|
||||
|
||||
const canRunAutoSwitch = (direction: GuideAutoSwitchDirection) => {
|
||||
if (
|
||||
!props.autoSwitch
|
||||
@@ -6262,15 +6316,24 @@ const handleWeakNetworkZoomIntent = async (event: {
|
||||
boundaryAttempt: boolean
|
||||
referenceVisibleWorldSpan: number | null
|
||||
thresholdReached: boolean
|
||||
outdoorExitReached: boolean
|
||||
viewport: GuideViewportState
|
||||
}) => {
|
||||
if (
|
||||
!weakNetworkFallbackActive.value
|
||||
|| isLoading.value
|
||||
|| !props.autoSwitch
|
||||
|| weakNetworkSceneTransitionInFlight
|
||||
) return
|
||||
const from = activeView.value
|
||||
|
||||
if (
|
||||
from === 'overview'
|
||||
&& event.direction === 'out'
|
||||
&& event.outdoorExitReached
|
||||
&& requestWeakNetworkSemanticExteriorExit()
|
||||
) return
|
||||
|
||||
if (!props.autoSwitch) return
|
||||
const shouldEnterFloor = from === 'overview' && event.direction === 'in'
|
||||
const shouldExitOverview = from === 'floor' && event.direction === 'out'
|
||||
if (!shouldEnterFloor && !shouldExitOverview) return
|
||||
@@ -6340,6 +6403,26 @@ const handleWeakNetworkZoomIntent = async (event: {
|
||||
}
|
||||
}
|
||||
|
||||
const canRequestWeakNetworkSemanticExteriorExit = () => (
|
||||
props.semanticZoomEnabled
|
||||
&& !semanticExteriorExitRequested
|
||||
&& weakNetworkFallbackActive.value
|
||||
&& !isLoading.value
|
||||
&& activeView.value === 'overview'
|
||||
&& !props.showRoute
|
||||
&& !props.routeNavigationActive
|
||||
&& !props.routeStartSelectionActive
|
||||
&& !props.targetFocus
|
||||
&& !selectedPOI.value
|
||||
)
|
||||
|
||||
const requestWeakNetworkSemanticExteriorExit = () => {
|
||||
if (!canRequestWeakNetworkSemanticExteriorExit()) return false
|
||||
semanticExteriorExitRequested = true
|
||||
emit('semanticExteriorExit')
|
||||
return true
|
||||
}
|
||||
|
||||
const zoomCamera = (direction: 'in' | 'out', options: { source?: ZoomCameraSource } = {}) => {
|
||||
if (weakNetworkFallbackActive.value) {
|
||||
const source = options.source || 'button'
|
||||
@@ -6369,6 +6452,13 @@ const zoomCamera = (direction: 'in' | 'out', options: { source?: ZoomCameraSourc
|
||||
|
||||
if (!Number.isFinite(nextDistance) || nextDistance <= 0) return
|
||||
|
||||
if (
|
||||
direction === 'out'
|
||||
&& activeView.value === 'overview'
|
||||
&& hasReachedOutdoorExitBoundary(nextDistance, maxDistance)
|
||||
&& requestSemanticExteriorExit()
|
||||
) return
|
||||
|
||||
const shouldTrackUserZoom = options.source === 'button'
|
||||
if (shouldTrackUserZoom) {
|
||||
trackButtonZoomForAutoSwitch(currentDistance, nextDistance)
|
||||
@@ -7115,7 +7205,9 @@ const createPoiDomLabelIcon = (iconKey: PoiIconKey) => {
|
||||
svg.setAttribute('data-poi-icon', iconKey)
|
||||
|
||||
const use = document.createElementNS('http://www.w3.org/2000/svg', 'use')
|
||||
use.setAttribute('href', getPoiIconHref(iconKey))
|
||||
const iconHref = getPoiIconHref(iconKey)
|
||||
use.setAttribute('href', iconHref)
|
||||
use.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', iconHref)
|
||||
svg.appendChild(use)
|
||||
return svg
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user