调整三维模型交互门控
Some checks failed
CI / verify (push) Has been cancelled

This commit is contained in:
lyf
2026-07-09 01:41:03 +08:00
parent 6a0fa61c8e
commit a5af007b20

View File

@@ -74,6 +74,7 @@ type ViewMode = 'overview' | 'floor' | 'multi'
type CameraPreset = 'top' | 'oblique'
type TouchGestureMode = 'orbit' | 'pan'
type ZoomCameraSource = 'button' | 'gesture'
type InteractionPolicyMode = 'display' | 'explore'
interface InitialModelProgressEvent {
progress: number
@@ -132,6 +133,34 @@ const SGS_VISUAL_RENDER_CONFIG = {
}
} as const
const degToRad = (degrees: number) => THREE.MathUtils.degToRad(degrees)
const INTERACTION_POLICY = {
display: {
minPolarAngle: degToRad(35),
maxPolarAngle: degToRad(76),
minAzimuthAngle: degToRad(-75),
maxAzimuthAngle: degToRad(75),
allowPan: true,
allowZoom: true
},
explore: {
minPolarAngle: degToRad(30),
maxPolarAngle: degToRad(78),
minAzimuthAngle: degToRad(-90),
maxAzimuthAngle: degToRad(90),
allowPan: true,
allowZoom: true
}
} as const satisfies Record<InteractionPolicyMode, {
minPolarAngle: number
maxPolarAngle: number
minAzimuthAngle: number
maxAzimuthAngle: number
allowPan: boolean
allowZoom: boolean
}>
interface CameraFitOptions {
distanceFactor?: number
targetOffsetRatio?: THREE.Vector3
@@ -366,6 +395,16 @@ let floorSwitchRequestedFloorId = ''
let isFloorSwitching = false
let hasLoadedFloorViewOnce = false
let pointerDownState: { x: number; y: number } | null = null
const activePointers = new Set<number>()
let activePointerTypes = new Map<number, string>()
let hadMultiPointerGesture = false
let isDesktopRotateModifierActive = false
let desktopRotateModifiers = {
shift: false,
ctrl: false,
meta: false,
alt: false
}
let cachedOverviewModel: THREE.Object3D | null = null
let cachedSharedModelUrl = ''
let autoSwitchOverviewMaxDim = 0
@@ -494,18 +533,89 @@ const logThreeMapDiagnostic = (
console.debug(`[ThreeMap] ${event} ${stringifyDiagnosticPayload(payload)}`)
}
const syncControlInteractionOptions = () => {
const getInteractionPolicyMode = (): InteractionPolicyMode => (
activeView.value === 'overview' ? 'display' : 'explore'
)
const getActiveTouchPointerCount = () => (
[...activePointerTypes.values()].filter((pointerType) => pointerType === 'touch').length
)
const updateDesktopRotateModifierState = (event?: KeyboardEvent | PointerEvent) => {
if (event) {
desktopRotateModifiers = {
shift: event.shiftKey,
ctrl: event.ctrlKey,
meta: event.metaKey,
alt: event.altKey
}
}
isDesktopRotateModifierActive = (
desktopRotateModifiers.shift
|| desktopRotateModifiers.ctrl
|| desktopRotateModifiers.meta
|| desktopRotateModifiers.alt
)
}
const shouldEnableRotation = () => (
getActiveTouchPointerCount() >= 2
|| isDesktopRotateModifierActive
)
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
return hasAlt && !hasShiftCtrlOrMeta
}
const resetInteractionGateState = () => {
activePointers.clear()
activePointerTypes.clear()
hadMultiPointerGesture = false
pointerDownState = null
desktopRotateModifiers = {
shift: false,
ctrl: false,
meta: false,
alt: false
}
isDesktopRotateModifierActive = false
syncControlInteractionOptions()
}
const syncControlInteractionOptions = (event?: PointerEvent) => {
if (!controls) return
controls.enableRotate = false
controls.enablePan = true
const policy = INTERACTION_POLICY[getInteractionPolicyMode()]
const enableRotate = shouldEnableRotation()
const enablePan = policy.allowPan && !enableRotate
controls.enableRotate = enableRotate
controls.enablePan = enablePan
controls.enableZoom = policy.allowZoom
controls.screenSpacePanning = true
controls.panSpeed = 0.85
controls.mouseButtons.LEFT = THREE.MOUSE.PAN
controls.minPolarAngle = policy.minPolarAngle
controls.maxPolarAngle = policy.maxPolarAngle
controls.minAzimuthAngle = policy.minAzimuthAngle
controls.maxAzimuthAngle = policy.maxAzimuthAngle
controls.mouseButtons.LEFT = enableRotate
? shouldUseDirectMouseRotate(event)
? THREE.MOUSE.ROTATE
: THREE.MOUSE.PAN
: enablePan
? THREE.MOUSE.PAN
: null
controls.mouseButtons.MIDDLE = THREE.MOUSE.DOLLY
controls.mouseButtons.RIGHT = THREE.MOUSE.PAN
controls.touches.ONE = THREE.TOUCH.PAN
controls.touches.TWO = THREE.TOUCH.DOLLY_PAN
controls.mouseButtons.RIGHT = enablePan ? THREE.MOUSE.PAN : null
controls.touches.ONE = enablePan ? THREE.TOUCH.PAN : null
controls.touches.TWO = THREE.TOUCH.DOLLY_ROTATE
}
const corePoiCategories = new Set([
@@ -1733,8 +1843,14 @@ const initThree = async () => {
resizeObserver = new ResizeObserver(handleResize)
resizeObserver.observe(container)
container.addEventListener('pointerdown', handlePointerDown)
container.addEventListener('pointerup', handlePointerUp)
container.addEventListener('pointerdown', handlePointerDown, true)
container.addEventListener('pointerup', handlePointerUp, true)
container.addEventListener('pointercancel', handlePointerCancel, true)
container.addEventListener('pointerleave', handlePointerLeave, true)
window.addEventListener('keydown', handleKeyDown)
window.addEventListener('keyup', handleKeyUp)
window.addEventListener('blur', handleWindowBlur)
document.addEventListener('visibilitychange', handleVisibilityChange)
renderer.domElement.addEventListener('wheel', handleWheelIntent, { passive: true, capture: true })
// 监听控制器变化,用于自动切换和点位可见层级更新
@@ -3361,6 +3477,7 @@ const loadOverview = async () => {
const loadToken = startModelLoad()
activeView.value = 'overview'
syncControlInteractionOptions()
clearSceneData()
if (cachedOverviewModel) {
@@ -3506,6 +3623,7 @@ const commitPreparedFloorScene = (
}
activeView.value = 'floor'
syncControlInteractionOptions()
currentFloor.value = expectedFloorId
clearSceneData()
activeModel = prepared.model
@@ -3588,6 +3706,7 @@ const loadMultiFloor = async () => {
const loadToken = startModelLoad()
activeView.value = 'multi'
syncControlInteractionOptions()
clearSceneData()
setProgress(18, '正在加载多层展示模型...')
@@ -4550,6 +4669,22 @@ const handleSceneTap = async (event: PointerEvent) => {
}
const handlePointerDown = (event: PointerEvent) => {
activePointers.add(event.pointerId)
activePointerTypes.set(event.pointerId, event.pointerType)
if (event.pointerType === 'touch' && getActiveTouchPointerCount() >= 2) {
hadMultiPointerGesture = true
pointerDownState = null
}
if (event.pointerType !== 'touch') {
updateDesktopRotateModifierState(event)
}
syncControlInteractionOptions(event)
if (event.pointerType === 'touch' && hadMultiPointerGesture) {
pointerDownState = null
return
}
pointerDownState = {
x: event.clientX,
y: event.clientY
@@ -4557,6 +4692,18 @@ const handlePointerDown = (event: PointerEvent) => {
}
const handlePointerUp = (event: PointerEvent) => {
activePointers.delete(event.pointerId)
activePointerTypes.delete(event.pointerId)
syncControlInteractionOptions(event)
if (hadMultiPointerGesture) {
if (getActiveTouchPointerCount() === 0) {
hadMultiPointerGesture = false
}
pointerDownState = null
return
}
if (!pointerDownState) return
const moveDistance = Math.hypot(event.clientX - pointerDownState.x, event.clientY - pointerDownState.y)
@@ -4567,6 +4714,43 @@ const handlePointerUp = (event: PointerEvent) => {
void handleSceneTap(event)
}
const handlePointerCancel = (event: PointerEvent) => {
activePointers.delete(event.pointerId)
activePointerTypes.delete(event.pointerId)
pointerDownState = null
if (getActiveTouchPointerCount() === 0) {
hadMultiPointerGesture = false
}
syncControlInteractionOptions(event)
}
const handlePointerLeave = (event: PointerEvent) => {
if (event.pointerType === 'mouse') {
pointerDownState = null
}
}
const handleKeyDown = (event: KeyboardEvent) => {
if (!event.shiftKey && !event.ctrlKey && !event.metaKey && !event.altKey) return
updateDesktopRotateModifierState(event)
syncControlInteractionOptions()
}
const handleKeyUp = (event: KeyboardEvent) => {
updateDesktopRotateModifierState(event)
syncControlInteractionOptions()
}
const handleWindowBlur = () => {
resetInteractionGateState()
}
const handleVisibilityChange = () => {
if (document.visibilityState !== 'visible') {
resetInteractionGateState()
}
}
const emitTargetFocus = (
request: TargetPoiFocusRequest,
status: TargetPoiFocusResult['status'],
@@ -4874,8 +5058,14 @@ const disposeScene = () => {
}
const container = getContainerElement()
container?.removeEventListener('pointerdown', handlePointerDown)
container?.removeEventListener('pointerup', handlePointerUp)
container?.removeEventListener('pointerdown', handlePointerDown, true)
container?.removeEventListener('pointerup', handlePointerUp, true)
container?.removeEventListener('pointercancel', handlePointerCancel, true)
container?.removeEventListener('pointerleave', handlePointerLeave, true)
window.removeEventListener('keydown', handleKeyDown)
window.removeEventListener('keyup', handleKeyUp)
window.removeEventListener('blur', handleWindowBlur)
document.removeEventListener('visibilitychange', handleVisibilityChange)
renderer?.domElement.removeEventListener('wheel', handleWheelIntent, true)
resizeObserver?.disconnect()
@@ -4901,6 +5091,7 @@ const disposeScene = () => {
isProgrammaticCameraChange = false
lastControlDistance = 0
activeControlGestureStartedAt = 0
resetInteractionGateState()
indoorEntryGestureLocked = false
indoorContextLockedUntil = 0
resetOverviewEntryIntent()