From 3a8afa8d930fce818b1899c1a2b3726d143166ad Mon Sep 17 00:00:00 2001 From: lyf <2514544224@qq.com> Date: Fri, 10 Jul 2026 13:09:04 +0800 Subject: [PATCH] =?UTF-8?q?=E8=B0=83=E6=95=B4=E6=A8=A1=E5=9E=8B=E4=BD=8D?= =?UTF-8?q?=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/map/ThreeMap.vue | 110 +++++++++++++++++++++++++++++--- 1 file changed, 100 insertions(+), 10 deletions(-) diff --git a/src/components/map/ThreeMap.vue b/src/components/map/ThreeMap.vue index b9c2e71..d8ce738 100644 --- a/src/components/map/ThreeMap.vue +++ b/src/components/map/ThreeMap.vue @@ -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(props.initialView) -const currentFloor = ref(props.initialFloorId) const loadingDisplayMessage = '正在加载馆内三维场景' const modelLoadErrorTitle = '馆内三维场景加载失败' const modelLoadErrorMessage = '可能是网络原因,请重新加载。' const setFriendlyModelLoadError = () => { setProgress(0, modelLoadErrorMessage) } +const activeView = ref(props.initialView) +const currentFloor = ref(props.initialFloorId) const selectedPOI = ref(null) const activeFocusPoiId = ref('') const floorIndex = ref([]) @@ -428,6 +436,8 @@ let floorExitZoomOutAccumulatedRatio = 0 let autoSwitchTemporarilyDisabled = false let autoSwitchDisableTimer: ReturnType | null = null let overviewEntryIntentStartedAt = 0 +let modelAdjustReportTimer: ReturnType | 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