修复楼层点位展示与楼层匹配
This commit is contained in:
@@ -132,6 +132,7 @@ interface ReusableModelResources {
|
|||||||
|
|
||||||
interface PoiMarkerCacheEntry {
|
interface PoiMarkerCacheEntry {
|
||||||
floorId: string
|
floorId: string
|
||||||
|
displayMode: PoiDisplayMode
|
||||||
pois: RenderPoi[]
|
pois: RenderPoi[]
|
||||||
group: THREE.Group
|
group: THREE.Group
|
||||||
markerSize: number
|
markerSize: number
|
||||||
@@ -393,20 +394,25 @@ const isExteriorModelNode = (object: THREE.Object3D) => (
|
|||||||
))
|
))
|
||||||
)
|
)
|
||||||
|
|
||||||
const getKnownFloorIdsBySpecificity = () => (
|
const getKnownFloorMatchEntriesBySpecificity = () => (
|
||||||
floorIndex.value
|
floorIndex.value
|
||||||
.map((floor) => floor.floorId)
|
.flatMap((floor) => [floor.floorId, floor.label, ...(floor.modelMatchKeys || [])]
|
||||||
.sort((a, b) => b.length - a.length)
|
.filter((value): value is string => Boolean(value))
|
||||||
|
.map((key) => ({ key, floorId: floor.floorId })))
|
||||||
|
.sort((a, b) => b.key.length - a.key.length)
|
||||||
)
|
)
|
||||||
|
|
||||||
const getModelNodeFloorIdByName = (name: string) => {
|
const getModelNodeFloorIdByName = (name: string) => {
|
||||||
if (!name) return ''
|
if (!name) return ''
|
||||||
|
|
||||||
return getKnownFloorIdsBySpecificity().find((floorId) => (
|
const normalizedName = normalizeModelMatchKey(name)
|
||||||
name === floorId
|
return getKnownFloorMatchEntriesBySpecificity().find(({ key }) => (
|
||||||
|| name.startsWith(`${floorId}_`)
|
name === key
|
||||||
|| name.startsWith(`${floorId}中心`)
|
|| name.startsWith(`${key}_`)
|
||||||
)) || ''
|
|| name.startsWith(`${key}中心`)
|
||||||
|
|| normalizedName === normalizeModelMatchKey(key)
|
||||||
|
|| normalizedName.startsWith(normalizeModelMatchKey(key))
|
||||||
|
))?.floorId || ''
|
||||||
}
|
}
|
||||||
|
|
||||||
const getModelNodeFloorId = (object: THREE.Object3D) => (
|
const getModelNodeFloorId = (object: THREE.Object3D) => (
|
||||||
@@ -489,6 +495,8 @@ const getPoiDisplayMode = (): PoiDisplayMode => {
|
|||||||
return 'detail'
|
return 'detail'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getPoiMarkerCacheKey = (floorId: string, displayMode = getPoiDisplayMode()) => `${floorId}:${displayMode}`
|
||||||
|
|
||||||
const getPoiGlyph = (poi: RenderPoi) => (
|
const getPoiGlyph = (poi: RenderPoi) => (
|
||||||
poiGlyphMap[poi.iconType] || poi.name.charAt(0) || '点'
|
poiGlyphMap[poi.iconType] || poi.name.charAt(0) || '点'
|
||||||
)
|
)
|
||||||
@@ -521,6 +529,7 @@ const getActiveModelSpan = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const getPoiVisibilityTier = (): PoiVisibilityTier => {
|
const getPoiVisibilityTier = (): PoiVisibilityTier => {
|
||||||
|
if (activeView.value === 'floor') return 'full'
|
||||||
if (!controls || !activeModel) return 'full'
|
if (!controls || !activeModel) return 'full'
|
||||||
|
|
||||||
const ratio = controls.getDistance() / getActiveModelSpan()
|
const ratio = controls.getDistance() / getActiveModelSpan()
|
||||||
@@ -2339,10 +2348,16 @@ const addFocusMarkerFromRequest = (request: TargetPoiFocusRequest) => {
|
|||||||
return poi
|
return poi
|
||||||
}
|
}
|
||||||
|
|
||||||
const createPoiMarkerGroup = (floorId: string, pois: RenderPoi[], markerSize: number) => {
|
const createPoiMarkerGroup = (
|
||||||
|
floorId: string,
|
||||||
|
displayMode: PoiDisplayMode,
|
||||||
|
pois: RenderPoi[],
|
||||||
|
markerSize: number
|
||||||
|
) => {
|
||||||
const group = new THREE.Group()
|
const group = new THREE.Group()
|
||||||
group.name = `GuideModelPOI_${floorId}`
|
group.name = `GuideModelPOI_${floorId}_${displayMode}`
|
||||||
group.userData.floorId = floorId
|
group.userData.floorId = floorId
|
||||||
|
group.userData.displayMode = displayMode
|
||||||
|
|
||||||
pois.forEach((poi) => {
|
pois.forEach((poi) => {
|
||||||
const [x, y, z] = poi.positionGltf!
|
const [x, y, z] = poi.positionGltf!
|
||||||
@@ -2374,22 +2389,25 @@ const loadFloorPOIs = async (floor: FloorIndexItem, loadToken?: number) => {
|
|||||||
|
|
||||||
if (loadToken !== undefined && !isCurrentModelLoad(loadToken)) return
|
if (loadToken !== undefined && !isCurrentModelLoad(loadToken)) return
|
||||||
|
|
||||||
const cachedEntry = poiMarkerGroupCache.get(floor.floorId)
|
const displayMode = getPoiDisplayMode()
|
||||||
|
const markerCacheKey = getPoiMarkerCacheKey(floor.floorId, displayMode)
|
||||||
|
const cachedEntry = poiMarkerGroupCache.get(markerCacheKey)
|
||||||
if (cachedEntry) {
|
if (cachedEntry) {
|
||||||
attachPoiMarkerGroup(cachedEntry)
|
attachPoiMarkerGroup(cachedEntry)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const cachedPois = poiDataCache.get(floor.floorId)
|
const cachedPois = poiDataCache.get(floor.floorId)
|
||||||
const validPois = cachedPois || (await props.modelSource.loadFloorPois(floor.floorId))
|
const floorPois = cachedPois || await props.modelSource.loadFloorPois(floor.floorId)
|
||||||
.filter((poi) => shouldShowPoiInCurrentMode(poi))
|
|
||||||
if (loadToken !== undefined && !isCurrentModelLoad(loadToken)) return
|
if (loadToken !== undefined && !isCurrentModelLoad(loadToken)) return
|
||||||
|
|
||||||
poiDataCache.set(floor.floorId, validPois)
|
poiDataCache.set(floor.floorId, floorPois)
|
||||||
|
const validPois = floorPois.filter((poi) => shouldShowPoiInCurrentMode(poi))
|
||||||
const markerSize = getPoiMarkerSize()
|
const markerSize = getPoiMarkerSize()
|
||||||
const markerGroup = createPoiMarkerGroup(floor.floorId, validPois, markerSize)
|
const markerGroup = createPoiMarkerGroup(floor.floorId, displayMode, validPois, markerSize)
|
||||||
const entry: PoiMarkerCacheEntry = {
|
const entry: PoiMarkerCacheEntry = {
|
||||||
floorId: floor.floorId,
|
floorId: floor.floorId,
|
||||||
|
displayMode,
|
||||||
pois: validPois,
|
pois: validPois,
|
||||||
group: markerGroup,
|
group: markerGroup,
|
||||||
markerSize
|
markerSize
|
||||||
@@ -2400,7 +2418,7 @@ const loadFloorPOIs = async (floor: FloorIndexItem, loadToken?: number) => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
poiMarkerGroupCache.set(floor.floorId, entry)
|
poiMarkerGroupCache.set(markerCacheKey, entry)
|
||||||
attachPoiMarkerGroup(entry)
|
attachPoiMarkerGroup(entry)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
70
src/domain/guideFloor.ts
Normal file
70
src/domain/guideFloor.ts
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
import type {
|
||||||
|
MuseumFloor,
|
||||||
|
MuseumPoi
|
||||||
|
} from '@/domain/museum'
|
||||||
|
|
||||||
|
interface FloorLike {
|
||||||
|
id?: string | number | null
|
||||||
|
label?: string | null
|
||||||
|
floorId?: string | number | null
|
||||||
|
floorCode?: string | null
|
||||||
|
floorName?: string | null
|
||||||
|
name?: string | null
|
||||||
|
modelGroup?: string | null
|
||||||
|
}
|
||||||
|
|
||||||
|
const indoorFloorCodePattern = /^L-?\d+(?:\.\d+)?$/i
|
||||||
|
const indoorFloorLabelPattern = /^(?:B\d+(?:\.\d+)?|\d+(?:\.\d+)?F)$/i
|
||||||
|
const rawNumericIdPattern = /^\d{6,}$/
|
||||||
|
const nonNavigableFloorPattern = /(EXTERIOR|OUTDOOR|BUILDING|FACADE|EXTERNAL|外立面|建筑外观|建筑外立面|室外模型|室外场景|室外底图|室外建筑|室外外观|室外)$/i
|
||||||
|
|
||||||
|
const normalizeValue = (value: unknown) => (
|
||||||
|
value === null || typeof value === 'undefined' ? '' : String(value).trim()
|
||||||
|
)
|
||||||
|
|
||||||
|
export const isNonNavigableFloorToken = (value: unknown) => {
|
||||||
|
const normalized = normalizeValue(value)
|
||||||
|
return Boolean(normalized && nonNavigableFloorPattern.test(normalized))
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isIndoorFloorToken = (value: unknown) => {
|
||||||
|
const normalized = normalizeValue(value)
|
||||||
|
if (!normalized || isNonNavigableFloorToken(normalized)) return false
|
||||||
|
|
||||||
|
return indoorFloorCodePattern.test(normalized)
|
||||||
|
|| indoorFloorLabelPattern.test(normalized)
|
||||||
|
|| rawNumericIdPattern.test(normalized)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isIndoorNavigableFloor = (floor: FloorLike | null | undefined) => {
|
||||||
|
if (!floor) return false
|
||||||
|
|
||||||
|
const tokens = [
|
||||||
|
floor.id,
|
||||||
|
floor.label,
|
||||||
|
floor.floorId,
|
||||||
|
floor.floorCode,
|
||||||
|
floor.floorName,
|
||||||
|
floor.name,
|
||||||
|
floor.modelGroup
|
||||||
|
]
|
||||||
|
|
||||||
|
if (tokens.some(isNonNavigableFloorToken)) return false
|
||||||
|
|
||||||
|
return tokens.some(isIndoorFloorToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const isPoiOnIndoorNavigableFloor = (poi: Pick<MuseumPoi, 'floorId' | 'floorLabel'>) => (
|
||||||
|
isIndoorNavigableFloor({
|
||||||
|
floorId: poi.floorId,
|
||||||
|
label: poi.floorLabel
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
export const filterIndoorNavigableFloors = <T extends MuseumFloor>(floors: T[]) => (
|
||||||
|
floors.filter((floor) => isIndoorNavigableFloor(floor))
|
||||||
|
)
|
||||||
|
|
||||||
|
export const filterPoisOnIndoorNavigableFloors = <T extends Pick<MuseumPoi, 'floorId' | 'floorLabel'>>(pois: T[]) => (
|
||||||
|
pois.filter(isPoiOnIndoorNavigableFloor)
|
||||||
|
)
|
||||||
@@ -15,6 +15,7 @@ export interface GuideModelFloorAsset {
|
|||||||
order: number
|
order: number
|
||||||
modelUrl: string
|
modelUrl: string
|
||||||
sharedModelAsset?: boolean
|
sharedModelAsset?: boolean
|
||||||
|
modelMatchKeys?: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GuideModelRenderPackage {
|
export interface GuideModelRenderPackage {
|
||||||
|
|||||||
@@ -89,6 +89,26 @@ const resolveSgsAssetUrl = (url?: string | null) => {
|
|||||||
const sortSgsFloors = (floors: SgsSdkFloorSummaryPayload[]) => [...floors]
|
const sortSgsFloors = (floors: SgsSdkFloorSummaryPayload[]) => [...floors]
|
||||||
.sort((a, b) => Number(a.sortOrder || 0) - Number(b.sortOrder || 0))
|
.sort((a, b) => Number(a.sortOrder || 0) - Number(b.sortOrder || 0))
|
||||||
|
|
||||||
|
const isSharedSgsModelAsset = (
|
||||||
|
modelUrl: string,
|
||||||
|
overviewModelUrl: string,
|
||||||
|
allModelUrls: string[]
|
||||||
|
) => {
|
||||||
|
if (!modelUrl) return false
|
||||||
|
|
||||||
|
return modelUrl === overviewModelUrl || allModelUrls.filter((url) => url === modelUrl).length > 1
|
||||||
|
}
|
||||||
|
|
||||||
|
const getSgsFloorModelMatchKeys = (floor: SgsSdkFloorSummaryPayload, label: string) => (
|
||||||
|
[
|
||||||
|
String(floor.floorId),
|
||||||
|
floor.floorCode,
|
||||||
|
floor.floorName,
|
||||||
|
label
|
||||||
|
]
|
||||||
|
.filter((value): value is string => Boolean(value))
|
||||||
|
)
|
||||||
|
|
||||||
export interface GuideModelRepository extends GuideModelSource {}
|
export interface GuideModelRepository extends GuideModelSource {}
|
||||||
|
|
||||||
export class StaticGuideModelRepository implements GuideModelRepository {
|
export class StaticGuideModelRepository implements GuideModelRepository {
|
||||||
@@ -144,17 +164,18 @@ export class SgsSdkGuideModelRepository implements GuideModelRepository {
|
|||||||
floors.map((floor) => this.provider.getFloorBundle(String(floor.floorId)))
|
floors.map((floor) => this.provider.getFloorBundle(String(floor.floorId)))
|
||||||
)
|
)
|
||||||
|
|
||||||
const floorAssets: GuideModelFloorAsset[] = floors
|
const floorModelItems = floors
|
||||||
.map((floor, index) => {
|
.map((floor, index) => {
|
||||||
const bundle = bundles[index]
|
const bundle = bundles[index]
|
||||||
const modelUrl = resolveSgsAssetUrl(bundle.model?.modelUrl || bundle.model?.fallbackModelUrl)
|
const modelUrl = resolveSgsAssetUrl(bundle.model?.modelUrl || bundle.model?.fallbackModelUrl)
|
||||||
|
const label = formatSgsFloorLabel(floor.floorCode, floor.floorName)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
floorId: String(floor.floorId),
|
floorId: String(floor.floorId),
|
||||||
label: formatSgsFloorLabel(floor.floorCode, floor.floorName),
|
label,
|
||||||
order: Number(floor.sortOrder || 0),
|
order: Number(floor.sortOrder || 0),
|
||||||
modelUrl,
|
modelUrl,
|
||||||
sharedModelAsset: false
|
modelMatchKeys: getSgsFloorModelMatchKeys(floor, label)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.filter((asset) => asset.modelUrl)
|
.filter((asset) => asset.modelUrl)
|
||||||
@@ -165,7 +186,12 @@ export class SgsSdkGuideModelRepository implements GuideModelRepository {
|
|||||||
: null
|
: null
|
||||||
const overviewModelUrl = resolveSgsAssetUrl(
|
const overviewModelUrl = resolveSgsAssetUrl(
|
||||||
overviewBundle?.model?.modelUrl || overviewBundle?.model?.fallbackModelUrl
|
overviewBundle?.model?.modelUrl || overviewBundle?.model?.fallbackModelUrl
|
||||||
) || floorAssets[0]?.modelUrl || ''
|
) || floorModelItems[0]?.modelUrl || ''
|
||||||
|
const floorModelUrls = floorModelItems.map((asset) => asset.modelUrl)
|
||||||
|
const floorAssets: GuideModelFloorAsset[] = floorModelItems.map((asset) => ({
|
||||||
|
...asset,
|
||||||
|
sharedModelAsset: isSharedSgsModelAsset(asset.modelUrl, overviewModelUrl, floorModelUrls)
|
||||||
|
}))
|
||||||
|
|
||||||
return {
|
return {
|
||||||
overviewModelUrl,
|
overviewModelUrl,
|
||||||
|
|||||||
Reference in New Issue
Block a user