|
|
|
|
@@ -1,6 +1,8 @@
|
|
|
|
|
<template>
|
|
|
|
|
<view class="three-map-container">
|
|
|
|
|
<view ref="containerRef" class="three-canvas-wrapper"></view>
|
|
|
|
|
<view ref="containerRef" class="three-canvas-wrapper">
|
|
|
|
|
<view ref="poiDomLabelLayerRef" class="three-poi-dom-label-layer"></view>
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
<view v-if="isLoading && !loadError" class="loading-overlay">
|
|
|
|
|
<view class="loading-content">
|
|
|
|
|
@@ -37,7 +39,7 @@
|
|
|
|
|
|
|
|
|
|
<view v-if="showControls && selectedPOI" class="poi-detail-popup">
|
|
|
|
|
<view class="poi-content">
|
|
|
|
|
<text class="poi-name">{{ selectedPOI.name }}</text>
|
|
|
|
|
<text class="poi-name">{{ presentVisitorPoi({ ...selectedPOI, floorLabel: formatFloorLabel(selectedPOI.floorId), primaryCategory: { label: selectedPOI.primaryCategoryZh } }).displayName }}</text>
|
|
|
|
|
<text class="poi-floor">{{ formatFloorLabel(selectedPOI.floorId) }} · {{ selectedPOI.primaryCategoryZh }}</text>
|
|
|
|
|
</view>
|
|
|
|
|
</view>
|
|
|
|
|
@@ -51,6 +53,7 @@ import { GLTFLoader, type GLTF } from 'three/addons/loaders/GLTFLoader.js'
|
|
|
|
|
import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js'
|
|
|
|
|
import { OrbitControls } from 'three/addons/controls/OrbitControls.js'
|
|
|
|
|
import FloorSwitcher from './FloorSwitcher.vue'
|
|
|
|
|
import { presentVisitorPoi } from '@/view-models/visitorPoiPresentation'
|
|
|
|
|
import type {
|
|
|
|
|
GuideModelFloorAsset,
|
|
|
|
|
GuideModelRenderPackage,
|
|
|
|
|
@@ -76,6 +79,14 @@ import {
|
|
|
|
|
type GuideAutoSwitchInputSource,
|
|
|
|
|
type GuideAutoSwitchRequest
|
|
|
|
|
} from './guideAutoSwitchStateMachine'
|
|
|
|
|
import {
|
|
|
|
|
domLabelBoundsOverlap,
|
|
|
|
|
getDomLabelBounds,
|
|
|
|
|
isDomLabelWithinViewport,
|
|
|
|
|
projectObjectToDom,
|
|
|
|
|
type PoiDomLabelAnchor,
|
|
|
|
|
type PoiDomLabelKind
|
|
|
|
|
} from './poiDomLabels'
|
|
|
|
|
|
|
|
|
|
type ViewMode = 'overview' | 'floor' | 'multi'
|
|
|
|
|
type CameraPreset = 'top' | 'oblique'
|
|
|
|
|
@@ -304,7 +315,7 @@ interface PoiSpriteUserData {
|
|
|
|
|
hitTargetBaseScale?: number
|
|
|
|
|
visibleMarker?: THREE.Sprite
|
|
|
|
|
hitTarget?: THREE.Sprite
|
|
|
|
|
labelSprite?: THREE.Sprite
|
|
|
|
|
labelHandle?: PoiDomLabelHandle
|
|
|
|
|
feedbackUntil?: number
|
|
|
|
|
labelBaseScaleX?: number
|
|
|
|
|
labelBaseScaleY?: number
|
|
|
|
|
@@ -315,6 +326,18 @@ interface PoiSpriteUserData {
|
|
|
|
|
isCorePoi?: boolean
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface PoiDomLabelHandle {
|
|
|
|
|
element: HTMLDivElement
|
|
|
|
|
anchor: THREE.Object3D
|
|
|
|
|
poi: RenderPoi
|
|
|
|
|
kind: PoiDomLabelKind
|
|
|
|
|
anchorMode: PoiDomLabelAnchor
|
|
|
|
|
floorId: string
|
|
|
|
|
size: { width: number; height: number }
|
|
|
|
|
active: boolean
|
|
|
|
|
ownsAnchor?: boolean
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface FocusHallMaterialState {
|
|
|
|
|
material: THREE.Material
|
|
|
|
|
color?: THREE.Color
|
|
|
|
|
@@ -361,6 +384,7 @@ interface PoiMarkerCacheEntry {
|
|
|
|
|
displayMode: PoiDisplayMode
|
|
|
|
|
pois: RenderPoi[]
|
|
|
|
|
group: THREE.Group
|
|
|
|
|
domLabelHandles: PoiDomLabelHandle[]
|
|
|
|
|
markerSize: number
|
|
|
|
|
rawPoiCount?: number
|
|
|
|
|
rawPositionedPoiCount?: number
|
|
|
|
|
@@ -430,6 +454,7 @@ const emit = defineEmits<{
|
|
|
|
|
}>()
|
|
|
|
|
|
|
|
|
|
const containerRef = ref<ContainerRef>(null)
|
|
|
|
|
const poiDomLabelLayerRef = ref<ContainerRef>(null)
|
|
|
|
|
const isLoading = ref(true)
|
|
|
|
|
const loadError = ref(false)
|
|
|
|
|
const loadingProgress = ref(0)
|
|
|
|
|
@@ -485,7 +510,9 @@ const poiDataCache = new Map<string, RenderPoi[]>()
|
|
|
|
|
const poiMarkerGroupCache = new Map<string, PoiMarkerCacheEntry>()
|
|
|
|
|
const preparedFloorModelCache = new Map<string, PreparedFloorModelCacheEntry>()
|
|
|
|
|
const poiCoordinateDiagnosticsKeys = new Set<string>()
|
|
|
|
|
let activeFocusLabelSprite: THREE.Sprite | null = null
|
|
|
|
|
let activeFocusDomLabel: PoiDomLabelHandle | null = null
|
|
|
|
|
let poiDomLabelResizeObserver: ResizeObserver | null = null
|
|
|
|
|
const poiDomLabelHandlesByElement = new Map<HTMLElement, PoiDomLabelHandle>()
|
|
|
|
|
let activeFocusPulseSprite: THREE.Sprite | null = null
|
|
|
|
|
let activeFocusBaseSprite: THREE.Sprite | null = null
|
|
|
|
|
let activeFocusHallGlowMesh: THREE.Mesh | null = null
|
|
|
|
|
@@ -1328,78 +1355,88 @@ const getPoiAmbientLabelPriority = (poi: RenderPoi) => {
|
|
|
|
|
return getPoiPriority(poi)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const getAmbientLabelScaleBoost = (poi: RenderPoi) => {
|
|
|
|
|
if (!controls || !activeModel) return 1
|
|
|
|
|
|
|
|
|
|
const distanceRatio = controls.getDistance() / getActiveModelSpan()
|
|
|
|
|
const baseBoost = Math.min(1.55, Math.max(0.92, distanceRatio / 0.28))
|
|
|
|
|
return isHallPoi(poi) ? Math.max(1, baseBoost) : baseBoost
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const updateAmbientLabelScale = (labelSprite: THREE.Sprite, poi: RenderPoi) => {
|
|
|
|
|
const userData = getPoiSpriteUserData(labelSprite)
|
|
|
|
|
const baseScaleX = typeof userData.labelBaseScaleX === 'number'
|
|
|
|
|
? userData.labelBaseScaleX
|
|
|
|
|
: labelSprite.scale.x
|
|
|
|
|
const baseScaleY = typeof userData.labelBaseScaleY === 'number'
|
|
|
|
|
? userData.labelBaseScaleY
|
|
|
|
|
: labelSprite.scale.y
|
|
|
|
|
const boost = getAmbientLabelScaleBoost(poi)
|
|
|
|
|
|
|
|
|
|
labelSprite.scale.set(baseScaleX * boost, baseScaleY * boost, 1)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const getProjectedScreenPosition = (object: THREE.Object3D) => {
|
|
|
|
|
if (!camera || !renderer) return null
|
|
|
|
|
const point = projectObjectToDom(
|
|
|
|
|
object,
|
|
|
|
|
camera,
|
|
|
|
|
renderer.domElement.clientWidth,
|
|
|
|
|
renderer.domElement.clientHeight
|
|
|
|
|
)
|
|
|
|
|
return point ? new THREE.Vector2(point.x, point.y) : null
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const projected = object.position.clone().project(camera)
|
|
|
|
|
if (!Number.isFinite(projected.x) || !Number.isFinite(projected.y) || projected.z < -1 || projected.z > 1) {
|
|
|
|
|
const getPoiDomLabelLayerElement = () => {
|
|
|
|
|
const layer = poiDomLabelLayerRef.value
|
|
|
|
|
if (!layer || typeof HTMLElement === 'undefined') return null
|
|
|
|
|
if (layer instanceof HTMLElement) return layer
|
|
|
|
|
const element = '$el' in layer ? layer.$el : null
|
|
|
|
|
return element instanceof HTMLElement ? element : null
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const updatePoiDomLabelSize = (handle: PoiDomLabelHandle) => {
|
|
|
|
|
const rect = handle.element.getBoundingClientRect()
|
|
|
|
|
if (rect.width && rect.height) {
|
|
|
|
|
handle.size = { width: rect.width, height: rect.height }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const setPoiDomLabelVisible = (handle: PoiDomLabelHandle, visible: boolean) => {
|
|
|
|
|
handle.element.style.visibility = visible ? 'visible' : 'hidden'
|
|
|
|
|
handle.element.style.opacity = visible ? '1' : '0'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const disposePoiDomLabel = (handle: PoiDomLabelHandle | null) => {
|
|
|
|
|
if (!handle) return
|
|
|
|
|
poiDomLabelResizeObserver?.unobserve(handle.element)
|
|
|
|
|
poiDomLabelHandlesByElement.delete(handle.element)
|
|
|
|
|
handle.element.remove()
|
|
|
|
|
if (handle.ownsAnchor) {
|
|
|
|
|
handle.anchor.parent?.remove(handle.anchor)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const updatePoiDomLabelPosition = (handle: PoiDomLabelHandle) => {
|
|
|
|
|
if (!camera || !renderer || !handle.active) {
|
|
|
|
|
setPoiDomLabelVisible(handle, false)
|
|
|
|
|
return null
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return new THREE.Vector2(
|
|
|
|
|
(projected.x + 1) * renderer.domElement.clientWidth * 0.5,
|
|
|
|
|
(1 - projected.y) * renderer.domElement.clientHeight * 0.5
|
|
|
|
|
const point = projectObjectToDom(
|
|
|
|
|
handle.anchor,
|
|
|
|
|
camera,
|
|
|
|
|
renderer.domElement.clientWidth,
|
|
|
|
|
renderer.domElement.clientHeight
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const getAmbientLabelBounds = (center: THREE.Vector2, poi: RenderPoi) => {
|
|
|
|
|
const width = isHallPoi(poi) ? 138 : 112
|
|
|
|
|
const height = isHallPoi(poi) ? 44 : 34
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
left: center.x - width * 0.5,
|
|
|
|
|
right: center.x + width * 0.5,
|
|
|
|
|
top: center.y - height * 0.5,
|
|
|
|
|
bottom: center.y + height * 0.5
|
|
|
|
|
if (!point) {
|
|
|
|
|
setPoiDomLabelVisible(handle, false)
|
|
|
|
|
return null
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const labelBoundsOverlap = (
|
|
|
|
|
left: ReturnType<typeof getAmbientLabelBounds>,
|
|
|
|
|
right: ReturnType<typeof getAmbientLabelBounds>,
|
|
|
|
|
spacing: number
|
|
|
|
|
) => (
|
|
|
|
|
left.left - spacing < right.right
|
|
|
|
|
&& left.right + spacing > right.left
|
|
|
|
|
&& left.top - spacing < right.bottom
|
|
|
|
|
&& left.bottom + spacing > right.top
|
|
|
|
|
)
|
|
|
|
|
const bounds = getDomLabelBounds(point, handle.size, handle.anchorMode)
|
|
|
|
|
if (!isDomLabelWithinViewport(bounds, renderer.domElement.clientWidth, renderer.domElement.clientHeight)) {
|
|
|
|
|
setPoiDomLabelVisible(handle, false)
|
|
|
|
|
return null
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
handle.element.style.transform = `translate3d(${point.x}px, ${point.y}px, 0) translate(-50%, -100%)`
|
|
|
|
|
return bounds
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const updateAmbientPoiLabels = () => {
|
|
|
|
|
if (!poiGroup || !camera || !renderer) return
|
|
|
|
|
|
|
|
|
|
const densityTier = getPoiLabelDensityTier()
|
|
|
|
|
const acceptedBounds: Array<ReturnType<typeof getAmbientLabelBounds>> = []
|
|
|
|
|
const acceptedBounds: ReturnType<typeof getDomLabelBounds>[] = []
|
|
|
|
|
const candidates = getPoiSprites()
|
|
|
|
|
.map((sprite) => {
|
|
|
|
|
const poi = sprite.userData.poi as RenderPoi | undefined
|
|
|
|
|
const labelSprite = getPoiSpriteUserData(sprite).labelSprite
|
|
|
|
|
const labelHandle = getPoiSpriteUserData(sprite).labelHandle
|
|
|
|
|
|
|
|
|
|
return poi && labelSprite
|
|
|
|
|
return poi && labelHandle
|
|
|
|
|
? {
|
|
|
|
|
marker: sprite,
|
|
|
|
|
labelSprite,
|
|
|
|
|
labelHandle,
|
|
|
|
|
poi,
|
|
|
|
|
priority: getPoiAmbientLabelPriority(poi)
|
|
|
|
|
}
|
|
|
|
|
@@ -1407,32 +1444,26 @@ const updateAmbientPoiLabels = () => {
|
|
|
|
|
})
|
|
|
|
|
.filter((candidate): candidate is {
|
|
|
|
|
marker: THREE.Sprite
|
|
|
|
|
labelSprite: THREE.Sprite
|
|
|
|
|
labelHandle: PoiDomLabelHandle
|
|
|
|
|
poi: RenderPoi
|
|
|
|
|
priority: number
|
|
|
|
|
} => Boolean(candidate))
|
|
|
|
|
.sort((left, right) => right.priority - left.priority)
|
|
|
|
|
|
|
|
|
|
candidates.forEach(({ marker, labelSprite, poi }) => {
|
|
|
|
|
const displayPosition = getPoiDisplayPosition(poi)
|
|
|
|
|
if (!displayPosition || !shouldShowAmbientPoiLabel(poi, marker.visible, densityTier)) {
|
|
|
|
|
labelSprite.visible = false
|
|
|
|
|
candidates.forEach(({ marker, labelHandle, poi }) => {
|
|
|
|
|
if (!shouldShowAmbientPoiLabel(poi, marker.visible, densityTier)) {
|
|
|
|
|
setPoiDomLabelVisible(labelHandle, false)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
updateAmbientLabelScale(labelSprite, poi)
|
|
|
|
|
|
|
|
|
|
const spacing = isHallPoi(poi) ? poiAmbientLabelHallSpacingPx : poiAmbientLabelServiceSpacingPx
|
|
|
|
|
labelSprite.position.copy(displayPosition)
|
|
|
|
|
const screenPosition = getProjectedScreenPosition(labelSprite)
|
|
|
|
|
if (!screenPosition) {
|
|
|
|
|
labelSprite.visible = false
|
|
|
|
|
const bounds = updatePoiDomLabelPosition(labelHandle)
|
|
|
|
|
if (!bounds) {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const bounds = getAmbientLabelBounds(screenPosition, poi)
|
|
|
|
|
const overlaps = acceptedBounds.some((acceptedBound) => labelBoundsOverlap(bounds, acceptedBound, spacing))
|
|
|
|
|
labelSprite.visible = !overlaps
|
|
|
|
|
const overlaps = acceptedBounds.some((acceptedBound) => domLabelBoundsOverlap(bounds, acceptedBound, spacing))
|
|
|
|
|
setPoiDomLabelVisible(labelHandle, !overlaps)
|
|
|
|
|
if (!overlaps) {
|
|
|
|
|
acceptedBounds.push(bounds)
|
|
|
|
|
}
|
|
|
|
|
@@ -1468,12 +1499,12 @@ const updatePoiVisibilityByDistance = () => {
|
|
|
|
|
if (!poi || !isPoiIncludedByVisibleFilter(poi)) {
|
|
|
|
|
sprite.visible = false
|
|
|
|
|
const hitTarget = getPoiSpriteUserData(sprite).hitTarget
|
|
|
|
|
const labelSprite = getPoiSpriteUserData(sprite).labelSprite
|
|
|
|
|
const labelHandle = getPoiSpriteUserData(sprite).labelHandle
|
|
|
|
|
if (hitTarget) {
|
|
|
|
|
hitTarget.visible = false
|
|
|
|
|
}
|
|
|
|
|
if (labelSprite) {
|
|
|
|
|
labelSprite.visible = false
|
|
|
|
|
if (labelHandle) {
|
|
|
|
|
setPoiDomLabelVisible(labelHandle, false)
|
|
|
|
|
}
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
@@ -1499,12 +1530,12 @@ const updatePoiVisibilityByDistance = () => {
|
|
|
|
|
if (!categoryVisible || (!isSelected && !isPinnedHall && (isTooClose || exceedsLimit))) {
|
|
|
|
|
sprite.visible = false
|
|
|
|
|
const hitTarget = getPoiSpriteUserData(sprite).hitTarget
|
|
|
|
|
const labelSprite = getPoiSpriteUserData(sprite).labelSprite
|
|
|
|
|
const labelHandle = getPoiSpriteUserData(sprite).labelHandle
|
|
|
|
|
if (hitTarget) {
|
|
|
|
|
hitTarget.visible = false
|
|
|
|
|
}
|
|
|
|
|
if (labelSprite) {
|
|
|
|
|
labelSprite.visible = false
|
|
|
|
|
if (labelHandle) {
|
|
|
|
|
setPoiDomLabelVisible(labelHandle, false)
|
|
|
|
|
}
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
@@ -1956,6 +1987,12 @@ const initThree = async () => {
|
|
|
|
|
fillLight.position.copy(SGS_VISUAL_RENDER_CONFIG.lights.fill.position)
|
|
|
|
|
scene.add(fillLight)
|
|
|
|
|
|
|
|
|
|
poiDomLabelResizeObserver = new ResizeObserver((entries) => {
|
|
|
|
|
entries.forEach((entry) => {
|
|
|
|
|
const handle = poiDomLabelHandlesByElement.get(entry.target as HTMLElement)
|
|
|
|
|
if (handle) updatePoiDomLabelSize(handle)
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
resizeObserver = new ResizeObserver(handleResize)
|
|
|
|
|
resizeObserver.observe(container)
|
|
|
|
|
container.addEventListener('pointerdown', handlePointerDown, true)
|
|
|
|
|
@@ -1988,6 +2025,7 @@ const startRenderLoop = () => {
|
|
|
|
|
controls?.update()
|
|
|
|
|
}
|
|
|
|
|
updatePoiTapFeedback(performance.now())
|
|
|
|
|
updateAmbientPoiLabels()
|
|
|
|
|
updateFocusLabelScale()
|
|
|
|
|
renderer.render(scene, camera)
|
|
|
|
|
animationId = window.requestAnimationFrame(render)
|
|
|
|
|
@@ -2011,6 +2049,7 @@ const handleResize = () => {
|
|
|
|
|
camera.aspect = nextAspect
|
|
|
|
|
camera.updateProjectionMatrix()
|
|
|
|
|
renderer.setSize(width, height)
|
|
|
|
|
updateAmbientPoiLabels()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const materialTextureKeys = [
|
|
|
|
|
@@ -2265,6 +2304,10 @@ const findNearestPoiMarkerByScreenPoint = (event: PointerEvent, rect: DOMRect) =
|
|
|
|
|
const detachPoiMarkerGroups = () => {
|
|
|
|
|
poiMarkerGroupCache.forEach((entry) => {
|
|
|
|
|
entry.group.parent?.remove(entry.group)
|
|
|
|
|
entry.domLabelHandles.forEach((handle) => {
|
|
|
|
|
handle.active = false
|
|
|
|
|
setPoiDomLabelVisible(handle, false)
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
if (poiGroup) {
|
|
|
|
|
@@ -2285,6 +2328,7 @@ const detachActivePoiLayer = () => {
|
|
|
|
|
const disposePoiMarkerCache = () => {
|
|
|
|
|
detachPoiMarkerGroups()
|
|
|
|
|
poiMarkerGroupCache.forEach((entry) => {
|
|
|
|
|
entry.domLabelHandles.forEach((handle) => disposePoiDomLabel(handle))
|
|
|
|
|
disposeObject(entry.group)
|
|
|
|
|
})
|
|
|
|
|
poiMarkerGroupCache.clear()
|
|
|
|
|
@@ -3141,7 +3185,7 @@ const resolveFloorIdFromRequest = (requestedFloorId?: string | null) => {
|
|
|
|
|
|
|
|
|
|
const isGroundEntryFloor = (floor: FloorIndexItem) => (
|
|
|
|
|
getFloorSortLevel(floor) === 1
|
|
|
|
|
|| getFloorMatchKeys(floor).some((key) => {
|
|
|
|
|
|| getFloorMatchKeys(floor).some((key) => {
|
|
|
|
|
const normalizedKey = normalizeModelMatchKey(key)
|
|
|
|
|
return normalizedKey === 'l1' || normalizedKey === '1f'
|
|
|
|
|
})
|
|
|
|
|
@@ -4504,148 +4548,57 @@ const createPoiHitTargetMaterial = () => {
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const createPoiLabelSprite = (poi: RenderPoi, markerSize: number) => {
|
|
|
|
|
const canvas = document.createElement('canvas')
|
|
|
|
|
canvas.width = 560
|
|
|
|
|
canvas.height = 168
|
|
|
|
|
const context = canvas.getContext('2d')
|
|
|
|
|
const label = poi.name.length > 14 ? `${poi.name.slice(0, 14)}…` : poi.name
|
|
|
|
|
const createPoiDomLabel = (
|
|
|
|
|
poi: RenderPoi,
|
|
|
|
|
anchor: THREE.Object3D,
|
|
|
|
|
kind: PoiDomLabelKind
|
|
|
|
|
) => {
|
|
|
|
|
const layer = getPoiDomLabelLayerElement()
|
|
|
|
|
if (!layer) return null
|
|
|
|
|
|
|
|
|
|
if (context) {
|
|
|
|
|
const color = getPoiColor(poi.primaryCategory)
|
|
|
|
|
context.clearRect(0, 0, canvas.width, canvas.height)
|
|
|
|
|
context.shadowColor = 'rgba(31, 35, 41, 0.2)'
|
|
|
|
|
context.shadowBlur = 18
|
|
|
|
|
context.shadowOffsetY = 8
|
|
|
|
|
context.fillStyle = 'rgba(255, 255, 255, 0.96)'
|
|
|
|
|
context.beginPath()
|
|
|
|
|
context.roundRect(40, 24, 480, 92, 28)
|
|
|
|
|
context.fill()
|
|
|
|
|
context.shadowColor = 'transparent'
|
|
|
|
|
context.lineWidth = 3
|
|
|
|
|
context.strokeStyle = 'rgba(31, 35, 41, 0.08)'
|
|
|
|
|
context.stroke()
|
|
|
|
|
const element = document.createElement('div')
|
|
|
|
|
element.className = `three-poi-dom-label three-poi-dom-label--${kind} ${isHallPoi(poi) ? 'three-poi-dom-label--hall' : 'three-poi-dom-label--service'}`
|
|
|
|
|
element.dataset.poiLabelKind = kind
|
|
|
|
|
element.dataset.poiId = String(poi.id)
|
|
|
|
|
element.dataset.floorId = String(poi.floorId)
|
|
|
|
|
element.setAttribute('aria-hidden', 'true')
|
|
|
|
|
|
|
|
|
|
context.beginPath()
|
|
|
|
|
context.moveTo(258, 116)
|
|
|
|
|
context.lineTo(302, 116)
|
|
|
|
|
context.lineTo(280, 144)
|
|
|
|
|
context.closePath()
|
|
|
|
|
context.fill()
|
|
|
|
|
const title = document.createElement('span')
|
|
|
|
|
title.className = 'three-poi-dom-label__title'
|
|
|
|
|
const limit = kind === 'focus' ? 14 : (isHallPoi(poi) ? 10 : 7)
|
|
|
|
|
const presentation = presentVisitorPoi({
|
|
|
|
|
...poi,
|
|
|
|
|
floorLabel: formatFloorLabel(poi.floorId),
|
|
|
|
|
primaryCategory: { label: poi.primaryCategoryZh }
|
|
|
|
|
})
|
|
|
|
|
title.textContent = presentation.displayName.length > limit
|
|
|
|
|
? `${presentation.displayName.slice(0, limit)}…`
|
|
|
|
|
: presentation.displayName
|
|
|
|
|
element.appendChild(title)
|
|
|
|
|
|
|
|
|
|
context.beginPath()
|
|
|
|
|
context.arc(86, 70, 20, 0, Math.PI * 2)
|
|
|
|
|
context.fillStyle = color
|
|
|
|
|
context.fill()
|
|
|
|
|
|
|
|
|
|
context.font = `700 24px ${CANVAS_FONT_FAMILY}`
|
|
|
|
|
context.textAlign = 'left'
|
|
|
|
|
context.textBaseline = 'middle'
|
|
|
|
|
context.fillStyle = '#ffffff'
|
|
|
|
|
context.fillText(getPoiGlyph(poi), 74, 70, 28)
|
|
|
|
|
|
|
|
|
|
context.font = `700 34px ${CANVAS_FONT_FAMILY}`
|
|
|
|
|
context.fillStyle = '#1f2329'
|
|
|
|
|
context.fillText(label, 120, 61, 360)
|
|
|
|
|
|
|
|
|
|
context.font = `500 22px ${CANVAS_FONT_FAMILY}`
|
|
|
|
|
context.fillStyle = '#6b7178'
|
|
|
|
|
context.fillText(`${formatFloorLabel(poi.floorId)} · ${poi.primaryCategoryZh}`, 120, 94, 360)
|
|
|
|
|
if (kind === 'focus') {
|
|
|
|
|
const meta = document.createElement('span')
|
|
|
|
|
meta.className = 'three-poi-dom-label__meta'
|
|
|
|
|
meta.textContent = `${formatFloorLabel(poi.floorId)} · ${poi.primaryCategoryZh}`
|
|
|
|
|
element.appendChild(meta)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const texture = new THREE.CanvasTexture(canvas)
|
|
|
|
|
texture.colorSpace = THREE.SRGBColorSpace
|
|
|
|
|
const sprite = new THREE.Sprite(new THREE.SpriteMaterial({
|
|
|
|
|
map: texture,
|
|
|
|
|
transparent: true,
|
|
|
|
|
depthTest: false,
|
|
|
|
|
depthWrite: false
|
|
|
|
|
}))
|
|
|
|
|
sprite.userData.isPoiLabel = true
|
|
|
|
|
sprite.userData.labelBaseScaleX = markerSize * 5.6
|
|
|
|
|
sprite.userData.labelBaseScaleY = markerSize * 1.68
|
|
|
|
|
sprite.renderOrder = 30
|
|
|
|
|
sprite.frustumCulled = false
|
|
|
|
|
sprite.scale.set(sprite.userData.labelBaseScaleX, sprite.userData.labelBaseScaleY, 1)
|
|
|
|
|
|
|
|
|
|
return sprite
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const createAmbientPoiLabelSprite = (poi: RenderPoi, markerSize: number) => {
|
|
|
|
|
const canvas = document.createElement('canvas')
|
|
|
|
|
canvas.width = 360
|
|
|
|
|
canvas.height = 112
|
|
|
|
|
const context = canvas.getContext('2d')
|
|
|
|
|
const labelLimit = isHallPoi(poi) ? 10 : 7
|
|
|
|
|
const label = poi.name.length > labelLimit ? `${poi.name.slice(0, labelLimit)}…` : poi.name
|
|
|
|
|
|
|
|
|
|
if (context) {
|
|
|
|
|
const color = getPoiColor(poi.primaryCategory)
|
|
|
|
|
const hallLabel = isHallPoi(poi)
|
|
|
|
|
const boxX = hallLabel ? 28 : 42
|
|
|
|
|
const boxY = hallLabel ? 18 : 26
|
|
|
|
|
const boxWidth = hallLabel ? 304 : 276
|
|
|
|
|
const boxHeight = hallLabel ? 60 : 48
|
|
|
|
|
const radius = hallLabel ? 22 : 18
|
|
|
|
|
|
|
|
|
|
context.clearRect(0, 0, canvas.width, canvas.height)
|
|
|
|
|
context.shadowColor = 'rgba(31, 35, 41, 0.18)'
|
|
|
|
|
context.shadowBlur = hallLabel ? 14 : 10
|
|
|
|
|
context.shadowOffsetY = hallLabel ? 7 : 5
|
|
|
|
|
context.fillStyle = hallLabel ? 'rgba(255, 255, 255, 0.98)' : 'rgba(255, 255, 255, 0.92)'
|
|
|
|
|
context.beginPath()
|
|
|
|
|
context.roundRect(boxX, boxY, boxWidth, boxHeight, radius)
|
|
|
|
|
context.fill()
|
|
|
|
|
context.shadowColor = 'transparent'
|
|
|
|
|
context.lineWidth = hallLabel ? 3 : 2
|
|
|
|
|
context.strokeStyle = hallLabel ? `${color}66` : 'rgba(31, 35, 41, 0.1)'
|
|
|
|
|
context.stroke()
|
|
|
|
|
|
|
|
|
|
context.beginPath()
|
|
|
|
|
context.moveTo(166, boxY + boxHeight)
|
|
|
|
|
context.lineTo(194, boxY + boxHeight)
|
|
|
|
|
context.lineTo(180, boxY + boxHeight + 18)
|
|
|
|
|
context.closePath()
|
|
|
|
|
context.fillStyle = hallLabel ? 'rgba(255, 255, 255, 0.98)' : 'rgba(255, 255, 255, 0.92)'
|
|
|
|
|
context.fill()
|
|
|
|
|
context.strokeStyle = hallLabel ? `${color}50` : 'rgba(31, 35, 41, 0.08)'
|
|
|
|
|
context.stroke()
|
|
|
|
|
|
|
|
|
|
context.beginPath()
|
|
|
|
|
context.arc(boxX + 30, boxY + boxHeight * 0.5, hallLabel ? 15 : 12, 0, Math.PI * 2)
|
|
|
|
|
context.fillStyle = color
|
|
|
|
|
context.fill()
|
|
|
|
|
|
|
|
|
|
context.font = `700 ${hallLabel ? 18 : 15}px ${CANVAS_FONT_FAMILY}`
|
|
|
|
|
context.textAlign = 'center'
|
|
|
|
|
context.textBaseline = 'middle'
|
|
|
|
|
context.fillStyle = '#ffffff'
|
|
|
|
|
context.fillText(getPoiGlyph(poi), boxX + 30, boxY + boxHeight * 0.5, 24)
|
|
|
|
|
|
|
|
|
|
context.font = `700 ${hallLabel ? 24 : 20}px ${CANVAS_FONT_FAMILY}`
|
|
|
|
|
context.textAlign = 'left'
|
|
|
|
|
context.fillStyle = '#1f2329'
|
|
|
|
|
context.fillText(label, boxX + 56, boxY + boxHeight * 0.5, boxWidth - 76)
|
|
|
|
|
layer.appendChild(element)
|
|
|
|
|
const handle: PoiDomLabelHandle = {
|
|
|
|
|
element,
|
|
|
|
|
anchor,
|
|
|
|
|
poi,
|
|
|
|
|
kind,
|
|
|
|
|
anchorMode: 'bottom',
|
|
|
|
|
floorId: poi.floorId,
|
|
|
|
|
size: { width: 1, height: 1 },
|
|
|
|
|
active: kind === 'focus'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const texture = new THREE.CanvasTexture(canvas)
|
|
|
|
|
texture.colorSpace = THREE.SRGBColorSpace
|
|
|
|
|
const sprite = new THREE.Sprite(new THREE.SpriteMaterial({
|
|
|
|
|
map: texture,
|
|
|
|
|
transparent: true,
|
|
|
|
|
depthTest: false,
|
|
|
|
|
depthWrite: false
|
|
|
|
|
}))
|
|
|
|
|
sprite.userData.isPoiLabel = true
|
|
|
|
|
sprite.userData.poi = poi
|
|
|
|
|
sprite.userData.labelBaseScaleX = markerSize * (isHallPoi(poi) ? 3.9 : 3.05)
|
|
|
|
|
sprite.userData.labelBaseScaleY = markerSize * (isHallPoi(poi) ? 1.22 : 0.95)
|
|
|
|
|
sprite.renderOrder = isHallPoi(poi) ? 18 : 15
|
|
|
|
|
sprite.frustumCulled = false
|
|
|
|
|
sprite.scale.set(sprite.userData.labelBaseScaleX, sprite.userData.labelBaseScaleY, 1)
|
|
|
|
|
sprite.visible = false
|
|
|
|
|
|
|
|
|
|
return sprite
|
|
|
|
|
updatePoiDomLabelSize(handle)
|
|
|
|
|
poiDomLabelHandlesByElement.set(element, handle)
|
|
|
|
|
poiDomLabelResizeObserver?.observe(element)
|
|
|
|
|
setPoiDomLabelVisible(handle, false)
|
|
|
|
|
return handle
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const createPoiPulseSprite = (poi: RenderPoi, markerSize: number) => {
|
|
|
|
|
@@ -4723,11 +4676,8 @@ const createPoiBaseSprite = (poi: RenderPoi, markerSize: number) => {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const disposeFocusLabel = () => {
|
|
|
|
|
if (!activeFocusLabelSprite) return
|
|
|
|
|
|
|
|
|
|
activeFocusLabelSprite.parent?.remove(activeFocusLabelSprite)
|
|
|
|
|
disposeObject(activeFocusLabelSprite)
|
|
|
|
|
activeFocusLabelSprite = null
|
|
|
|
|
disposePoiDomLabel(activeFocusDomLabel)
|
|
|
|
|
activeFocusDomLabel = null
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const disposeFocusPulse = () => {
|
|
|
|
|
@@ -4751,13 +4701,14 @@ const showFocusPoiLabel = (poi: RenderPoi) => {
|
|
|
|
|
if (!poiGroup || !displayPosition) return
|
|
|
|
|
|
|
|
|
|
disposeFocusLabel()
|
|
|
|
|
|
|
|
|
|
const markerSize = getPoiMarkerSize()
|
|
|
|
|
activeFocusLabelSprite = createPoiLabelSprite(poi, markerSize)
|
|
|
|
|
activeFocusLabelSprite.frustumCulled = false
|
|
|
|
|
activeFocusLabelSprite.position.copy(displayPosition)
|
|
|
|
|
updateFocusLabelScale()
|
|
|
|
|
poiGroup.add(activeFocusLabelSprite)
|
|
|
|
|
const anchor = new THREE.Object3D()
|
|
|
|
|
anchor.position.copy(displayPosition)
|
|
|
|
|
poiGroup.add(anchor)
|
|
|
|
|
activeFocusDomLabel = createPoiDomLabel(poi, anchor, 'focus')
|
|
|
|
|
if (activeFocusDomLabel) {
|
|
|
|
|
activeFocusDomLabel.ownsAnchor = true
|
|
|
|
|
activeFocusDomLabel.element.dataset.focusLabel = 'active'
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const showFocusPoiBase = (poi: RenderPoi) => {
|
|
|
|
|
@@ -4815,26 +4766,10 @@ const getPoiMarkerSize = () => (
|
|
|
|
|
: 3
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
const getFocusLabelScaleBoost = () => {
|
|
|
|
|
if (!controls || !activeModel) return 1
|
|
|
|
|
|
|
|
|
|
const distanceRatio = controls.getDistance() / getActiveModelSpan()
|
|
|
|
|
return Math.min(2.25, Math.max(1, distanceRatio / 0.36))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const updateFocusLabelScale = () => {
|
|
|
|
|
if (!activeFocusLabelSprite) return
|
|
|
|
|
|
|
|
|
|
const userData = getPoiSpriteUserData(activeFocusLabelSprite)
|
|
|
|
|
const baseScaleX = typeof userData.labelBaseScaleX === 'number'
|
|
|
|
|
? userData.labelBaseScaleX
|
|
|
|
|
: activeFocusLabelSprite.scale.x
|
|
|
|
|
const baseScaleY = typeof userData.labelBaseScaleY === 'number'
|
|
|
|
|
? userData.labelBaseScaleY
|
|
|
|
|
: activeFocusLabelSprite.scale.y
|
|
|
|
|
const scaleBoost = getFocusLabelScaleBoost()
|
|
|
|
|
|
|
|
|
|
activeFocusLabelSprite.scale.set(baseScaleX * scaleBoost, baseScaleY * scaleBoost, 1)
|
|
|
|
|
if (!activeFocusDomLabel) return
|
|
|
|
|
const bounds = updatePoiDomLabelPosition(activeFocusDomLabel)
|
|
|
|
|
setPoiDomLabelVisible(activeFocusDomLabel, Boolean(bounds))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const setPoiSpriteFocusStyle = (sprite: THREE.Sprite, focused: boolean) => {
|
|
|
|
|
@@ -4878,9 +4813,6 @@ const findLoadedPoi = (poiId: string) => (
|
|
|
|
|
const createPoiMarkerSprites = (poi: RenderPoi, markerSize: number) => {
|
|
|
|
|
const sprite = new THREE.Sprite(createPoiMaterial(poi))
|
|
|
|
|
const hitTarget = new THREE.Sprite(createPoiHitTargetMaterial())
|
|
|
|
|
const labelSprite = shouldCreateAmbientPoiLabel(poi)
|
|
|
|
|
? createAmbientPoiLabelSprite(poi, markerSize)
|
|
|
|
|
: null
|
|
|
|
|
const isCorePoi = corePoiCategories.has(poi.primaryCategory) || poi.primaryCategory === 'target_preview'
|
|
|
|
|
const hitTargetScale = markerSize * (isCorePoi ? poiHitTargetCoreScaleMultiplier : poiHitTargetScaleMultiplier)
|
|
|
|
|
|
|
|
|
|
@@ -4889,9 +4821,6 @@ const createPoiMarkerSprites = (poi: RenderPoi, markerSize: number) => {
|
|
|
|
|
sprite.userData.isCorePoi = isCorePoi
|
|
|
|
|
sprite.userData.hitTarget = hitTarget
|
|
|
|
|
sprite.frustumCulled = false
|
|
|
|
|
if (labelSprite) {
|
|
|
|
|
sprite.userData.labelSprite = labelSprite
|
|
|
|
|
}
|
|
|
|
|
sprite.userData.hitTargetBaseScale = hitTargetScale
|
|
|
|
|
|
|
|
|
|
hitTarget.userData.baseScale = hitTargetScale
|
|
|
|
|
@@ -4904,7 +4833,7 @@ const createPoiMarkerSprites = (poi: RenderPoi, markerSize: number) => {
|
|
|
|
|
|
|
|
|
|
setPoiSpriteFocusStyle(sprite, poi.id === activeFocusPoiId.value)
|
|
|
|
|
|
|
|
|
|
return { sprite, hitTarget, labelSprite }
|
|
|
|
|
return { sprite, hitTarget }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const createPoiFromFocusRequest = (request: TargetPoiFocusRequest): RenderPoi | null => {
|
|
|
|
|
@@ -4946,6 +4875,7 @@ const createPoiMarkerGroup = (
|
|
|
|
|
markerSize: number
|
|
|
|
|
) => {
|
|
|
|
|
const group = new THREE.Group()
|
|
|
|
|
const domLabelHandles: PoiDomLabelHandle[] = []
|
|
|
|
|
group.name = `GuideModelPOI_${floorId}_${displayMode}`
|
|
|
|
|
group.userData.floorId = floorId
|
|
|
|
|
group.userData.currentFloor = floorId
|
|
|
|
|
@@ -4955,12 +4885,15 @@ const createPoiMarkerGroup = (
|
|
|
|
|
const displayPosition = getPoiDisplayPosition(poi)
|
|
|
|
|
if (!displayPosition) return
|
|
|
|
|
|
|
|
|
|
const { sprite, hitTarget, labelSprite } = createPoiMarkerSprites(poi, markerSize)
|
|
|
|
|
const { sprite, hitTarget } = createPoiMarkerSprites(poi, markerSize)
|
|
|
|
|
sprite.position.copy(displayPosition)
|
|
|
|
|
hitTarget.position.copy(displayPosition)
|
|
|
|
|
if (labelSprite) {
|
|
|
|
|
labelSprite.position.copy(displayPosition)
|
|
|
|
|
labelSprite.visible = false
|
|
|
|
|
const labelHandle = shouldCreateAmbientPoiLabel(poi)
|
|
|
|
|
? createPoiDomLabel(poi, sprite, 'ambient')
|
|
|
|
|
: null
|
|
|
|
|
if (labelHandle) {
|
|
|
|
|
sprite.userData.labelHandle = labelHandle
|
|
|
|
|
domLabelHandles.push(labelHandle)
|
|
|
|
|
}
|
|
|
|
|
sprite.visible = isPoiIncludedByVisibleFilter(poi)
|
|
|
|
|
&& (
|
|
|
|
|
@@ -4970,12 +4903,9 @@ const createPoiMarkerGroup = (
|
|
|
|
|
hitTarget.visible = sprite.visible
|
|
|
|
|
group.add(sprite)
|
|
|
|
|
group.add(hitTarget)
|
|
|
|
|
if (labelSprite) {
|
|
|
|
|
group.add(labelSprite)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
return group
|
|
|
|
|
return { group, domLabelHandles }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const attachPoiMarkerGroup = (entry: PoiMarkerCacheEntry) => {
|
|
|
|
|
@@ -4985,6 +4915,9 @@ const attachPoiMarkerGroup = (entry: PoiMarkerCacheEntry) => {
|
|
|
|
|
if (entry.group.parent !== poiGroup) {
|
|
|
|
|
poiGroup.add(entry.group)
|
|
|
|
|
}
|
|
|
|
|
entry.domLabelHandles.forEach((handle) => {
|
|
|
|
|
handle.active = true
|
|
|
|
|
})
|
|
|
|
|
poiGroup.userData.floorId = entry.floorId
|
|
|
|
|
poiGroup.userData.currentFloor = entry.floorId
|
|
|
|
|
updatePoiMarkerFocus()
|
|
|
|
|
@@ -5079,7 +5012,8 @@ const prepareFloorPOIs = async (
|
|
|
|
|
floorId: floor.floorId,
|
|
|
|
|
displayMode,
|
|
|
|
|
pois: validPois,
|
|
|
|
|
group: markerGroup,
|
|
|
|
|
group: markerGroup.group,
|
|
|
|
|
domLabelHandles: markerGroup.domLabelHandles,
|
|
|
|
|
markerSize,
|
|
|
|
|
rawPoiCount: floorPois.length,
|
|
|
|
|
rawPositionedPoiCount: floorPois.filter((poi) => Boolean(poi.positionGltf)).length,
|
|
|
|
|
@@ -5087,7 +5021,8 @@ const prepareFloorPOIs = async (
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (loadToken !== undefined && !isCurrentModelLoad(loadToken)) {
|
|
|
|
|
disposeObject(markerGroup)
|
|
|
|
|
markerGroup.domLabelHandles.forEach((handle) => disposePoiDomLabel(handle))
|
|
|
|
|
disposeObject(markerGroup.group)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -5771,6 +5706,9 @@ const disposeScene = () => {
|
|
|
|
|
|
|
|
|
|
resizeObserver?.disconnect()
|
|
|
|
|
resizeObserver = null
|
|
|
|
|
poiDomLabelResizeObserver?.disconnect()
|
|
|
|
|
poiDomLabelResizeObserver = null
|
|
|
|
|
poiDomLabelHandlesByElement.clear()
|
|
|
|
|
|
|
|
|
|
// 清理自动切换相关资源
|
|
|
|
|
if (controls) {
|
|
|
|
|
@@ -5920,6 +5858,97 @@ onUnmounted(() => {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.three-poi-dom-label-layer {
|
|
|
|
|
position: absolute;
|
|
|
|
|
inset: 0;
|
|
|
|
|
z-index: 4;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
pointer-events: none;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
:deep(.three-poi-dom-label) {
|
|
|
|
|
position: absolute;
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
box-sizing: border-box;
|
|
|
|
|
max-width: min(190px, calc(100% - 20px));
|
|
|
|
|
pointer-events: none;
|
|
|
|
|
white-space: nowrap;
|
|
|
|
|
color: #1f2329;
|
|
|
|
|
background: rgba(255, 255, 255, 0.96);
|
|
|
|
|
border: 1px solid rgba(31, 35, 41, 0.12);
|
|
|
|
|
border-radius: 5px;
|
|
|
|
|
box-shadow: 0 4px 12px rgba(31, 35, 41, 0.2);
|
|
|
|
|
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.86);
|
|
|
|
|
transition: opacity 120ms ease;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
:deep(.three-poi-dom-label::after) {
|
|
|
|
|
position: absolute;
|
|
|
|
|
left: 50%;
|
|
|
|
|
bottom: -5px;
|
|
|
|
|
width: 8px;
|
|
|
|
|
height: 8px;
|
|
|
|
|
box-sizing: border-box;
|
|
|
|
|
content: '';
|
|
|
|
|
background: rgba(255, 255, 255, 0.96);
|
|
|
|
|
border-right: 1px solid rgba(31, 35, 41, 0.12);
|
|
|
|
|
border-bottom: 1px solid rgba(31, 35, 41, 0.12);
|
|
|
|
|
transform: translateX(-50%) rotate(45deg);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
:deep(.three-poi-dom-label--ambient) {
|
|
|
|
|
min-height: 30px;
|
|
|
|
|
padding: 5px 9px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
:deep(.three-poi-dom-label--ambient.three-poi-dom-label--hall) {
|
|
|
|
|
min-height: 34px;
|
|
|
|
|
padding: 6px 10px;
|
|
|
|
|
border-color: rgba(47, 111, 237, 0.42);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
:deep(.three-poi-dom-label--ambient.three-poi-dom-label--service) {
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
line-height: 18px;
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
:deep(.three-poi-dom-label--ambient.three-poi-dom-label--hall) {
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
line-height: 20px;
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
:deep(.three-poi-dom-label--focus) {
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
align-items: flex-start;
|
|
|
|
|
min-width: 156px;
|
|
|
|
|
padding: 8px 11px;
|
|
|
|
|
border-color: rgba(47, 111, 237, 0.5);
|
|
|
|
|
box-shadow: 0 6px 18px rgba(31, 35, 41, 0.24);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
:deep(.three-poi-dom-label__title) {
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
max-width: 100%;
|
|
|
|
|
text-overflow: ellipsis;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
:deep(.three-poi-dom-label--focus .three-poi-dom-label__title) {
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
line-height: 22px;
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
:deep(.three-poi-dom-label__meta) {
|
|
|
|
|
margin-top: 1px;
|
|
|
|
|
color: #626b75;
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
line-height: 18px;
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.loading-overlay {
|
|
|
|
|
position: absolute;
|
|
|
|
|
inset: 0;
|
|
|
|
|
|