|
@@ -1,13 +1,16 @@
|
|
|
/** Cesium-only adapter: terrain - virtual depth, ENU tiles, glTF GPU instance streams. */
|
|
/** Cesium-only adapter: terrain - virtual depth, ENU tiles, glTF GPU instance streams. */
|
|
|
-import { Cartesian3, Cartographic, Color, Matrix4, Model, Quaternion, Transforms, sampleTerrainMostDetailed } from 'cesium'
|
|
|
|
|
|
|
+import { Cartesian3, Cartographic, DistanceDisplayCondition, Matrix4, Model, Quaternion, Transforms, sampleTerrainMostDetailed } from 'cesium'
|
|
|
import { planPipeModels, PIPE_MODEL_NOTE } from '../domain/pipeModels.js'
|
|
import { planPipeModels, PIPE_MODEL_NOTE } from '../domain/pipeModels.js'
|
|
|
import { createPipeModelBatches } from './pipeModelBatches.js'
|
|
import { createPipeModelBatches } from './pipeModelBatches.js'
|
|
|
import { enuToGltf, instanceGltf } from './pipeInstanceGltf.js'
|
|
import { enuToGltf, instanceGltf } from './pipeInstanceGltf.js'
|
|
|
|
|
+import { PIPE_DISPLAY } from '../domain/pipeVisual.js'
|
|
|
|
|
+import { createPipeModelShader } from './pipeModelShader.js'
|
|
|
|
|
|
|
|
const templates = new Map()
|
|
const templates = new Map()
|
|
|
const DEG = Math.PI / 180
|
|
const DEG = Math.PI / 180
|
|
|
const TILE_DEG = 0.01
|
|
const TILE_DEG = 0.01
|
|
|
-const COLORS = { GAS:'#ff9d4d', WATER_SUPPLY:'#4db8ff', DRAINAGE:'#7c6cff', MANHOLE:'#8fa3ad' }
|
|
|
|
|
|
|
+const FAR_GUIDE = new DistanceDisplayCondition(PIPE_DISPLAY.guideFromM, Number.MAX_VALUE)
|
|
|
|
|
+const ALL_DISTANCES = new DistanceDisplayCondition(0, Number.MAX_VALUE)
|
|
|
const gltfCoordinates = (point) => enuToGltf([point.x, point.y, point.z])
|
|
const gltfCoordinates = (point) => enuToGltf([point.x, point.y, point.z])
|
|
|
|
|
|
|
|
async function templateOf(kind, base) {
|
|
async function templateOf(kind, base) {
|
|
@@ -75,7 +78,7 @@ async function groupsFor(viewer, plan, heights, alive) {
|
|
|
for(const row of rows) {
|
|
for(const row of rows) {
|
|
|
const p=row.start||row.position
|
|
const p=row.start||row.position
|
|
|
const tileX=Math.floor(p[0]/TILE_DEG),tileY=Math.floor(p[1]/TILE_DEG)
|
|
const tileX=Math.floor(p[0]/TILE_DEG),tileY=Math.floor(p[1]/TILE_DEG)
|
|
|
- const key=`${tileX}:${tileY}:${row.kind}:${row.industry||'unknown'}`
|
|
|
|
|
|
|
+ const key=`${tileX}:${tileY}:${row.kind}:${row.colorKey||'UNKNOWN'}`
|
|
|
const origin=[(tileX+.5)*TILE_DEG,(tileY+.5)*TILE_DEG]
|
|
const origin=[(tileX+.5)*TILE_DEG,(tileY+.5)*TILE_DEG]
|
|
|
const matrix=Transforms.eastNorthUpToFixedFrame(Cartesian3.fromDegrees(...origin))
|
|
const matrix=Transforms.eastNorthUpToFixedFrame(Cartesian3.fromDegrees(...origin))
|
|
|
const inverse=Matrix4.inverseTransformation(matrix,new Matrix4())
|
|
const inverse=Matrix4.inverseTransformation(matrix,new Matrix4())
|
|
@@ -88,9 +91,9 @@ async function groupsFor(viewer, plan, heights, alive) {
|
|
|
const length=Cartesian3.magnitude(delta)
|
|
const length=Cartesian3.magnitude(delta)
|
|
|
if(length<0.001){missing.add(row.id);continue}
|
|
if(length<0.001){missing.add(row.id);continue}
|
|
|
rotation=rotationFromZ(new Cartesian3(...gltfCoordinates(delta)))
|
|
rotation=rotationFromZ(new Cartesian3(...gltfCoordinates(delta)))
|
|
|
- scale=[row.diameterM,row.diameterM,length]
|
|
|
|
|
|
|
+ scale=[row.displayDiameterM,row.displayDiameterM,length]
|
|
|
}
|
|
}
|
|
|
- if(!groups.has(key)) groups.set(key,{key,kind:row.kind,industry:row.industry,origin,instances:[]})
|
|
|
|
|
|
|
+ if(!groups.has(key)) groups.set(key,{key,kind:row.kind,industry:row.industry,colorKey:row.colorKey||'UNKNOWN',origin,instances:[]})
|
|
|
groups.get(key).instances.push({id:row.id,translation,rotation,scale})
|
|
groups.get(key).instances.push({id:row.id,translation,rotation,scale})
|
|
|
}
|
|
}
|
|
|
// One missing span keeps its entire business pipe's fallback visible.
|
|
// One missing span keeps its entire business pipe's fallback visible.
|
|
@@ -99,6 +102,7 @@ async function groupsFor(viewer, plan, heights, alive) {
|
|
|
|
|
|
|
|
export function createCesiumPipeModels(viewer, { source, onChange=()=>{}, base=import.meta.env?.BASE_URL||'/' }={}) {
|
|
export function createCesiumPipeModels(viewer, { source, onChange=()=>{}, base=import.meta.env?.BASE_URL||'/' }={}) {
|
|
|
const heights=new Map()
|
|
const heights=new Map()
|
|
|
|
|
+ const guideStates=new WeakMap()
|
|
|
let active=true,generation=0,fingerprint='',missing=new Set(),allIds=[],task=null,planning=false
|
|
let active=true,generation=0,fingerprint='',missing=new Set(),allIds=[],task=null,planning=false
|
|
|
let lastGroups=[], signatures=new Map(), terrainFailed=false, lastTerrainAttempt=0
|
|
let lastGroups=[], signatures=new Map(), terrainFailed=false, lastTerrainAttempt=0
|
|
|
let diameterSummary={reported:0,interpolated:0,missing:0}
|
|
let diameterSummary={reported:0,interpolated:0,missing:0}
|
|
@@ -106,8 +110,17 @@ export function createCesiumPipeModels(viewer, { source, onChange=()=>{}, base=i
|
|
|
if(!active) return
|
|
if(!active) return
|
|
|
const covered=new Set(state.covered.filter((id)=>!missing.has(id)))
|
|
const covered=new Set(state.covered.filter((id)=>!missing.has(id)))
|
|
|
for(const entity of source.entities.values) {
|
|
for(const entity of source.entities.values) {
|
|
|
- const show=!covered.has(entity.id)
|
|
|
|
|
- if(entity.show!==show) entity.show=show
|
|
|
|
|
|
|
+ const modeled=covered.has(entity.id)
|
|
|
|
|
+ if(entity.polyline) {
|
|
|
|
|
+ if(entity.show!==true) entity.show=true
|
|
|
|
|
+ if(guideStates.get(entity.polyline)!==modeled) {
|
|
|
|
|
+ entity.polyline.distanceDisplayCondition=modeled?FAR_GUIDE:ALL_DISTANCES
|
|
|
|
|
+ guideStates.set(entity.polyline,modeled)
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ const show=!modeled
|
|
|
|
|
+ if(entity.show!==show) entity.show=show
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
onChange({...state,covered:[...covered],fallback:allIds.length-covered.size,planning,terrainFailed,diameterSummary,note:PIPE_MODEL_NOTE})
|
|
onChange({...state,covered:[...covered],fallback:allIds.length-covered.size,planning,terrainFailed,diameterSummary,note:PIPE_MODEL_NOTE})
|
|
|
viewer.scene.requestRender()
|
|
viewer.scene.requestRender()
|
|
@@ -119,24 +132,26 @@ export function createCesiumPipeModels(viewer, { source, onChange=()=>{}, base=i
|
|
|
const gltf=instanceGltf(template,group.instances)
|
|
const gltf=instanceGltf(template,group.instances)
|
|
|
// Use a public URL/Resource entry, not Cesium's private options.gltf JSON hook.
|
|
// Use a public URL/Resource entry, not Cesium's private options.gltf JSON hook.
|
|
|
const url=URL.createObjectURL(new Blob([JSON.stringify(gltf)],{type:'model/gltf+json'}))
|
|
const url=URL.createObjectURL(new Blob([JSON.stringify(gltf)],{type:'model/gltf+json'}))
|
|
|
|
|
+ const shader=createPipeModelShader(group.colorKey)
|
|
|
let model
|
|
let model
|
|
|
try {
|
|
try {
|
|
|
model=await Model.fromGltfAsync({url,modelMatrix:Transforms.eastNorthUpToFixedFrame(Cartesian3.fromDegrees(...group.origin)),
|
|
model=await Model.fromGltfAsync({url,modelMatrix:Transforms.eastNorthUpToFixedFrame(Cartesian3.fromDegrees(...group.origin)),
|
|
|
id:{kind:'pipe-model-batch',key:group.key,instances:group.instances.map((row)=>row.id)},incrementallyLoadTextures:false,
|
|
id:{kind:'pipe-model-batch',key:group.key,instances:group.instances.map((row)=>row.id)},incrementallyLoadTextures:false,
|
|
|
- color:group.kind==='spline'?Color.fromCssColorString(COLORS[group.industry]||'#8fa3ad'):Color.WHITE})
|
|
|
|
|
|
|
+ customShader:shader})
|
|
|
const release=()=>{ URL.revokeObjectURL(url) }
|
|
const release=()=>{ URL.revokeObjectURL(url) }
|
|
|
model.readyEvent.addEventListener(()=>{release();events.ready()})
|
|
model.readyEvent.addEventListener(()=>{release();events.ready()})
|
|
|
model.errorEvent.addEventListener(()=>{release();events.error()})
|
|
model.errorEvent.addEventListener(()=>{release();events.error()})
|
|
|
if(!active || !events.alive() || viewer.isDestroyed()){model.destroy();throw new Error('Disposed pipe batch')}
|
|
if(!active || !events.alive() || viewer.isDestroyed()){model.destroy();throw new Error('Disposed pipe batch')}
|
|
|
viewer.scene.primitives.add(model)
|
|
viewer.scene.primitives.add(model)
|
|
|
viewer.scene.requestRender()
|
|
viewer.scene.requestRender()
|
|
|
- return {model,url}
|
|
|
|
|
- } catch(error){URL.revokeObjectURL(url);throw error}
|
|
|
|
|
|
|
+ return {model,url,shader}
|
|
|
|
|
+ } catch(error){URL.revokeObjectURL(url);shader.destroy();throw error}
|
|
|
},
|
|
},
|
|
|
- remove:({model,url})=>{
|
|
|
|
|
|
|
+ remove:({model,url,shader})=>{
|
|
|
URL.revokeObjectURL(url)
|
|
URL.revokeObjectURL(url)
|
|
|
if(!viewer.isDestroyed()&&!model.isDestroyed()) viewer.scene.primitives.remove(model)
|
|
if(!viewer.isDestroyed()&&!model.isDestroyed()) viewer.scene.primitives.remove(model)
|
|
|
else if(!model.isDestroyed()) model.destroy()
|
|
else if(!model.isDestroyed()) model.destroy()
|
|
|
|
|
+ if(!shader.isDestroyed()) shader.destroy()
|
|
|
},
|
|
},
|
|
|
onChange:publish,
|
|
onChange:publish,
|
|
|
})
|
|
})
|