@@ -133,6 +133,14 @@ const SGS_VISUAL_RENDER_CONFIG = {
|
||||
}
|
||||
} as const
|
||||
|
||||
const OVERVIEW_INITIAL_CAMERA_PARAMS = {
|
||||
position: new THREE.Vector3(496.3356, 708.6058, 137.501),
|
||||
target: new THREE.Vector3(-4.1432, -31.3659, 3.3982),
|
||||
zoom: 1
|
||||
} as const
|
||||
|
||||
const MODEL_ADJUST_IDLE_REPORT_DELAY_MS = 800
|
||||
|
||||
const degToRad = (degrees: number) => THREE.MathUtils.degToRad(degrees)
|
||||
|
||||
const INTERACTION_POLICY = {
|
||||
@@ -347,14 +355,14 @@ const isLoading = ref(true)
|
||||
const loadError = ref(false)
|
||||
const loadingProgress = ref(0)
|
||||
const loadingMessage = ref('正在读取馆内导览资源...')
|
||||
const activeView = ref<ViewMode>(props.initialView)
|
||||
const currentFloor = ref(props.initialFloorId)
|
||||
const loadingDisplayMessage = '正在加载馆内三维场景'
|
||||
const modelLoadErrorTitle = '馆内三维场景加载失败'
|
||||
const modelLoadErrorMessage = '可能是网络原因,请重新加载。'
|
||||
const setFriendlyModelLoadError = () => {
|
||||
setProgress(0, modelLoadErrorMessage)
|
||||
}
|
||||
const activeView = ref<ViewMode>(props.initialView)
|
||||
const currentFloor = ref(props.initialFloorId)
|
||||
const selectedPOI = ref<RenderPoi | null>(null)
|
||||
const activeFocusPoiId = ref('')
|
||||
const floorIndex = ref<FloorIndexItem[]>([])
|
||||
@@ -428,6 +436,8 @@ let floorExitZoomOutAccumulatedRatio = 0
|
||||
let autoSwitchTemporarilyDisabled = false
|
||||
let autoSwitchDisableTimer: ReturnType<typeof setTimeout> | null = null
|
||||
let overviewEntryIntentStartedAt = 0
|
||||
let modelAdjustReportTimer: ReturnType<typeof setTimeout> | null = null
|
||||
let hasPendingManualModelAdjustment = false
|
||||
let overviewEntryStartDistance = 0
|
||||
let overviewEntryZoomInRounds = 0
|
||||
let overviewWheelZoomInRounds = 0
|
||||
@@ -539,6 +549,57 @@ const logThreeMapDiagnostic = (
|
||||
console.debug(`[ThreeMap] ${event} ${stringifyDiagnosticPayload(payload)}`)
|
||||
}
|
||||
|
||||
const roundModelAdjustValue = (value: number) => Number(value.toFixed(4))
|
||||
|
||||
const serializeVector3 = (value: THREE.Vector3) => ({
|
||||
x: roundModelAdjustValue(value.x),
|
||||
y: roundModelAdjustValue(value.y),
|
||||
z: roundModelAdjustValue(value.z)
|
||||
})
|
||||
|
||||
const serializeEulerDegrees = (value: THREE.Euler) => ({
|
||||
x: roundModelAdjustValue(THREE.MathUtils.radToDeg(value.x)),
|
||||
y: roundModelAdjustValue(THREE.MathUtils.radToDeg(value.y)),
|
||||
z: roundModelAdjustValue(THREE.MathUtils.radToDeg(value.z))
|
||||
})
|
||||
|
||||
const clearModelAdjustReportTimer = () => {
|
||||
if (!modelAdjustReportTimer) return
|
||||
clearTimeout(modelAdjustReportTimer)
|
||||
modelAdjustReportTimer = null
|
||||
}
|
||||
|
||||
const emitModelAdjustIdleReport = () => {
|
||||
modelAdjustReportTimer = null
|
||||
if (!hasPendingManualModelAdjustment || !camera || !controls || !activeModel) return
|
||||
|
||||
hasPendingManualModelAdjustment = false
|
||||
const payload = {
|
||||
view: activeView.value,
|
||||
floorId: currentFloor.value,
|
||||
modelName: activeModel.name || 'unnamed-model',
|
||||
model: {
|
||||
position: serializeVector3(activeModel.position),
|
||||
rotationDeg: serializeEulerDegrees(activeModel.rotation),
|
||||
scale: serializeVector3(activeModel.scale)
|
||||
},
|
||||
camera: {
|
||||
position: serializeVector3(camera.position),
|
||||
target: serializeVector3(controls.target),
|
||||
distance: roundModelAdjustValue(controls.getDistance()),
|
||||
zoom: roundModelAdjustValue(camera.zoom)
|
||||
}
|
||||
}
|
||||
|
||||
console.info('[ThreeMap] 模型停止移动,当前调参信息:', payload)
|
||||
}
|
||||
|
||||
const scheduleModelAdjustIdleReport = () => {
|
||||
if (!hasPendingManualModelAdjustment || isProgrammaticCameraChange || !camera || !controls || !activeModel) return
|
||||
clearModelAdjustReportTimer()
|
||||
modelAdjustReportTimer = window.setTimeout(emitModelAdjustIdleReport, MODEL_ADJUST_IDLE_REPORT_DELAY_MS)
|
||||
}
|
||||
|
||||
const getInteractionPolicyMode = (): InteractionPolicyMode => (
|
||||
activeView.value === 'overview' ? 'display' : 'explore'
|
||||
)
|
||||
@@ -572,11 +633,12 @@ const shouldEnableRotation = () => (
|
||||
|
||||
const shouldUseDirectMouseRotate = (event?: PointerEvent) => {
|
||||
const hasAlt = event?.altKey ?? desktopRotateModifiers.alt
|
||||
const hasShiftCtrlOrMeta = event
|
||||
? event.shiftKey || event.ctrlKey || event.metaKey
|
||||
: desktopRotateModifiers.shift || desktopRotateModifiers.ctrl || desktopRotateModifiers.meta
|
||||
const hasCtrlOrMeta = event
|
||||
? event.ctrlKey || event.metaKey
|
||||
: desktopRotateModifiers.ctrl || desktopRotateModifiers.meta
|
||||
const hasShift = event?.shiftKey ?? desktopRotateModifiers.shift
|
||||
|
||||
return hasAlt && !hasShiftCtrlOrMeta
|
||||
return (hasCtrlOrMeta || hasAlt) && !hasShift
|
||||
}
|
||||
|
||||
const resetInteractionGateState = () => {
|
||||
@@ -600,6 +662,7 @@ const syncControlInteractionOptions = (event?: PointerEvent) => {
|
||||
const policy = INTERACTION_POLICY[getInteractionPolicyMode()]
|
||||
const enableRotate = shouldEnableRotation()
|
||||
const enablePan = policy.allowPan && !enableRotate
|
||||
const directMouseRotate = enableRotate && shouldUseDirectMouseRotate(event)
|
||||
|
||||
controls.enableRotate = enableRotate
|
||||
controls.enablePan = enablePan
|
||||
@@ -612,14 +675,18 @@ const syncControlInteractionOptions = (event?: PointerEvent) => {
|
||||
controls.maxAzimuthAngle = policy.maxAzimuthAngle
|
||||
|
||||
controls.mouseButtons.LEFT = enableRotate
|
||||
? shouldUseDirectMouseRotate(event)
|
||||
? directMouseRotate
|
||||
? THREE.MOUSE.ROTATE
|
||||
: THREE.MOUSE.PAN
|
||||
: enablePan
|
||||
? THREE.MOUSE.PAN
|
||||
: null
|
||||
controls.mouseButtons.MIDDLE = THREE.MOUSE.DOLLY
|
||||
controls.mouseButtons.RIGHT = enablePan ? THREE.MOUSE.PAN : null
|
||||
controls.mouseButtons.RIGHT = directMouseRotate
|
||||
? THREE.MOUSE.ROTATE
|
||||
: enablePan
|
||||
? THREE.MOUSE.PAN
|
||||
: null
|
||||
controls.touches.ONE = enablePan ? THREE.TOUCH.PAN : null
|
||||
controls.touches.TWO = THREE.TOUCH.DOLLY_ROTATE
|
||||
}
|
||||
@@ -1437,6 +1504,7 @@ const refreshPoiVisibilityByDistance = () => {
|
||||
const handleControlChange = () => {
|
||||
updatePoiVisibilityByDistance()
|
||||
checkAutoSwitch()
|
||||
scheduleModelAdjustIdleReport()
|
||||
}
|
||||
|
||||
const clearProgrammaticCameraTimer = () => {
|
||||
@@ -1567,6 +1635,10 @@ const updatePoiTapFeedback = (now: number) => {
|
||||
|
||||
const handleControlStart = () => {
|
||||
activeControlGestureStartedAt = Date.now()
|
||||
if (!isProgrammaticCameraChange) {
|
||||
hasPendingManualModelAdjustment = true
|
||||
clearModelAdjustReportTimer()
|
||||
}
|
||||
cancelCameraTween({ manual: true })
|
||||
}
|
||||
|
||||
@@ -1577,6 +1649,7 @@ const handleControlEnd = () => {
|
||||
}
|
||||
activeControlGestureStartedAt = 0
|
||||
updateLastControlDistance()
|
||||
scheduleModelAdjustIdleReport()
|
||||
}
|
||||
|
||||
const setProgress = (progress: number, message: string) => {
|
||||
@@ -3375,6 +3448,21 @@ const fitCameraToObject = (object: THREE.Object3D, preset: CameraPreset = 'obliq
|
||||
setCameraView(center, maxDim, direction, overviewFitOptions)
|
||||
}
|
||||
|
||||
const applyOverviewInitialCamera = () => {
|
||||
if (!camera || !controls) return
|
||||
|
||||
camera.position.copy(OVERVIEW_INITIAL_CAMERA_PARAMS.position)
|
||||
camera.zoom = OVERVIEW_INITIAL_CAMERA_PARAMS.zoom
|
||||
camera.updateProjectionMatrix()
|
||||
controls.target.copy(OVERVIEW_INITIAL_CAMERA_PARAMS.target)
|
||||
controls.update()
|
||||
|
||||
const distance = camera.position.distanceTo(controls.target)
|
||||
if (Number.isFinite(distance)) {
|
||||
lastControlDistance = distance
|
||||
}
|
||||
}
|
||||
|
||||
const resetCamera = () => {
|
||||
if (activeModel) {
|
||||
fitCameraToObject(activeModel)
|
||||
@@ -3492,7 +3580,7 @@ const loadOverview = async () => {
|
||||
applyModelVisibilityForView(activeModel, 'overview')
|
||||
cacheAutoSwitchOverviewMaxDim(activeModel)
|
||||
targetScene.add(activeModel)
|
||||
fitCameraToObject(activeModel)
|
||||
applyOverviewInitialCamera()
|
||||
loadCurrentFloorPoiMarkersInBackground(loadToken)
|
||||
resetAutoSwitchDistanceTracking()
|
||||
refreshPoiVisibilityByDistance()
|
||||
@@ -3523,7 +3611,7 @@ const loadOverview = async () => {
|
||||
cachedOverviewModel = activeModel
|
||||
cachedSharedModelUrl = packageData.overviewModelUrl
|
||||
targetScene.add(activeModel)
|
||||
fitCameraToObject(activeModel)
|
||||
applyOverviewInitialCamera()
|
||||
loadCurrentFloorPoiMarkersInBackground(loadToken)
|
||||
resetAutoSwitchDistanceTracking()
|
||||
refreshPoiVisibilityByDistance()
|
||||
@@ -5090,6 +5178,8 @@ const disposeScene = () => {
|
||||
|
||||
clearOverviewWheelAutoSwitchTimer()
|
||||
clearProgrammaticCameraTimer()
|
||||
clearModelAdjustReportTimer()
|
||||
hasPendingManualModelAdjustment = false
|
||||
cameraTween = null
|
||||
isProgrammaticCameraChange = false
|
||||
lastControlDistance = 0
|
||||
|
||||
Reference in New Issue
Block a user