From 6dfe98f4d3731fb290f3576babfc26fd68ab695d Mon Sep 17 00:00:00 2001 From: vladyslavtk Date: Wed, 9 Oct 2024 21:02:42 +0300 Subject: [PATCH 01/15] WIP: models uplaod and positioning on map --- src/apps/permits/demoPermitConfig.ts | 2 +- src/apps/permits/index.ts | 45 +- src/plugins/cesium/ngv-cesium-factories.ts | 18 +- .../ngv-plugin-cesium-model-interact.ts | 516 ++++++++++++++++++ .../cesium/ngv-plugin-cesium-upload.ts | 179 ++++++ .../cesium/ngv-plugin-cesium-widget.ts | 19 +- src/plugins/ui/ngv-upload.ts | 177 ++++++ 7 files changed, 940 insertions(+), 16 deletions(-) create mode 100644 src/plugins/cesium/ngv-plugin-cesium-model-interact.ts create mode 100644 src/plugins/cesium/ngv-plugin-cesium-upload.ts create mode 100644 src/plugins/ui/ngv-upload.ts diff --git a/src/apps/permits/demoPermitConfig.ts b/src/apps/permits/demoPermitConfig.ts index c9d7bc8..90d7230 100644 --- a/src/apps/permits/demoPermitConfig.ts +++ b/src/apps/permits/demoPermitConfig.ts @@ -28,7 +28,7 @@ export const config: IPermitsConfig = { }, layers: { // tilesets: ['@cesium/googlePhotorealistic'], - models: ['@demo/sofa', '@demo/thatopensmall'], + // models: ['@demo/sofa', '@demo/thatopensmall'], imageries: ['@geoadmin/pixel-karte-farbe'], // terrain: '@geoadmin/terrain', }, diff --git a/src/apps/permits/index.ts b/src/apps/permits/index.ts index f86c982..18c3b50 100644 --- a/src/apps/permits/index.ts +++ b/src/apps/permits/index.ts @@ -1,6 +1,6 @@ import type {HTMLTemplateResult} from 'lit'; import {html} from 'lit'; -import {customElement} from 'lit/decorators.js'; +import {customElement, state} from 'lit/decorators.js'; import '../../structure/ngv-structure-app.js'; @@ -11,19 +11,31 @@ import {ABaseApp} from '../../structure/BaseApp.js'; import type {IPermitsConfig} from './ingv-config-permits.js'; import '../../plugins/cesium/ngv-plugin-cesium-widget'; -import type {CesiumWidget, Model} from '@cesium/engine'; +import '../../plugins/cesium/ngv-plugin-cesium-upload'; +import '../../plugins/cesium/ngv-plugin-cesium-model-interact'; +import type { + CesiumWidget, + DataSource, + DataSourceCollection, + Model, +} from '@cesium/engine'; import { Math as CesiumMath, Ellipsoid, HeadingPitchRoll, Transforms, + PrimitiveCollection, } from '@cesium/engine'; +import type {ViewerInitializedDetails} from '../../plugins/cesium/ngv-plugin-cesium-widget.js'; @customElement('ngv-app-permits') @localized() export class NgvAppPermits extends ABaseApp { + @state() private viewer: CesiumWidget; + private primitiveCollection: PrimitiveCollection = new PrimitiveCollection(); + private dataSourceCollection: DataSourceCollection; constructor() { super(() => import('./demoPermitConfig.js')); @@ -56,13 +68,28 @@ export class NgvAppPermits extends ABaseApp { } return html` - ) => { - this.viewer = evt.detail; - }} - > +
+ + , + ) => { + this.viewer = evt.detail.viewer; + this.viewer.scene.primitives.add(this.primitiveCollection); + this.dataSourceCollection = evt.detail.dataSourceCollection; + }} + > + +
`; } diff --git a/src/plugins/cesium/ngv-cesium-factories.ts b/src/plugins/cesium/ngv-cesium-factories.ts index 349d243..109df45 100644 --- a/src/plugins/cesium/ngv-cesium-factories.ts +++ b/src/plugins/cesium/ngv-cesium-factories.ts @@ -5,6 +5,9 @@ import { CesiumWidget, Cartesian3, Model, + DataSourceCollection, + DataSourceDisplay, + CustomDataSource, } from '@cesium/engine'; import type { @@ -202,7 +205,7 @@ export async function initCesiumWidget( container: HTMLDivElement, cesiumContext: IngvCesiumContext, modelCallback: (name: string, model: Model) => void, -): Promise { +): Promise<{viewer: CesiumWidget; dataSourceCollection: DataSourceCollection}> { modelCallback = modelCallback || (() => { @@ -248,6 +251,17 @@ export async function initCesiumWidget( Object.assign({}, cesiumContext.widgetOptions), ); + const dataSourceCollection = new DataSourceCollection(); + const dataSourceDisplay = new DataSourceDisplay({ + scene: viewer.scene, + dataSourceCollection: dataSourceCollection, + }); + const clock = viewer.clock; + // todo: check if OK + clock.onTick.addEventListener(() => { + dataSourceDisplay.update(clock.currentTime); + }); + const stuffToDo: Promise[] = []; if (cesiumContext.layers.terrain) { const name = cesiumContext.layers.terrain; @@ -374,5 +388,5 @@ export async function initCesiumWidget( duration: 0, }); - return viewer; + return {viewer, dataSourceCollection}; } diff --git a/src/plugins/cesium/ngv-plugin-cesium-model-interact.ts b/src/plugins/cesium/ngv-plugin-cesium-model-interact.ts new file mode 100644 index 0000000..a2efb9c --- /dev/null +++ b/src/plugins/cesium/ngv-plugin-cesium-model-interact.ts @@ -0,0 +1,516 @@ +import {css, html, LitElement} from 'lit'; +import type {HTMLTemplateResult, PropertyValues} from 'lit'; +import {customElement, property, state} from 'lit/decorators.js'; +import type { + CesiumWidget, + DataSource, + Model, + PrimitiveCollection, + Entity, + DataSourceCollection, + Scene, +} from '@cesium/engine'; +import { + Axis, + Cartesian3, + Color, + Matrix4, + Plane, + ScreenSpaceEventHandler, + ScreenSpaceEventType, + Transforms, + Cartographic, + Cartesian2, + Quaternion, + Matrix3, + CustomDataSource, + CallbackProperty, + Ellipsoid, + IntersectionTests, + Ray, + JulianDate, + BoundingSphere, + ArcType, + TranslationRotationScale, + HeadingPitchRoll, +} from '@cesium/engine'; + +const SIDE_PLANES: Plane[] = [ + new Plane(new Cartesian3(0, 0, 1), 0.5), + new Plane(new Cartesian3(0, 0, -1), 0.5), + new Plane(new Cartesian3(0, 1, 0), 0.5), + new Plane(new Cartesian3(0, -1, 0), 0.5), + new Plane(new Cartesian3(1, 0, 0), 0.5), + new Plane(new Cartesian3(-1, 0, 0), 0.5), +]; + +const CORNER_POINT_VECTORS = [ + new Cartesian3(0.5, 0.5, 0.5), + new Cartesian3(0.5, -0.5, 0.5), + new Cartesian3(-0.5, -0.5, 0.5), + new Cartesian3(-0.5, 0.5, 0.5), +]; + +const FACE_POINT_VECTORS = [ + new Cartesian3(0.5, 0.0, 0.0), + new Cartesian3(0.0, 0.5, 0.0), + new Cartesian3(0.0, 0.0, 0.5), +]; + +type GrabType = 'side' | 'top' | 'edge' | undefined; + +@customElement('ngv-plugin-cesium-model-interact') +export class NgvPluginCesiumModelInteract extends LitElement { + @property({type: Object}) + private viewer: CesiumWidget; + @property({type: Object}) + private primitiveCollection: PrimitiveCollection; + @property({type: Object}) + private dataSourceCollection: DataSourceCollection; + @state() + private cursor: 'default' | 'grab' | 'grabbing' | 'pointer' = 'default'; + @state() + private chosenModel: Model | undefined; + @state() + private position: Cartesian3 = new Cartesian3(); + private eventHandler: ScreenSpaceEventHandler | undefined; + private sidePlanesDataSource: DataSource | undefined; + private topDownPlanesDataSource: DataSource | undefined; + private edgeLinesDataSource: DataSource | undefined; + private moveStart: Cartesian3 = new Cartesian3(); + private moveStep: Cartesian3 = new Cartesian3(); + private pickedPointOffset: Cartesian3 = new Cartesian3(); + private dragStart: boolean = false; + private movePlane: Plane | undefined; + private grabType: GrabType; + + // todo move in UI plugin + static styles = css` + .model-info-overlay { + position: absolute; + background-color: white; + display: flex; + flex-direction: column; + z-index: 1; + margin-left: auto; + margin-right: auto; + top: 10%; + left: 10%; + transform: translate(-10%, -10%); + padding: 10px; + gap: 10px; + border-radius: 4px; + border: 1px solid rgba(0, 0, 0, 0.16); + box-shadow: 0 1px 0 rgba(0, 0, 0, 0.05); + } + + button, + input[type='text'] { + border-radius: 4px; + padding: 0 16px; + height: 40px; + cursor: pointer; + background-color: white; + border: 1px solid rgba(0, 0, 0, 0.16); + box-shadow: 0 1px 0 rgba(0, 0, 0, 0.05); + margin-right: 16px; + transition: background-color 200ms; + } + + input[type='text'] { + cursor: text; + } + `; + + initEvents(): void { + this.eventHandler = new ScreenSpaceEventHandler(this.viewer.canvas); + this.eventHandler.setInputAction( + (evt: ScreenSpaceEventHandler.MotionEvent) => this.onMouseMove(evt), + ScreenSpaceEventType.MOUSE_MOVE, + ); + this.eventHandler.setInputAction( + (evt: ScreenSpaceEventHandler.PositionedEvent) => this.onClick(evt), + ScreenSpaceEventType.LEFT_CLICK, + ); + this.eventHandler.setInputAction( + (evt: ScreenSpaceEventHandler.PositionedEvent) => this.onLeftDown(evt), + ScreenSpaceEventType.LEFT_DOWN, + ); + this.eventHandler.setInputAction( + (evt: ScreenSpaceEventHandler.PositionedEvent) => this.onLeftUp(evt), + ScreenSpaceEventType.LEFT_UP, + ); + } + + removeEvents(): void { + if (this.cursor !== 'default') { + this.viewer.canvas.style.cursor = 'default'; + } + if (this.eventHandler) { + this.eventHandler.destroy(); + this.eventHandler = null; + } + } + + createPlaneEntity(planeLocal: Plane, model: Model, color: Color) { + const modelMatrix = model.modelMatrix; + + const normalAxis = planeLocal.normal.x + ? Axis.X + : planeLocal.normal.y + ? Axis.Y + : Axis.Z; + const planeDimensions = new Cartesian2(); + const dimensions: Cartesian3 = model.id.dimensions; + let scale = new Cartesian3(); + + if (normalAxis === Axis.X) { + planeDimensions.x = dimensions.y; + planeDimensions.y = dimensions.z; + scale = new Cartesian3(dimensions.x, dimensions.y, dimensions.z); + } else if (normalAxis === Axis.Y) { + planeDimensions.x = dimensions.x; + planeDimensions.y = dimensions.z; + dimensions.clone(scale); + } else if (normalAxis === Axis.Z) { + planeDimensions.x = dimensions.x; + planeDimensions.y = dimensions.y; + scale = new Cartesian3(dimensions.y, dimensions.x, dimensions.z); + } + + const scaleMatrix = Matrix4.fromScale(scale, new Matrix4()); + const plane = Plane.transform(planeLocal, scaleMatrix); + + const dataSource = + normalAxis === Axis.Z + ? this.topDownPlanesDataSource + : this.sidePlanesDataSource; + + dataSource.entities.add({ + position: new CallbackProperty( + () => this.chosenModel.boundingSphere.center, + false, + ), + orientation: Quaternion.fromRotationMatrix( + Matrix4.getRotation(modelMatrix, new Matrix3()), + ), + plane: { + plane: plane, + dimensions: planeDimensions, + material: color.withAlpha(0.5), + outline: true, + outlineColor: Color.WHITE, + }, + }); + } + + async onClick(evt: ScreenSpaceEventHandler.PositionedEvent): Promise { + const model: Model | undefined = this.pickModel(evt.position); + if (model) { + if (!this.chosenModel) { + this.chosenModel = model; + Cartesian3.add( + Cartesian3.ZERO, + Matrix4.getTranslation( + this.chosenModel.modelMatrix, + new Cartesian3(), + ), + this.position, + ); + const centerDiff = Cartesian3.subtract( + model.boundingSphere.center, + this.position, + new Cartesian3(), + ); + SIDE_PLANES.forEach((p) => this.createPlaneEntity(p, model, Color.RED)); + + const localEdges: [Cartesian3, Cartesian3][] = []; + CORNER_POINT_VECTORS.forEach((vector, i) => { + const upPoint = vector; + const downPoint = Cartesian3.clone(upPoint, new Cartesian3()); + downPoint.z *= -1; + + const nextUpPoint = CORNER_POINT_VECTORS[(i + 1) % 4]; + const nextDownPoint = Cartesian3.clone(nextUpPoint, new Cartesian3()); + nextDownPoint.z *= -1; + + const verticalEdge: [Cartesian3, Cartesian3] = [upPoint, downPoint]; + const topEdge: [Cartesian3, Cartesian3] = [nextUpPoint, upPoint]; + const bottomEdge: [Cartesian3, Cartesian3] = [ + nextDownPoint, + downPoint, + ]; + localEdges.push(verticalEdge, topEdge, bottomEdge); + }); + localEdges.forEach((localEdge) => { + const positions = [new Cartesian3(), new Cartesian3()]; + this.edgeLinesDataSource.entities.add({ + polyline: { + show: true, + positions: new CallbackProperty(() => { + // todo improve + const modelMatrix = model.modelMatrix; + const matrix = Matrix4.fromTranslationRotationScale( + new TranslationRotationScale( + Matrix4.getTranslation(modelMatrix, new Cartesian3()), + Quaternion.fromRotationMatrix( + Matrix4.getRotation(modelMatrix, new Matrix3()), + ), + this.chosenModel.id.dimensions, + ), + ); + Cartesian3.add( + Matrix4.multiplyByPoint(matrix, localEdge[0], positions[0]), + centerDiff, + positions[0], + ); + Cartesian3.add( + Matrix4.multiplyByPoint(matrix, localEdge[1], positions[1]), + centerDiff, + positions[1], + ); + return positions; + }, false), + width: 2, + material: Color.WHITE, + arcType: ArcType.NONE, + }, + }); + }); + } + } + } + + onLeftDown(evt: ScreenSpaceEventHandler.PositionedEvent): void { + this.grabType = this.pickGrabType(evt.position); + if (this.grabType && this.cursor !== 'grabbing') { + this.viewer.canvas.style.cursor = this.cursor = 'grabbing'; + this.viewer.scene.screenSpaceCameraController.enableInputs = false; + this.viewer.scene.pickPosition(evt.position, this.moveStart); + this.dragStart = true; + + const normal = Ellipsoid.WGS84.geodeticSurfaceNormal(this.moveStart); + this.movePlane = Plane.fromPointNormal(this.moveStart, normal); + } + } + onLeftUp(): void { + if (this.cursor === 'grabbing') { + this.viewer.canvas.style.cursor = this.cursor = 'grab'; + this.viewer.scene.screenSpaceCameraController.enableInputs = true; + this.grabType = undefined; + } + } + + onMouseMove(evt: ScreenSpaceEventHandler.MotionEvent): void { + // todo split on smaller functions + // todo add scratches + if (this.grabType && this.chosenModel) { + const endPosition = this.viewer.scene.pickPosition(evt.endPosition); + if (!endPosition) return; + + if (this.grabType === 'edge') { + const dx = evt.endPosition.x - evt.startPosition.x; + const sensitivity = 0.05; + const hpr = new HeadingPitchRoll(); + hpr.heading = -dx * sensitivity; + hpr.pitch = 0; + hpr.roll = 0; + + const quaternion = new Quaternion(); + Quaternion.multiply( + Quaternion.fromRotationMatrix( + Matrix4.getRotation(this.chosenModel.modelMatrix, new Matrix3()), + quaternion, + ), + Quaternion.fromHeadingPitchRoll(hpr), + quaternion, + ); + + this.chosenModel.modelMatrix = Matrix4.fromTranslationRotationScale( + new TranslationRotationScale( + Matrix4.getTranslation( + this.chosenModel.modelMatrix, + new Cartesian3(), + ), + quaternion, + Matrix4.getScale(this.chosenModel.modelMatrix, new Cartesian3()), + ), + ); + return; + } + if (this.dragStart) { + Cartesian3.subtract( + endPosition, + Matrix4.getTranslation( + this.chosenModel.modelMatrix, + new Cartesian3(), + ), + this.pickedPointOffset, + ); + this.dragStart = false; + } + + const pickedPosition = Cartesian3.add( + this.position, + this.pickedPointOffset, + new Cartesian3(), + ); + + if (this.grabType === 'top') { + const cartPickedPosition = Cartographic.fromCartesian(pickedPosition); + const topPos = pickedPosition.clone(); + // Cartesian3.fromRadians( + // cartPickedPosition.longitude, + // cartPickedPosition.latitude, + // cartPickedPosition.height + this.chosenModel.id.max.y, + // ); + const bottomPos = Cartesian3.fromRadians( + cartPickedPosition.longitude, + cartPickedPosition.latitude, + cartPickedPosition.height - this.chosenModel.id.dimensions.y, + ); + const top2d = this.viewer.scene.cartesianToCanvasCoordinates( + topPos, + new Cartesian2(), + ); + const bottom2d = this.viewer.scene.cartesianToCanvasCoordinates( + bottomPos, + new Cartesian2(), + ); + const axis2D = Cartesian2.subtract(top2d, bottom2d, new Cartesian2()); + const scratchMouseMoveVector = Cartesian2.subtract( + evt.endPosition, + top2d, + new Cartesian2(), + ); + const scalar2d = + Cartesian2.dot(scratchMouseMoveVector, axis2D) / + Cartesian2.dot(axis2D, axis2D); + + const pixelSize = this.viewer.scene.camera.getPixelSize( + this.chosenModel.boundingSphere, + this.viewer.scene.drawingBufferWidth, + this.viewer.scene.drawingBufferHeight, + ); + const scalar3d = + pixelSize * scalar2d * this.chosenModel.id.dimensions.y; + + const upDirection = Cartesian3.normalize( + this.position, + new Cartesian3(), + ); + Cartesian3.multiplyByScalar(upDirection, scalar3d, this.moveStep); + } else if (this.grabType === 'side') { + const cameraRay = this.viewer.scene.camera.getPickRay( + evt.endPosition, + new Ray(), + ); + if (!cameraRay) { + return; + } + const nextPosition = IntersectionTests.rayPlane( + cameraRay, + this.movePlane, + ); + + if (!nextPosition) { + return; + } + + Cartesian3.subtract(nextPosition, pickedPosition, this.moveStep); + } + + Cartesian3.add(this.position, this.moveStep, this.position); + + this.chosenModel.modelMatrix = Transforms.eastNorthUpToFixedFrame( + this.position, + ); + return; + } + + const model: Model | undefined = this.pickModel(evt.endPosition); + if (this.grabType || model) { + if (this.cursor !== 'pointer' && !this.chosenModel) { + this.viewer.canvas.style.cursor = this.cursor = 'pointer'; + } else if (this.chosenModel) { + if (this.cursor !== 'grab' && this.cursor !== 'grabbing') { + this.viewer.canvas.style.cursor = this.cursor = 'grab'; + } + } + } else { + if (this.cursor !== 'default') { + this.viewer.canvas.style.cursor = this.cursor = 'default'; + } + } + } + + pickModel(position: Cartesian2): Model | undefined { + const pickedObject: {primitive: Model | undefined} = < + {primitive: Model | undefined} + >this.viewer.scene.pick(position); + return pickedObject?.primitive && + this.primitiveCollection.contains(pickedObject.primitive) + ? pickedObject.primitive + : undefined; + } + + pickGrabType(position: Cartesian2): GrabType { + const pickedObject: {id: Entity | undefined} = <{id: Entity | undefined}>( + this.viewer.scene.pick(position) + ); + if (!pickedObject?.id?.id) return undefined; + if (this.sidePlanesDataSource.entities.contains(pickedObject.id)) { + return 'side'; + } else if ( + this.topDownPlanesDataSource.entities.contains(pickedObject.id) + ) { + return 'top'; + } else if (this.edgeLinesDataSource.entities.contains(pickedObject.id)) { + return 'edge'; + } + } + + protected firstUpdated(_changedProperties: PropertyValues): void { + this.initEvents(); + // todo improve + this.dataSourceCollection + .add(new CustomDataSource()) + .then((dataSource) => (this.sidePlanesDataSource = dataSource)) + .catch((e) => console.error(e)); + this.dataSourceCollection + .add(new CustomDataSource()) + .then((dataSource) => (this.topDownPlanesDataSource = dataSource)) + .catch((e) => console.error(e)); + this.dataSourceCollection + .add(new CustomDataSource()) + .then((dataSource) => (this.edgeLinesDataSource = dataSource)) + .catch((e) => console.error(e)); + super.firstUpdated(_changedProperties); + } + + protected shouldUpdate(): boolean { + return !!this.viewer && !!this.primitiveCollection; + } + render(): HTMLTemplateResult | string { + if (!this.chosenModel) return ''; + // todo move in UI plugin + return html`
+ ${JSON.stringify(this.position)} + +
`; + } + + disconnectedCallback(): void { + this.removeEvents(); + super.disconnectedCallback(); + } +} diff --git a/src/plugins/cesium/ngv-plugin-cesium-upload.ts b/src/plugins/cesium/ngv-plugin-cesium-upload.ts new file mode 100644 index 0000000..01e319e --- /dev/null +++ b/src/plugins/cesium/ngv-plugin-cesium-upload.ts @@ -0,0 +1,179 @@ +import type {CesiumWidget, Model, PrimitiveCollection} from '@cesium/engine'; +import { + Cartesian3, + Cartographic, + HeadingPitchRoll, + HeightReference, + Math as CesiumMath, + Matrix4, + Quaternion, + ScreenSpaceEventHandler, + ScreenSpaceEventType, + Transforms, + TranslationRotationScale, +} from '@cesium/engine'; +import type {HTMLTemplateResult} from 'lit'; +import {html, LitElement} from 'lit'; +import {customElement, property} from 'lit/decorators.js'; +import '../ui/ngv-upload.js'; +import {instantiateModel} from './ngv-cesium-factories.js'; + +const cartographicScratch = new Cartographic(); + +@customElement('ngv-plugin-cesium-upload') +export class NgvPluginCesiumUpload extends LitElement { + @property({type: Object}) + private viewer: CesiumWidget; + @property({type: Object}) + private primitiveCollection: PrimitiveCollection; + private eventHandler: ScreenSpaceEventHandler | null = null; + private uploadedModel: Model | undefined; + + async upload(url: string): Promise { + const response = await fetch(url); + const arrayBuffer = await response.arrayBuffer(); + const glb = new Uint8Array(arrayBuffer); + + // Extract JSON chunk and binary chunk + const jsonLength = new DataView(arrayBuffer, 12, 4).getUint32(0, true); + const jsonChunk = new TextDecoder().decode( + glb.subarray(20, 20 + jsonLength), + ); + const json = JSON.parse(jsonChunk); + console.log(json); + + // Now access the binary data buffer view for vertex positions + const bufferView = + json.bufferViews[ + json.accessors[json.meshes[0].primitives[0].attributes.POSITION] + .bufferView + ]; + const byteOffset = bufferView.byteOffset; + const byteLength = bufferView.byteLength; + + // Extract the vertex data from the binary chunk (after JSON chunk) + const binaryData = new Float32Array( + arrayBuffer, + byteOffset + 20 + jsonLength, + byteLength / Float32Array.BYTES_PER_ELEMENT, + ); + + // Initialize AABB min/max values + const min = new Cartesian3( + Number.POSITIVE_INFINITY, + Number.POSITIVE_INFINITY, + Number.POSITIVE_INFINITY, + ); + const max = new Cartesian3( + Number.NEGATIVE_INFINITY, + Number.NEGATIVE_INFINITY, + Number.NEGATIVE_INFINITY, + ); + + // Iterate over the vertex positions and compute the AABB + for (let i = 0; i < binaryData.length; i += 3) { + const vertex = new Cartesian3( + binaryData[i], + binaryData[i + 2], + binaryData[i + 1], + ); + + // Update AABB min and max + Cartesian3.minimumByComponent(min, vertex, min); + Cartesian3.maximumByComponent(max, vertex, max); + } + + // Compute dimensions (width, height, depth) + const dimensions = Cartesian3.subtract(max, min, new Cartesian3()); + console.log('AABB Min:', min); + console.log('AABB Max:', max); + console.log('Dimensions (Width, Height, Depth):', dimensions); + + const modelOrientation = [90, 0, 0]; + const modelMatrix = Matrix4.fromTranslationRotationScale( + new TranslationRotationScale( + Cartesian3.ZERO, + Quaternion.fromHeadingPitchRoll( + new HeadingPitchRoll(...modelOrientation.map(CesiumMath.toRadians)), + ), + ), + ); + + // const modelMatrix = new Matrix4(); + // + // json.nodes.forEach((node) => { + // if (!node.matrix) return; + // const matrix = new Matrix4(...node.matrix); + // Matrix4.add(modelMatrix, matrix, modelMatrix); + // }); + + this.uploadedModel = await instantiateModel({ + type: 'model', + options: { + url, + scene: this.viewer.scene, + modelMatrix, + id: { + dimensions, + min, + max, + }, + }, + }); + this.primitiveCollection.add(this.uploadedModel); + this.viewer.scene.requestRender(); + this.showControls(); + } + + showControls(): void { + this.eventHandler = new ScreenSpaceEventHandler(this.viewer.canvas); + this.eventHandler.setInputAction( + (evt: ScreenSpaceEventHandler.MotionEvent) => this.onMouseMove(evt), + ScreenSpaceEventType.MOUSE_MOVE, + ); + this.eventHandler.setInputAction( + this.onClick.bind(this), + ScreenSpaceEventType.LEFT_CLICK, + ); + this.viewer.canvas.style.cursor = 'move'; + } + + onClick(): void { + this.viewer.canvas.style.cursor = 'default'; + this.eventHandler.destroy(); + this.eventHandler = null; + } + + onMouseMove(event: ScreenSpaceEventHandler.MotionEvent): void { + const position = this.viewer.scene.pickPosition(event.endPosition); + const cart = Cartographic.fromCartesian( + position, + this.viewer.scene.ellipsoid, + cartographicScratch, + ); + const altitude = this.viewer.scene.globe.getHeight(cart); + cart.height = altitude || 0; + Cartographic.toCartesian(cart, this.viewer.scene.ellipsoid, position); + + this.uploadedModel.modelMatrix = + Transforms.eastNorthUpToFixedFrame(position); + } + + protected shouldUpdate(): boolean { + return !!this.viewer && !!this.primitiveCollection; + } + + render(): HTMLTemplateResult { + return html` `; + } +} + +declare global { + interface HTMLElementTagNameMap { + 'ngv-plugin-cesium-upload': NgvPluginCesiumUpload; + } +} diff --git a/src/plugins/cesium/ngv-plugin-cesium-widget.ts b/src/plugins/cesium/ngv-plugin-cesium-widget.ts index 7bc098a..e247503 100644 --- a/src/plugins/cesium/ngv-plugin-cesium-widget.ts +++ b/src/plugins/cesium/ngv-plugin-cesium-widget.ts @@ -5,12 +5,18 @@ import {customElement, property, query} from 'lit/decorators.js'; // @ts-expect-error Vite specific ?inline parameter import style from '@cesium/engine/Source/Widget/CesiumWidget.css?inline'; import type {IngvCesiumContext} from '../../interfaces/cesium/ingv-cesium-context.js'; -import type {CesiumWidget, Model} from '@cesium/engine'; +import type {CesiumWidget, DataSourceCollection, Model} from '@cesium/engine'; import {initCesiumWidget} from './ngv-cesium-factories.js'; +export type ViewerInitializedDetails = { + viewer: CesiumWidget; + dataSourceCollection: DataSourceCollection; +}; + @customElement('ngv-plugin-cesium-widget') export class NgvPluginCesiumWidget extends LitElement { public viewer: CesiumWidget; + public dataSourceCollection: DataSourceCollection; static styles = [ unsafeCSS(style), @@ -41,14 +47,19 @@ export class NgvPluginCesiumWidget extends LitElement { private element: HTMLDivElement; private async initCesiumViewer(): Promise { - this.viewer = await initCesiumWidget( + const {viewer, dataSourceCollection} = await initCesiumWidget( this.element, this.cesiumContext, this.modelCallback, ); + this.viewer = viewer; + this.dataSourceCollection = dataSourceCollection; this.dispatchEvent( - new CustomEvent('viewerInitialized', { - detail: this.viewer, + new CustomEvent('viewerInitialized', { + detail: { + viewer, + dataSourceCollection, + }, }), ); } diff --git a/src/plugins/ui/ngv-upload.ts b/src/plugins/ui/ngv-upload.ts new file mode 100644 index 0000000..1fa8ff4 --- /dev/null +++ b/src/plugins/ui/ngv-upload.ts @@ -0,0 +1,177 @@ +import {customElement, property, query, state} from 'lit/decorators.js'; +import type {HTMLTemplateResult} from 'lit'; +import {css, html, LitElement} from 'lit'; +import {classMap} from 'lit/directives/class-map.js'; + +export type NgvUploadOptions = { + accept?: string; + mainBtnText?: string; + urlInput?: boolean; + urlPlaceholderText?: string; + fileInput?: boolean; + uploadBtnText?: string; +}; + +const defaultOptions: NgvUploadOptions = { + mainBtnText: 'Upload', + uploadBtnText: 'Upload', + urlInput: true, + fileInput: true, + urlPlaceholderText: 'Put file URL here', + accept: '*/*', +}; + +/** + * TODO + * * type check + * * error handling + * * multiple files upload + * * drag & drop ? + * * URL input + */ +@customElement('ngv-upload') +export class NgvUpload extends LitElement { + @property({type: Object}) options: NgvUploadOptions; + @state() showPopup = false; + @state() fileUrl: string | undefined; + @query('input[type="file"]') fileInput: HTMLInputElement; + + static styles = css` + .main-btn { + margin: 10px; + } + + .upload-container { + width: 100vw; + height: 100vh; + top: 0; + left: 0; + z-index: 1; + position: absolute; + } + + .upload-container.hidden { + display: none; + } + + .upload-backdrop { + background-color: black; + opacity: 50%; + position: absolute; + width: 100%; + height: 100%; + } + + .upload-popup { + position: absolute; + background-color: white; + display: flex; + flex-direction: column; + z-index: 2; + margin-left: auto; + margin-right: auto; + top: 10%; + left: 50%; + transform: translate(-50%, -10%); + padding: 10px; + gap: 10px; + border-radius: 4px; + border: 1px solid rgba(0, 0, 0, 0.16); + box-shadow: 0 1px 0 rgba(0, 0, 0, 0.05); + } + + button, + input[type='text'], + input[type='file']::file-selector-button { + border-radius: 4px; + padding: 0 16px; + height: 40px; + cursor: pointer; + background-color: white; + border: 1px solid rgba(0, 0, 0, 0.16); + box-shadow: 0 1px 0 rgba(0, 0, 0, 0.05); + margin-right: 16px; + transition: background-color 200ms; + } + + input[type='text'] { + cursor: text; + } + + input[type='file'] { + background-color: #f3f4f6; + cursor: pointer; + } + + input[type='file']::file-selector-button:active { + background-color: #e5e7eb; + } + `; + + async onFileUpload(file: File): Promise { + const arrayBufer = await file.arrayBuffer(); + const blob = new Blob([arrayBufer]); + this.fileUrl = URL.createObjectURL(blob); + } + + upload(): void { + this.dispatchEvent( + new CustomEvent('uploaded', {detail: this.fileUrl}), + ); + this.fileInput.value = ''; + this.showPopup = false; + } + + override willUpdate(): void { + const options: NgvUploadOptions = {...defaultOptions}; + if (this.options) { + Object.keys((key: keyof NgvUploadOptions) => { + if (this.options[key] !== undefined && this.options[key] !== null) { + options[key] = this.options[key]; + } + }); + } + this.options = options; + } + + render(): HTMLTemplateResult { + return html` +
+
+ { + const target = e.target; + if (!target || !target.files?.length) return; + await this.onFileUpload(target.files[0]); + }} + /> + + +
+
+
`; + } +} + +declare global { + interface HTMLElementTagNameMap { + 'ngv-upload': NgvUpload; + } +} From 2194867262cf8c36020aa42cedda9c6247c7bdda Mon Sep 17 00:00:00 2001 From: vladyslavtk Date: Wed, 30 Oct 2024 15:22:47 +0200 Subject: [PATCH 02/15] fixes and adaptations --- .../illumination/ngv-main-illumination.ts | 6 +- src/apps/permits/index.ts | 32 +++---- .../ngv-plugin-cesium-model-interact.ts | 82 ++++++++++++----- .../cesium/ngv-plugin-cesium-upload.ts | 13 ++- src/plugins/ui/ngv-upload.ts | 91 ++++++------------- 5 files changed, 114 insertions(+), 110 deletions(-) diff --git a/src/apps/illumination/ngv-main-illumination.ts b/src/apps/illumination/ngv-main-illumination.ts index 00a77c5..d1235e2 100644 --- a/src/apps/illumination/ngv-main-illumination.ts +++ b/src/apps/illumination/ngv-main-illumination.ts @@ -6,6 +6,7 @@ import {type CesiumWidget} from '@cesium/engine'; import type {IIlluminationConfig} from './ingv-config-illumination.js'; import '../../plugins/cesium/ngv-plugin-cesium-widget.js'; +import {ViewerInitializedDetails} from '../../plugins/cesium/ngv-plugin-cesium-widget.js'; @customElement('ngv-main-illumination') export class NgvMainIllumination extends LitElement { @@ -23,6 +24,7 @@ export class NgvMainIllumination extends LitElement { `; updated(): void { + if (!this.viewer?.clock) return; this.viewer.clock.currentTime = this.date; console.log(this.date.toString()); } @@ -32,8 +34,8 @@ export class NgvMainIllumination extends LitElement {
) => { - this.viewer = evt.detail; + @viewerInitialized=${(evt: CustomEvent) => { + this.viewer = evt.detail.viewer; this.updated(); }} > diff --git a/src/apps/permits/index.ts b/src/apps/permits/index.ts index 18c3b50..6d4c537 100644 --- a/src/apps/permits/index.ts +++ b/src/apps/permits/index.ts @@ -13,12 +13,7 @@ import type {IPermitsConfig} from './ingv-config-permits.js'; import '../../plugins/cesium/ngv-plugin-cesium-widget'; import '../../plugins/cesium/ngv-plugin-cesium-upload'; import '../../plugins/cesium/ngv-plugin-cesium-model-interact'; -import type { - CesiumWidget, - DataSource, - DataSourceCollection, - Model, -} from '@cesium/engine'; +import type {CesiumWidget, DataSourceCollection, Model} from '@cesium/engine'; import { Math as CesiumMath, @@ -68,28 +63,29 @@ export class NgvAppPermits extends ABaseApp { } return html` -
+
- , - ) => { - this.viewer = evt.detail.viewer; - this.viewer.scene.primitives.add(this.primitiveCollection); - this.dataSourceCollection = evt.detail.dataSourceCollection; - }} - >
+ ) => { + this.viewer = evt.detail.viewer; + this.viewer.scene.primitives.add(this.primitiveCollection); + this.dataSourceCollection = evt.detail.dataSourceCollection; + }} + > `; } diff --git a/src/plugins/cesium/ngv-plugin-cesium-model-interact.ts b/src/plugins/cesium/ngv-plugin-cesium-model-interact.ts index a2efb9c..dad34f7 100644 --- a/src/plugins/cesium/ngv-plugin-cesium-model-interact.ts +++ b/src/plugins/cesium/ngv-plugin-cesium-model-interact.ts @@ -8,7 +8,6 @@ import type { PrimitiveCollection, Entity, DataSourceCollection, - Scene, } from '@cesium/engine'; import { Axis, @@ -73,6 +72,8 @@ export class NgvPluginCesiumModelInteract extends LitElement { private chosenModel: Model | undefined; @state() private position: Cartesian3 = new Cartesian3(); + @state() + private models: Model[] = []; private eventHandler: ScreenSpaceEventHandler | undefined; private sidePlanesDataSource: DataSource | undefined; private topDownPlanesDataSource: DataSource | undefined; @@ -86,17 +87,14 @@ export class NgvPluginCesiumModelInteract extends LitElement { // todo move in UI plugin static styles = css` - .model-info-overlay { - position: absolute; + .model-list, + .model-info { background-color: white; display: flex; flex-direction: column; z-index: 1; margin-left: auto; margin-right: auto; - top: 10%; - left: 10%; - transform: translate(-10%, -10%); padding: 10px; gap: 10px; border-radius: 4px; @@ -104,6 +102,18 @@ export class NgvPluginCesiumModelInteract extends LitElement { box-shadow: 0 1px 0 rgba(0, 0, 0, 0.05); } + .model-item { + text-overflow: ellipsis; + display: flex; + align-items: center; + column-gap: 10px; + } + + .model-item span { + overflow: hidden; + text-overflow: ellipsis; + } + button, input[type='text'] { border-radius: 4px; @@ -113,7 +123,6 @@ export class NgvPluginCesiumModelInteract extends LitElement { background-color: white; border: 1px solid rgba(0, 0, 0, 0.16); box-shadow: 0 1px 0 rgba(0, 0, 0, 0.05); - margin-right: 16px; transition: background-color 200ms; } @@ -140,6 +149,13 @@ export class NgvPluginCesiumModelInteract extends LitElement { (evt: ScreenSpaceEventHandler.PositionedEvent) => this.onLeftUp(evt), ScreenSpaceEventType.LEFT_UP, ); + + this.primitiveCollection.primitiveAdded.addEventListener(() => + this.onPrimitivesChanged(), + ); + this.primitiveCollection.primitiveRemoved.addEventListener(() => + this.onPrimitivesChanged(), + ); } removeEvents(): void { @@ -152,6 +168,13 @@ export class NgvPluginCesiumModelInteract extends LitElement { } } + onPrimitivesChanged(): void { + this.models = []; + for (let i = 0; i < this.primitiveCollection.length; i++) { + this.models.push(this.primitiveCollection.get(i)); + } + } + createPlaneEntity(planeLocal: Plane, model: Model, color: Color) { const modelMatrix = model.modelMatrix; @@ -492,21 +515,38 @@ export class NgvPluginCesiumModelInteract extends LitElement { return !!this.viewer && !!this.primitiveCollection; } render(): HTMLTemplateResult | string { - if (!this.chosenModel) return ''; + if (!this.chosenModel && !this.models?.length) return ''; // todo move in UI plugin - return html`
- ${JSON.stringify(this.position)} - -
`; + return this.chosenModel + ? html`
+ ${this.chosenModel.id.name} + +
` + : html`

Uploaded models:

+
+ ${this.models.map( + (m) => + html`
+ ${m.id.name} + +
`, + )} +
`; } disconnectedCallback(): void { diff --git a/src/plugins/cesium/ngv-plugin-cesium-upload.ts b/src/plugins/cesium/ngv-plugin-cesium-upload.ts index 01e319e..e7ecf71 100644 --- a/src/plugins/cesium/ngv-plugin-cesium-upload.ts +++ b/src/plugins/cesium/ngv-plugin-cesium-upload.ts @@ -17,6 +17,7 @@ import {html, LitElement} from 'lit'; import {customElement, property} from 'lit/decorators.js'; import '../ui/ngv-upload.js'; import {instantiateModel} from './ngv-cesium-factories.js'; +import {FileUploadDetails} from '../ui/ngv-upload.js'; const cartographicScratch = new Cartographic(); @@ -29,8 +30,8 @@ export class NgvPluginCesiumUpload extends LitElement { private eventHandler: ScreenSpaceEventHandler | null = null; private uploadedModel: Model | undefined; - async upload(url: string): Promise { - const response = await fetch(url); + async upload(fileDetails: FileUploadDetails): Promise { + const response = await fetch(fileDetails.url); const arrayBuffer = await response.arrayBuffer(); const glb = new Uint8Array(arrayBuffer); @@ -85,9 +86,6 @@ export class NgvPluginCesiumUpload extends LitElement { // Compute dimensions (width, height, depth) const dimensions = Cartesian3.subtract(max, min, new Cartesian3()); - console.log('AABB Min:', min); - console.log('AABB Max:', max); - console.log('Dimensions (Width, Height, Depth):', dimensions); const modelOrientation = [90, 0, 0]; const modelMatrix = Matrix4.fromTranslationRotationScale( @@ -110,10 +108,11 @@ export class NgvPluginCesiumUpload extends LitElement { this.uploadedModel = await instantiateModel({ type: 'model', options: { - url, + url: fileDetails.url, scene: this.viewer.scene, modelMatrix, id: { + name: fileDetails.name, dimensions, min, max, @@ -165,7 +164,7 @@ export class NgvPluginCesiumUpload extends LitElement { render(): HTMLTemplateResult { return html` { this.upload(evt.detail); }}" >`; diff --git a/src/plugins/ui/ngv-upload.ts b/src/plugins/ui/ngv-upload.ts index 1fa8ff4..49716ee 100644 --- a/src/plugins/ui/ngv-upload.ts +++ b/src/plugins/ui/ngv-upload.ts @@ -1,7 +1,6 @@ import {customElement, property, query, state} from 'lit/decorators.js'; import type {HTMLTemplateResult} from 'lit'; import {css, html, LitElement} from 'lit'; -import {classMap} from 'lit/directives/class-map.js'; export type NgvUploadOptions = { accept?: string; @@ -21,6 +20,11 @@ const defaultOptions: NgvUploadOptions = { accept: '*/*', }; +export type FileUploadDetails = { + url: string; + name: string; +}; + /** * TODO * * type check @@ -33,46 +37,16 @@ const defaultOptions: NgvUploadOptions = { export class NgvUpload extends LitElement { @property({type: Object}) options: NgvUploadOptions; @state() showPopup = false; - @state() fileUrl: string | undefined; + @state() fileDetails: FileUploadDetails | undefined; @query('input[type="file"]') fileInput: HTMLInputElement; static styles = css` - .main-btn { - margin: 10px; - } - - .upload-container { - width: 100vw; - height: 100vh; - top: 0; - left: 0; - z-index: 1; - position: absolute; - } - - .upload-container.hidden { - display: none; - } - - .upload-backdrop { - background-color: black; - opacity: 50%; - position: absolute; - width: 100%; - height: 100%; - } - .upload-popup { - position: absolute; background-color: white; display: flex; flex-direction: column; - z-index: 2; margin-left: auto; margin-right: auto; - top: 10%; - left: 50%; - transform: translate(-50%, -10%); padding: 10px; gap: 10px; border-radius: 4px; @@ -111,12 +85,17 @@ export class NgvUpload extends LitElement { async onFileUpload(file: File): Promise { const arrayBufer = await file.arrayBuffer(); const blob = new Blob([arrayBufer]); - this.fileUrl = URL.createObjectURL(blob); + this.fileDetails = { + url: URL.createObjectURL(blob), + name: file.name, + }; } upload(): void { this.dispatchEvent( - new CustomEvent('uploaded', {detail: this.fileUrl}), + new CustomEvent('uploaded', { + detail: this.fileDetails, + }), ); this.fileInput.value = ''; this.showPopup = false; @@ -135,38 +114,26 @@ export class NgvUpload extends LitElement { } render(): HTMLTemplateResult { - return html` -
-
- { - const target = e.target; - if (!target || !target.files?.length) return; - await this.onFileUpload(target.files[0]); - }} - /> - - -
-
-
`; + +
`; } } From 9416bb7eb9073642d118f5e837973fb12b4a4a1b Mon Sep 17 00:00:00 2001 From: vladyslavtk Date: Wed, 30 Oct 2024 17:49:19 +0200 Subject: [PATCH 03/15] temporary solution for models storing --- src/apps/permits/localStore.ts | 154 ++++++++++++++++++ .../ngv-plugin-cesium-model-interact.ts | 65 +++++++- .../cesium/ngv-plugin-cesium-upload.ts | 12 +- 3 files changed, 221 insertions(+), 10 deletions(-) create mode 100644 src/apps/permits/localStore.ts diff --git a/src/apps/permits/localStore.ts b/src/apps/permits/localStore.ts new file mode 100644 index 0000000..408c550 --- /dev/null +++ b/src/apps/permits/localStore.ts @@ -0,0 +1,154 @@ +// todo Just for easier testing. Better place should be find and structure improved. + +import { + Cartesian3, + Matrix3, + Matrix4, + Model, + Quaternion, + TranslationRotationScale, +} from '@cesium/engine'; +import {instantiateModel} from '../../plugins/cesium/ngv-cesium-factories.js'; + +const DB_NAME = 'uploadedModelsStore'; + +export function storeBlobInIndexedDB(blob: Blob, name: string): Promise { + return new Promise((resolve, reject) => { + const request = indexedDB.open(DB_NAME, 2); + + request.onupgradeneeded = () => { + const db = request.result; + if (!db.objectStoreNames.contains('files')) { + db.createObjectStore('files'); + } + }; + + request.onsuccess = () => { + const db = request.result; + const transaction = db.transaction('files', 'readwrite'); + const store = transaction.objectStore('files'); + store.put(blob, name); + transaction.oncomplete = () => { + console.log('Blob stored successfully'); + resolve(); + }; + transaction.onerror = () => { + reject(new Error('Error storing blob')); + }; + }; + + request.onerror = (event) => console.error('IndexedDB error:', event); + }); +} + +export function getBlobFromIndexedDB(name: string): Promise { + return new Promise((resolve, reject) => { + const request = indexedDB.open(DB_NAME, 2); + + request.onupgradeneeded = () => { + const db = request.result; + if (!db.objectStoreNames.contains('files')) { + db.createObjectStore('files'); + } + }; + + request.onsuccess = () => { + const db = request.result; + const transaction = db.transaction('files', 'readonly'); + const store = transaction.objectStore('files'); + const getRequest = >store.get(name); + + getRequest.onsuccess = () => { + const blob = getRequest.result; + if (blob) { + resolve(blob); // Return the Blob object directly + } else { + reject(new Error('Blob not found')); + } + }; + + getRequest.onerror = () => + reject(new Error('Error retrieving blob from IndexedDB')); + }; + + request.onerror = () => reject(new Error('Error opening IndexedDB')); + }); +} + +export function deleteFromIndexedDB(name: string): Promise { + return new Promise((resolve, reject) => { + const request = indexedDB.open(DB_NAME, 2); + + request.onsuccess = () => { + const db = request.result; + const transaction = db.transaction('files', 'readwrite'); + const store = transaction.objectStore('files'); + const deleteRequest = store.delete(name); + + deleteRequest.onsuccess = () => { + console.log(`Item with key "${name}" deleted successfully`); + resolve(); + }; + + deleteRequest.onerror = () => { + console.error('Error deleting item from IndexedDB'); + reject(new Error('Error deleting item from IndexedDB')); + }; + }; + + request.onerror = () => { + console.error('Error opening IndexedDB'); + reject(new Error('Error opening IndexedDB')); + }; + }); +} + +export type StoredModel = { + name: string; + min: number[]; + max: number[]; + dimensions: number[]; + translation: number[]; + rotation: number[]; + scale: number[]; +}; + +export const STORE_KEY = 'localStoreModels'; + +export function updateModelsInLocalStore(models: Model[]): void { + const localStoreModels: StoredModel[] = []; + models.forEach((model) => { + const translation = Matrix4.getTranslation( + model.modelMatrix, + new Cartesian3(), + ); + const scale = Matrix4.getScale(model.modelMatrix, new Cartesian3()); + const rotation = Quaternion.fromRotationMatrix( + Matrix4.getRotation(model.modelMatrix, new Matrix3()), + ); + localStoreModels.push({ + name: model.id.name, + min: [model.id.min.x, model.id.min.y, model.id.min.z], + max: [model.id.max.x, model.id.max.y, model.id.max.z], + dimensions: [ + model.id.dimensions.x, + model.id.dimensions.y, + model.id.dimensions.z, + ], + translation: [translation.x, translation.y, translation.z], + rotation: [rotation.x, rotation.y, rotation.z, rotation.w], + scale: [scale.x, scale.y, scale.z], + }); + }); + localStorage.setItem(STORE_KEY, JSON.stringify(localStoreModels)); +} + +export function getStoredModels(): StoredModel[] { + if (!localStorage.getItem(STORE_KEY)) return []; + try { + return JSON.parse(localStorage.getItem(STORE_KEY)); + } catch (e) { + console.error('Not possible to parse models from local storage', e); + return []; + } +} diff --git a/src/plugins/cesium/ngv-plugin-cesium-model-interact.ts b/src/plugins/cesium/ngv-plugin-cesium-model-interact.ts index dad34f7..761a172 100644 --- a/src/plugins/cesium/ngv-plugin-cesium-model-interact.ts +++ b/src/plugins/cesium/ngv-plugin-cesium-model-interact.ts @@ -32,7 +32,16 @@ import { ArcType, TranslationRotationScale, HeadingPitchRoll, + Math as CesiumMath, } from '@cesium/engine'; +import {instantiateModel} from './ngv-cesium-factories.js'; +import { + deleteFromIndexedDB, + getBlobFromIndexedDB, + getStoredModels, + StoredModel, + updateModelsInLocalStore, +} from '../../apps/permits/localStore.js'; const SIDE_PLANES: Plane[] = [ new Plane(new Cartesian3(0, 0, 1), 0.5), @@ -150,12 +159,14 @@ export class NgvPluginCesiumModelInteract extends LitElement { ScreenSpaceEventType.LEFT_UP, ); - this.primitiveCollection.primitiveAdded.addEventListener(() => - this.onPrimitivesChanged(), - ); - this.primitiveCollection.primitiveRemoved.addEventListener(() => - this.onPrimitivesChanged(), - ); + this.primitiveCollection.primitiveAdded.addEventListener(() => { + console.log('primitiveAdded'); + this.onPrimitivesChanged(); + }); + this.primitiveCollection.primitiveRemoved.addEventListener((p) => { + deleteFromIndexedDB(p.id.name); + this.onPrimitivesChanged(); + }); } removeEvents(): void { @@ -173,9 +184,10 @@ export class NgvPluginCesiumModelInteract extends LitElement { for (let i = 0; i < this.primitiveCollection.length; i++) { this.models.push(this.primitiveCollection.get(i)); } + updateModelsInLocalStore(this.models); } - createPlaneEntity(planeLocal: Plane, model: Model, color: Color) { + createPlaneEntity(planeLocal: Plane, model: Model, color: Color): void { const modelMatrix = model.modelMatrix; const normalAxis = planeLocal.normal.x @@ -493,8 +505,42 @@ export class NgvPluginCesiumModelInteract extends LitElement { } } - protected firstUpdated(_changedProperties: PropertyValues): void { - this.initEvents(); + async firstUpdated(_changedProperties: PropertyValues): Promise { + // todo improve code + const models = getStoredModels(); + if (models) { + await Promise.all( + models.map(async (m: StoredModel) => { + const blob = await getBlobFromIndexedDB(m.name); + const model = await instantiateModel({ + type: 'model', + options: { + url: URL.createObjectURL(blob), + scene: this.viewer.scene, + modelMatrix: Matrix4.fromTranslationRotationScale( + new TranslationRotationScale( + new Cartesian3(...m.translation), + new Quaternion(...m.rotation), + new Cartesian3(...m.scale), + ), + ), + id: { + name: m.name, + min: new Cartesian3(...Object.values(m.min)), + max: new Cartesian3(...Object.values(m.max)), + dimensions: new Cartesian3(...Object.values(m.dimensions)), + }, + }, + }); + this.primitiveCollection.add(model); + }), + ); + this.onPrimitivesChanged(); + this.initEvents(); + this.viewer.scene.requestRender(); + } else { + this.initEvents(); + } // todo improve this.dataSourceCollection .add(new CustomDataSource()) @@ -526,6 +572,7 @@ export class NgvPluginCesiumModelInteract extends LitElement { this.sidePlanesDataSource.entities.removeAll(); this.topDownPlanesDataSource.entities.removeAll(); this.edgeLinesDataSource.entities.removeAll(); + this.onPrimitivesChanged(); }}" > Done diff --git a/src/plugins/cesium/ngv-plugin-cesium-upload.ts b/src/plugins/cesium/ngv-plugin-cesium-upload.ts index e7ecf71..206719e 100644 --- a/src/plugins/cesium/ngv-plugin-cesium-upload.ts +++ b/src/plugins/cesium/ngv-plugin-cesium-upload.ts @@ -18,6 +18,10 @@ import {customElement, property} from 'lit/decorators.js'; import '../ui/ngv-upload.js'; import {instantiateModel} from './ngv-cesium-factories.js'; import {FileUploadDetails} from '../ui/ngv-upload.js'; +import { + storeBlobInIndexedDB, + updateModelsInLocalStore, +} from '../../apps/permits/localStore.js'; const cartographicScratch = new Cartographic(); @@ -41,7 +45,6 @@ export class NgvPluginCesiumUpload extends LitElement { glb.subarray(20, 20 + jsonLength), ); const json = JSON.parse(jsonChunk); - console.log(json); // Now access the binary data buffer view for vertex positions const bufferView = @@ -119,6 +122,7 @@ export class NgvPluginCesiumUpload extends LitElement { }, }, }); + await storeBlobInIndexedDB(new Blob([arrayBuffer]), fileDetails.name); this.primitiveCollection.add(this.uploadedModel); this.viewer.scene.requestRender(); this.showControls(); @@ -141,6 +145,12 @@ export class NgvPluginCesiumUpload extends LitElement { this.viewer.canvas.style.cursor = 'default'; this.eventHandler.destroy(); this.eventHandler = null; + // todo improve + const models: Model[] = []; + for (let i = 0; i < this.primitiveCollection.length; i++) { + models.push(this.primitiveCollection.get(i)); + } + updateModelsInLocalStore(models); } onMouseMove(event: ScreenSpaceEventHandler.MotionEvent): void { From 6a3b342c412ddf4dc4e8cadc62e480c4c07ae98b Mon Sep 17 00:00:00 2001 From: vladyslavtk Date: Tue, 5 Nov 2024 22:17:53 +0200 Subject: [PATCH 04/15] rotation fixes --- .../ngv-plugin-cesium-model-interact.ts | 170 ++++++++---------- 1 file changed, 74 insertions(+), 96 deletions(-) diff --git a/src/plugins/cesium/ngv-plugin-cesium-model-interact.ts b/src/plugins/cesium/ngv-plugin-cesium-model-interact.ts index 761a172..b184801 100644 --- a/src/plugins/cesium/ngv-plugin-cesium-model-interact.ts +++ b/src/plugins/cesium/ngv-plugin-cesium-model-interact.ts @@ -27,19 +27,17 @@ import { Ellipsoid, IntersectionTests, Ray, - JulianDate, - BoundingSphere, ArcType, TranslationRotationScale, HeadingPitchRoll, - Math as CesiumMath, + JulianDate, } from '@cesium/engine'; import {instantiateModel} from './ngv-cesium-factories.js'; +import type {StoredModel} from '../../apps/permits/localStore.js'; import { deleteFromIndexedDB, getBlobFromIndexedDB, getStoredModels, - StoredModel, updateModelsInLocalStore, } from '../../apps/permits/localStore.js'; @@ -59,6 +57,20 @@ const CORNER_POINT_VECTORS = [ new Cartesian3(-0.5, 0.5, 0.5), ]; +const LOCAL_EDGES: [Cartesian3, Cartesian3][] = []; +CORNER_POINT_VECTORS.forEach((vector, i) => { + const upPoint = vector; + const downPoint = Cartesian3.clone(upPoint, new Cartesian3()); + downPoint.z *= -1; + const nextUpPoint = CORNER_POINT_VECTORS[(i + 1) % 4]; + const nextDownPoint = Cartesian3.clone(nextUpPoint, new Cartesian3()); + nextDownPoint.z *= -1; + const verticalEdge: [Cartesian3, Cartesian3] = [upPoint, downPoint]; + const topEdge: [Cartesian3, Cartesian3] = [nextUpPoint, upPoint]; + const bottomEdge: [Cartesian3, Cartesian3] = [nextDownPoint, downPoint]; + LOCAL_EDGES.push(verticalEdge, topEdge, bottomEdge); +}); + const FACE_POINT_VECTORS = [ new Cartesian3(0.5, 0.0, 0.0), new Cartesian3(0.0, 0.5, 0.0), @@ -160,7 +172,6 @@ export class NgvPluginCesiumModelInteract extends LitElement { ); this.primitiveCollection.primitiveAdded.addEventListener(() => { - console.log('primitiveAdded'); this.onPrimitivesChanged(); }); this.primitiveCollection.primitiveRemoved.addEventListener((p) => { @@ -188,8 +199,6 @@ export class NgvPluginCesiumModelInteract extends LitElement { } createPlaneEntity(planeLocal: Plane, model: Model, color: Color): void { - const modelMatrix = model.modelMatrix; - const normalAxis = planeLocal.normal.x ? Axis.X : planeLocal.normal.y @@ -226,8 +235,12 @@ export class NgvPluginCesiumModelInteract extends LitElement { () => this.chosenModel.boundingSphere.center, false, ), - orientation: Quaternion.fromRotationMatrix( - Matrix4.getRotation(modelMatrix, new Matrix3()), + orientation: new CallbackProperty( + () => + Quaternion.fromRotationMatrix( + Matrix4.getRotation(this.chosenModel.modelMatrix, new Matrix3()), + ), + false, ), plane: { plane: plane, @@ -239,79 +252,49 @@ export class NgvPluginCesiumModelInteract extends LitElement { }); } + createEdge(localEdge: Cartesian3[]): void { + // todo improve + const positions = [new Cartesian3(), new Cartesian3()]; + this.edgeLinesDataSource.entities.add({ + polyline: { + show: true, + positions: new CallbackProperty(() => { + const modelMatrix = this.chosenModel.modelMatrix; + const matrix = Matrix4.fromTranslationRotationScale( + new TranslationRotationScale( + Matrix4.getTranslation(modelMatrix, new Cartesian3()), + Quaternion.fromRotationMatrix( + Matrix4.getRotation(modelMatrix, new Matrix3()), + ), + this.chosenModel.id.dimensions, + ), + ); + Matrix4.multiplyByPoint(matrix, localEdge[0], positions[0]); + Matrix4.multiplyByPoint(matrix, localEdge[1], positions[1]); + const centerDiff = Cartesian3.subtract( + this.chosenModel.boundingSphere.center, + this.position, + new Cartesian3(), + ); + Cartesian3.add(positions[0], centerDiff, positions[0]); + Cartesian3.add(positions[1], centerDiff, positions[1]); + return positions; + }, false), + width: 10, + material: Color.WHITE, + arcType: ArcType.NONE, + }, + }); + } + async onClick(evt: ScreenSpaceEventHandler.PositionedEvent): Promise { const model: Model | undefined = this.pickModel(evt.position); if (model) { if (!this.chosenModel) { this.chosenModel = model; - Cartesian3.add( - Cartesian3.ZERO, - Matrix4.getTranslation( - this.chosenModel.modelMatrix, - new Cartesian3(), - ), - this.position, - ); - const centerDiff = Cartesian3.subtract( - model.boundingSphere.center, - this.position, - new Cartesian3(), - ); + Matrix4.getTranslation(this.chosenModel.modelMatrix, this.position); SIDE_PLANES.forEach((p) => this.createPlaneEntity(p, model, Color.RED)); - - const localEdges: [Cartesian3, Cartesian3][] = []; - CORNER_POINT_VECTORS.forEach((vector, i) => { - const upPoint = vector; - const downPoint = Cartesian3.clone(upPoint, new Cartesian3()); - downPoint.z *= -1; - - const nextUpPoint = CORNER_POINT_VECTORS[(i + 1) % 4]; - const nextDownPoint = Cartesian3.clone(nextUpPoint, new Cartesian3()); - nextDownPoint.z *= -1; - - const verticalEdge: [Cartesian3, Cartesian3] = [upPoint, downPoint]; - const topEdge: [Cartesian3, Cartesian3] = [nextUpPoint, upPoint]; - const bottomEdge: [Cartesian3, Cartesian3] = [ - nextDownPoint, - downPoint, - ]; - localEdges.push(verticalEdge, topEdge, bottomEdge); - }); - localEdges.forEach((localEdge) => { - const positions = [new Cartesian3(), new Cartesian3()]; - this.edgeLinesDataSource.entities.add({ - polyline: { - show: true, - positions: new CallbackProperty(() => { - // todo improve - const modelMatrix = model.modelMatrix; - const matrix = Matrix4.fromTranslationRotationScale( - new TranslationRotationScale( - Matrix4.getTranslation(modelMatrix, new Cartesian3()), - Quaternion.fromRotationMatrix( - Matrix4.getRotation(modelMatrix, new Matrix3()), - ), - this.chosenModel.id.dimensions, - ), - ); - Cartesian3.add( - Matrix4.multiplyByPoint(matrix, localEdge[0], positions[0]), - centerDiff, - positions[0], - ); - Cartesian3.add( - Matrix4.multiplyByPoint(matrix, localEdge[1], positions[1]), - centerDiff, - positions[1], - ); - return positions; - }, false), - width: 2, - material: Color.WHITE, - arcType: ArcType.NONE, - }, - }); - }); + LOCAL_EDGES.forEach((localEdge) => this.createEdge(localEdge)); } } } @@ -348,29 +331,17 @@ export class NgvPluginCesiumModelInteract extends LitElement { const sensitivity = 0.05; const hpr = new HeadingPitchRoll(); hpr.heading = -dx * sensitivity; - hpr.pitch = 0; - hpr.roll = 0; - const quaternion = new Quaternion(); - Quaternion.multiply( - Quaternion.fromRotationMatrix( - Matrix4.getRotation(this.chosenModel.modelMatrix, new Matrix3()), - quaternion, - ), + const rotation = Matrix3.fromQuaternion( Quaternion.fromHeadingPitchRoll(hpr), - quaternion, ); - this.chosenModel.modelMatrix = Matrix4.fromTranslationRotationScale( - new TranslationRotationScale( - Matrix4.getTranslation( - this.chosenModel.modelMatrix, - new Cartesian3(), - ), - quaternion, - Matrix4.getScale(this.chosenModel.modelMatrix, new Cartesian3()), - ), + Matrix4.multiplyByMatrix3( + this.chosenModel.modelMatrix, + rotation, + this.chosenModel.modelMatrix, ); + return; } if (this.dragStart) { @@ -457,8 +428,15 @@ export class NgvPluginCesiumModelInteract extends LitElement { Cartesian3.add(this.position, this.moveStep, this.position); - this.chosenModel.modelMatrix = Transforms.eastNorthUpToFixedFrame( - this.position, + Matrix4.fromTranslationRotationScale( + new TranslationRotationScale( + this.position, + Quaternion.fromRotationMatrix( + Matrix4.getRotation(this.chosenModel.modelMatrix, new Matrix3()), + ), + Matrix4.getScale(this.chosenModel.modelMatrix, new Cartesian3()), + ), + this.chosenModel.modelMatrix, ); return; } From 8d8cd41e423dbc9afdc0d25c0fe868ab0f12b815 Mon Sep 17 00:00:00 2001 From: vladyslavtk Date: Mon, 11 Nov 2024 17:34:51 +0200 Subject: [PATCH 05/15] added globe options for cesium plugin --- src/apps/permits/demoPermitConfig.ts | 3 +++ src/interfaces/cesium/ingv-cesium-context.ts | 3 ++- src/plugins/cesium/ngv-cesium-factories.ts | 4 ++++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/apps/permits/demoPermitConfig.ts b/src/apps/permits/demoPermitConfig.ts index 90d7230..6abcb46 100644 --- a/src/apps/permits/demoPermitConfig.ts +++ b/src/apps/permits/demoPermitConfig.ts @@ -43,6 +43,9 @@ export const config: IPermitsConfig = { widgetOptions: { scene3DOnly: true, }, + globeOptions: { + depthTestAgainstTerrain: true, + }, }, }, }; diff --git a/src/interfaces/cesium/ingv-cesium-context.ts b/src/interfaces/cesium/ingv-cesium-context.ts index 22da76e..bf6089c 100644 --- a/src/interfaces/cesium/ingv-cesium-context.ts +++ b/src/interfaces/cesium/ingv-cesium-context.ts @@ -1,4 +1,4 @@ -import type {CesiumWidget} from '@cesium/engine'; +import type {CesiumWidget, Globe} from '@cesium/engine'; import type {INGVCatalog} from './ingv-catalog.js'; export interface IngvCesiumContext { @@ -32,4 +32,5 @@ export interface IngvCesiumContext { }; }; widgetOptions?: ConstructorParameters[1]; + globeOptions?: Partial; } diff --git a/src/plugins/cesium/ngv-cesium-factories.ts b/src/plugins/cesium/ngv-cesium-factories.ts index 109df45..c8b9235 100644 --- a/src/plugins/cesium/ngv-cesium-factories.ts +++ b/src/plugins/cesium/ngv-cesium-factories.ts @@ -251,6 +251,10 @@ export async function initCesiumWidget( Object.assign({}, cesiumContext.widgetOptions), ); + if (cesiumContext.globeOptions) { + Object.assign(viewer.scene.globe, cesiumContext.globeOptions); + } + const dataSourceCollection = new DataSourceCollection(); const dataSourceDisplay = new DataSourceDisplay({ scene: viewer.scene, From e892dc152830e2adbd0a61a444307fdabff98876 Mon Sep 17 00:00:00 2001 From: vladyslavtk Date: Mon, 11 Nov 2024 18:43:28 +0200 Subject: [PATCH 06/15] Add scale and some fixes --- .../ngv-plugin-cesium-model-interact.ts | 246 +++++++++++++----- 1 file changed, 186 insertions(+), 60 deletions(-) diff --git a/src/plugins/cesium/ngv-plugin-cesium-model-interact.ts b/src/plugins/cesium/ngv-plugin-cesium-model-interact.ts index b184801..c388bf7 100644 --- a/src/plugins/cesium/ngv-plugin-cesium-model-interact.ts +++ b/src/plugins/cesium/ngv-plugin-cesium-model-interact.ts @@ -6,7 +6,6 @@ import type { DataSource, Model, PrimitiveCollection, - Entity, DataSourceCollection, } from '@cesium/engine'; import { @@ -17,7 +16,6 @@ import { Plane, ScreenSpaceEventHandler, ScreenSpaceEventType, - Transforms, Cartographic, Cartesian2, Quaternion, @@ -30,7 +28,8 @@ import { ArcType, TranslationRotationScale, HeadingPitchRoll, - JulianDate, + Entity, + ColorMaterialProperty, } from '@cesium/engine'; import {instantiateModel} from './ngv-cesium-factories.js'; import type {StoredModel} from '../../apps/permits/localStore.js'; @@ -66,18 +65,12 @@ CORNER_POINT_VECTORS.forEach((vector, i) => { const nextDownPoint = Cartesian3.clone(nextUpPoint, new Cartesian3()); nextDownPoint.z *= -1; const verticalEdge: [Cartesian3, Cartesian3] = [upPoint, downPoint]; - const topEdge: [Cartesian3, Cartesian3] = [nextUpPoint, upPoint]; - const bottomEdge: [Cartesian3, Cartesian3] = [nextDownPoint, downPoint]; - LOCAL_EDGES.push(verticalEdge, topEdge, bottomEdge); + // const topEdge: [Cartesian3, Cartesian3] = [nextUpPoint, upPoint]; + // const bottomEdge: [Cartesian3, Cartesian3] = [nextDownPoint, downPoint]; + LOCAL_EDGES.push(verticalEdge); }); -const FACE_POINT_VECTORS = [ - new Cartesian3(0.5, 0.0, 0.0), - new Cartesian3(0.0, 0.5, 0.0), - new Cartesian3(0.0, 0.0, 0.5), -]; - -type GrabType = 'side' | 'top' | 'edge' | undefined; +type GrabType = 'side' | 'top' | 'edge' | 'corner' | undefined; @customElement('ngv-plugin-cesium-model-interact') export class NgvPluginCesiumModelInteract extends LitElement { @@ -88,7 +81,13 @@ export class NgvPluginCesiumModelInteract extends LitElement { @property({type: Object}) private dataSourceCollection: DataSourceCollection; @state() - private cursor: 'default' | 'grab' | 'grabbing' | 'pointer' = 'default'; + private cursor: + | 'default' + | 'move' + | 'pointer' + | 'ns-resize' + | 'ew-resize' + | 'nesw-resize' = 'default'; @state() private chosenModel: Model | undefined; @state() @@ -99,12 +98,14 @@ export class NgvPluginCesiumModelInteract extends LitElement { private sidePlanesDataSource: DataSource | undefined; private topDownPlanesDataSource: DataSource | undefined; private edgeLinesDataSource: DataSource | undefined; + private cornerPointsDataSource: DataSource | undefined; private moveStart: Cartesian3 = new Cartesian3(); private moveStep: Cartesian3 = new Cartesian3(); private pickedPointOffset: Cartesian3 = new Cartesian3(); private dragStart: boolean = false; private movePlane: Plane | undefined; private grabType: GrabType; + private hoveredEdge: Entity | undefined; // todo move in UI plugin static styles = css` @@ -167,7 +168,7 @@ export class NgvPluginCesiumModelInteract extends LitElement { ScreenSpaceEventType.LEFT_DOWN, ); this.eventHandler.setInputAction( - (evt: ScreenSpaceEventHandler.PositionedEvent) => this.onLeftUp(evt), + () => this.onLeftUp(), ScreenSpaceEventType.LEFT_UP, ); @@ -175,8 +176,9 @@ export class NgvPluginCesiumModelInteract extends LitElement { this.onPrimitivesChanged(); }); this.primitiveCollection.primitiveRemoved.addEventListener((p) => { - deleteFromIndexedDB(p.id.name); - this.onPrimitivesChanged(); + deleteFromIndexedDB(p.id.name) + .then(() => this.onPrimitivesChanged()) + .catch((e) => console.error(e)); }); } @@ -204,26 +206,36 @@ export class NgvPluginCesiumModelInteract extends LitElement { : planeLocal.normal.y ? Axis.Y : Axis.Z; - const planeDimensions = new Cartesian2(); - const dimensions: Cartesian3 = model.id.dimensions; - let scale = new Cartesian3(); - - if (normalAxis === Axis.X) { - planeDimensions.x = dimensions.y; - planeDimensions.y = dimensions.z; - scale = new Cartesian3(dimensions.x, dimensions.y, dimensions.z); - } else if (normalAxis === Axis.Y) { - planeDimensions.x = dimensions.x; - planeDimensions.y = dimensions.z; - dimensions.clone(scale); - } else if (normalAxis === Axis.Z) { - planeDimensions.x = dimensions.x; - planeDimensions.y = dimensions.y; - scale = new Cartesian3(dimensions.y, dimensions.x, dimensions.z); - } - const scaleMatrix = Matrix4.fromScale(scale, new Matrix4()); - const plane = Plane.transform(planeLocal, scaleMatrix); + const getDimensionsAndScale = () => { + const planeDimensions = new Cartesian2(); + const dimensions: Cartesian3 = Cartesian3.clone(model.id.dimensions); + Cartesian3.multiplyComponents( + Matrix4.getScale(this.chosenModel.modelMatrix, new Cartesian3()), + dimensions, + dimensions, + ); + let scale = new Cartesian3(); + + if (normalAxis === Axis.X) { + planeDimensions.x = dimensions.y; + planeDimensions.y = dimensions.z; + scale = new Cartesian3(dimensions.x, dimensions.y, dimensions.z); + } else if (normalAxis === Axis.Y) { + planeDimensions.x = dimensions.x; + planeDimensions.y = dimensions.z; + dimensions.clone(scale); + } else if (normalAxis === Axis.Z) { + planeDimensions.x = dimensions.x; + planeDimensions.y = dimensions.y; + scale = new Cartesian3(dimensions.y, dimensions.x, dimensions.z); + } + const scaleMatrix = Matrix4.fromScale(scale, new Matrix4()); + return { + scaleMatrix, + planeDimensions, + }; + }; const dataSource = normalAxis === Axis.Z @@ -243,8 +255,14 @@ export class NgvPluginCesiumModelInteract extends LitElement { false, ), plane: { - plane: plane, - dimensions: planeDimensions, + plane: new CallbackProperty(() => { + const {scaleMatrix} = getDimensionsAndScale(); + return Plane.transform(planeLocal, scaleMatrix); + }, false), + dimensions: new CallbackProperty(() => { + const {planeDimensions} = getDimensionsAndScale(); + return planeDimensions; + }, false), material: color.withAlpha(0.5), outline: true, outlineColor: Color.WHITE, @@ -266,7 +284,14 @@ export class NgvPluginCesiumModelInteract extends LitElement { Quaternion.fromRotationMatrix( Matrix4.getRotation(modelMatrix, new Matrix3()), ), - this.chosenModel.id.dimensions, + Cartesian3.multiplyComponents( + Matrix4.getScale( + this.chosenModel.modelMatrix, + new Cartesian3(), + ), + this.chosenModel.id.dimensions, + new Cartesian3(), + ), ), ); Matrix4.multiplyByPoint(matrix, localEdge[0], positions[0]); @@ -281,13 +306,54 @@ export class NgvPluginCesiumModelInteract extends LitElement { return positions; }, false), width: 10, - material: Color.WHITE, + material: Color.WHITE.withAlpha(0.3), arcType: ArcType.NONE, }, }); } - async onClick(evt: ScreenSpaceEventHandler.PositionedEvent): Promise { + createCornerPoint(localEdges: Cartesian3[]): void { + // todo improve + const position = new Cartesian3(); + localEdges.forEach((localEdge) => { + this.cornerPointsDataSource.entities.add({ + position: new CallbackProperty(() => { + const modelMatrix = this.chosenModel.modelMatrix; + const matrix = Matrix4.fromTranslationRotationScale( + new TranslationRotationScale( + Matrix4.getTranslation(modelMatrix, new Cartesian3()), + Quaternion.fromRotationMatrix( + Matrix4.getRotation(modelMatrix, new Matrix3()), + ), + Cartesian3.multiplyComponents( + Matrix4.getScale( + this.chosenModel.modelMatrix, + new Cartesian3(), + ), + this.chosenModel.id.dimensions, + new Cartesian3(), + ), + ), + ); + Matrix4.multiplyByPoint(matrix, localEdge, position); + const centerDiff = Cartesian3.subtract( + this.chosenModel.boundingSphere.center, + this.position, + new Cartesian3(), + ); + Cartesian3.add(position, centerDiff, position); + return position; + }, false), + ellipsoid: { + show: true, + radii: new Cartesian3(10, 10, 10), + material: Color.BROWN, + }, + }); + }); + } + + onClick(evt: ScreenSpaceEventHandler.PositionedEvent): void { const model: Model | undefined = this.pickModel(evt.position); if (model) { if (!this.chosenModel) { @@ -295,14 +361,14 @@ export class NgvPluginCesiumModelInteract extends LitElement { Matrix4.getTranslation(this.chosenModel.modelMatrix, this.position); SIDE_PLANES.forEach((p) => this.createPlaneEntity(p, model, Color.RED)); LOCAL_EDGES.forEach((localEdge) => this.createEdge(localEdge)); + LOCAL_EDGES.forEach((localEdge) => this.createCornerPoint(localEdge)); } } } onLeftDown(evt: ScreenSpaceEventHandler.PositionedEvent): void { this.grabType = this.pickGrabType(evt.position); - if (this.grabType && this.cursor !== 'grabbing') { - this.viewer.canvas.style.cursor = this.cursor = 'grabbing'; + if (this.grabType) { this.viewer.scene.screenSpaceCameraController.enableInputs = false; this.viewer.scene.pickPosition(evt.position, this.moveStart); this.dragStart = true; @@ -312,8 +378,7 @@ export class NgvPluginCesiumModelInteract extends LitElement { } } onLeftUp(): void { - if (this.cursor === 'grabbing') { - this.viewer.canvas.style.cursor = this.cursor = 'grab'; + if (this.grabType) { this.viewer.scene.screenSpaceCameraController.enableInputs = true; this.grabType = undefined; } @@ -344,6 +409,22 @@ export class NgvPluginCesiumModelInteract extends LitElement { return; } + if (this.grabType === 'corner') { + const dx = evt.endPosition.x - evt.startPosition.x; + const sensitivity = 0.01; + const scaleAmount = 1 + dx * sensitivity; + + // Apply scale to the model matrix of the primitive + const scaleMatrix = Matrix4.fromScale( + new Cartesian3(scaleAmount, scaleAmount, scaleAmount), + ); + Matrix4.multiply( + this.chosenModel.modelMatrix, + scaleMatrix, + this.chosenModel.modelMatrix, + ); + return; + } if (this.dragStart) { Cartesian3.subtract( endPosition, @@ -365,11 +446,6 @@ export class NgvPluginCesiumModelInteract extends LitElement { if (this.grabType === 'top') { const cartPickedPosition = Cartographic.fromCartesian(pickedPosition); const topPos = pickedPosition.clone(); - // Cartesian3.fromRadians( - // cartPickedPosition.longitude, - // cartPickedPosition.latitude, - // cartPickedPosition.height + this.chosenModel.id.max.y, - // ); const bottomPos = Cartesian3.fromRadians( cartPickedPosition.longitude, cartPickedPosition.latitude, @@ -440,20 +516,52 @@ export class NgvPluginCesiumModelInteract extends LitElement { ); return; } + this.updateCursor(evt.endPosition); + } - const model: Model | undefined = this.pickModel(evt.endPosition); - if (this.grabType || model) { - if (this.cursor !== 'pointer' && !this.chosenModel) { + updateCursor(position: Cartesian2): void { + const model: Model | undefined = this.pickModel(position); + const isSidePlane = !!this.pickEntity(position, this.sidePlanesDataSource); + const isTopPlane = !!this.pickEntity( + position, + this.topDownPlanesDataSource, + ); + const edgeEntity = this.pickEntity(position, this.edgeLinesDataSource); + const isCorner = !!this.pickEntity(position, this.cornerPointsDataSource); + if (model && !this.chosenModel) { + if (this.cursor !== 'pointer') { this.viewer.canvas.style.cursor = this.cursor = 'pointer'; - } else if (this.chosenModel) { - if (this.cursor !== 'grab' && this.cursor !== 'grabbing') { - this.viewer.canvas.style.cursor = this.cursor = 'grab'; - } } - } else { - if (this.cursor !== 'default') { - this.viewer.canvas.style.cursor = this.cursor = 'default'; + } else if (isSidePlane) { + if (this.cursor !== 'move') { + this.viewer.canvas.style.cursor = this.cursor = 'move'; } + } else if (isTopPlane) { + if (this.cursor !== 'ns-resize') { + this.viewer.canvas.style.cursor = this.cursor = 'ns-resize'; + } + } else if (isCorner) { + if (this.cursor !== 'nesw-resize') { + this.viewer.canvas.style.cursor = this.cursor = 'nesw-resize'; + } + } else if (edgeEntity) { + if (this.cursor !== 'ew-resize') { + this.viewer.canvas.style.cursor = this.cursor = 'ew-resize'; + } + if (!this.hoveredEdge) { + this.hoveredEdge = edgeEntity; + this.hoveredEdge.polyline.material = new ColorMaterialProperty( + Color.WHITE.withAlpha(0.9), + ); + } + } else if (this.cursor !== 'default') { + this.viewer.canvas.style.cursor = this.cursor = 'default'; + } + if (this.hoveredEdge && !edgeEntity) { + this.hoveredEdge.polyline.material = new ColorMaterialProperty( + Color.WHITE.withAlpha(0.3), + ); + this.hoveredEdge = undefined; } } @@ -467,6 +575,17 @@ export class NgvPluginCesiumModelInteract extends LitElement { : undefined; } + pickEntity(position: Cartesian2, dataSource: DataSource): Entity | undefined { + const obj: {id: Entity | undefined} = <{id: Entity | undefined}>( + this.viewer.scene.pick(position) + ); + return obj?.id && + obj.id instanceof Entity && + dataSource.entities.contains(obj.id) + ? obj?.id + : undefined; + } + pickGrabType(position: Cartesian2): GrabType { const pickedObject: {id: Entity | undefined} = <{id: Entity | undefined}>( this.viewer.scene.pick(position) @@ -480,6 +599,8 @@ export class NgvPluginCesiumModelInteract extends LitElement { return 'top'; } else if (this.edgeLinesDataSource.entities.contains(pickedObject.id)) { return 'edge'; + } else if (this.cornerPointsDataSource.entities.contains(pickedObject.id)) { + return 'corner'; } } @@ -532,6 +653,10 @@ export class NgvPluginCesiumModelInteract extends LitElement { .add(new CustomDataSource()) .then((dataSource) => (this.edgeLinesDataSource = dataSource)) .catch((e) => console.error(e)); + this.dataSourceCollection + .add(new CustomDataSource()) + .then((dataSource) => (this.cornerPointsDataSource = dataSource)) + .catch((e) => console.error(e)); super.firstUpdated(_changedProperties); } @@ -550,6 +675,7 @@ export class NgvPluginCesiumModelInteract extends LitElement { this.sidePlanesDataSource.entities.removeAll(); this.topDownPlanesDataSource.entities.removeAll(); this.edgeLinesDataSource.entities.removeAll(); + this.cornerPointsDataSource.entities.removeAll(); this.onPrimitivesChanged(); }}" > From 2fa6cb99231f1e55c1ad9f67db4f00f3889e67a2 Mon Sep 17 00:00:00 2001 From: vladyslavtk Date: Wed, 13 Nov 2024 17:17:24 +0200 Subject: [PATCH 07/15] fixes --- .../illumination/ngv-main-illumination.ts | 2 +- src/apps/permits/localStore.ts | 17 +-- src/plugins/cesium/ngv-cesium-factories.ts | 1 - .../ngv-plugin-cesium-model-interact.ts | 50 ++++--- .../cesium/ngv-plugin-cesium-upload.ts | 139 +++++++++--------- src/plugins/ui/ngv-upload.ts | 34 ++--- 6 files changed, 121 insertions(+), 122 deletions(-) diff --git a/src/apps/illumination/ngv-main-illumination.ts b/src/apps/illumination/ngv-main-illumination.ts index d1235e2..afee3dc 100644 --- a/src/apps/illumination/ngv-main-illumination.ts +++ b/src/apps/illumination/ngv-main-illumination.ts @@ -6,7 +6,7 @@ import {type CesiumWidget} from '@cesium/engine'; import type {IIlluminationConfig} from './ingv-config-illumination.js'; import '../../plugins/cesium/ngv-plugin-cesium-widget.js'; -import {ViewerInitializedDetails} from '../../plugins/cesium/ngv-plugin-cesium-widget.js'; +import type {ViewerInitializedDetails} from '../../plugins/cesium/ngv-plugin-cesium-widget.js'; @customElement('ngv-main-illumination') export class NgvMainIllumination extends LitElement { diff --git a/src/apps/permits/localStore.ts b/src/apps/permits/localStore.ts index 408c550..41149ff 100644 --- a/src/apps/permits/localStore.ts +++ b/src/apps/permits/localStore.ts @@ -1,14 +1,7 @@ // todo Just for easier testing. Better place should be find and structure improved. -import { - Cartesian3, - Matrix3, - Matrix4, - Model, - Quaternion, - TranslationRotationScale, -} from '@cesium/engine'; -import {instantiateModel} from '../../plugins/cesium/ngv-cesium-factories.js'; +import {Cartesian3, Matrix3, Matrix4, Quaternion} from '@cesium/engine'; +import type {UploadedModel} from '../../plugins/cesium/ngv-plugin-cesium-upload.js'; const DB_NAME = 'uploadedModelsStore'; @@ -105,8 +98,6 @@ export function deleteFromIndexedDB(name: string): Promise { export type StoredModel = { name: string; - min: number[]; - max: number[]; dimensions: number[]; translation: number[]; rotation: number[]; @@ -115,7 +106,7 @@ export type StoredModel = { export const STORE_KEY = 'localStoreModels'; -export function updateModelsInLocalStore(models: Model[]): void { +export function updateModelsInLocalStore(models: UploadedModel[]): void { const localStoreModels: StoredModel[] = []; models.forEach((model) => { const translation = Matrix4.getTranslation( @@ -128,8 +119,6 @@ export function updateModelsInLocalStore(models: Model[]): void { ); localStoreModels.push({ name: model.id.name, - min: [model.id.min.x, model.id.min.y, model.id.min.z], - max: [model.id.max.x, model.id.max.y, model.id.max.z], dimensions: [ model.id.dimensions.x, model.id.dimensions.y, diff --git a/src/plugins/cesium/ngv-cesium-factories.ts b/src/plugins/cesium/ngv-cesium-factories.ts index c8b9235..9eb93b1 100644 --- a/src/plugins/cesium/ngv-cesium-factories.ts +++ b/src/plugins/cesium/ngv-cesium-factories.ts @@ -7,7 +7,6 @@ import { Model, DataSourceCollection, DataSourceDisplay, - CustomDataSource, } from '@cesium/engine'; import type { diff --git a/src/plugins/cesium/ngv-plugin-cesium-model-interact.ts b/src/plugins/cesium/ngv-plugin-cesium-model-interact.ts index c388bf7..0420e0e 100644 --- a/src/plugins/cesium/ngv-plugin-cesium-model-interact.ts +++ b/src/plugins/cesium/ngv-plugin-cesium-model-interact.ts @@ -4,11 +4,11 @@ import {customElement, property, state} from 'lit/decorators.js'; import type { CesiumWidget, DataSource, - Model, PrimitiveCollection, DataSourceCollection, } from '@cesium/engine'; import { + Model, Axis, Cartesian3, Color, @@ -39,6 +39,7 @@ import { getStoredModels, updateModelsInLocalStore, } from '../../apps/permits/localStore.js'; +import type {UploadedModel} from './ngv-plugin-cesium-upload.js'; const SIDE_PLANES: Plane[] = [ new Plane(new Cartesian3(0, 0, 1), 0.5), @@ -89,11 +90,11 @@ export class NgvPluginCesiumModelInteract extends LitElement { | 'ew-resize' | 'nesw-resize' = 'default'; @state() - private chosenModel: Model | undefined; + private chosenModel: UploadedModel | undefined; @state() private position: Cartesian3 = new Cartesian3(); @state() - private models: Model[] = []; + private models: UploadedModel[] = []; private eventHandler: ScreenSpaceEventHandler | undefined; private sidePlanesDataSource: DataSource | undefined; private topDownPlanesDataSource: DataSource | undefined; @@ -175,11 +176,13 @@ export class NgvPluginCesiumModelInteract extends LitElement { this.primitiveCollection.primitiveAdded.addEventListener(() => { this.onPrimitivesChanged(); }); - this.primitiveCollection.primitiveRemoved.addEventListener((p) => { - deleteFromIndexedDB(p.id.name) - .then(() => this.onPrimitivesChanged()) - .catch((e) => console.error(e)); - }); + this.primitiveCollection.primitiveRemoved.addEventListener( + (p: UploadedModel) => { + deleteFromIndexedDB(p.id.name) + .then(() => this.onPrimitivesChanged()) + .catch((e) => console.error(e)); + }, + ); } removeEvents(): void { @@ -195,12 +198,19 @@ export class NgvPluginCesiumModelInteract extends LitElement { onPrimitivesChanged(): void { this.models = []; for (let i = 0; i < this.primitiveCollection.length; i++) { - this.models.push(this.primitiveCollection.get(i)); + const model = this.primitiveCollection.get(i) as UploadedModel; + if (model instanceof Model) { + this.models.push(model); + } } updateModelsInLocalStore(this.models); } - createPlaneEntity(planeLocal: Plane, model: Model, color: Color): void { + createPlaneEntity( + planeLocal: Plane, + model: UploadedModel, + color: Color, + ): void { const normalAxis = planeLocal.normal.x ? Axis.X : planeLocal.normal.y @@ -586,7 +596,7 @@ export class NgvPluginCesiumModelInteract extends LitElement { : undefined; } - pickGrabType(position: Cartesian2): GrabType { + pickGrabType(position: Cartesian2): GrabType | undefined { const pickedObject: {id: Entity | undefined} = <{id: Entity | undefined}>( this.viewer.scene.pick(position) ); @@ -602,13 +612,14 @@ export class NgvPluginCesiumModelInteract extends LitElement { } else if (this.cornerPointsDataSource.entities.contains(pickedObject.id)) { return 'corner'; } + return undefined; } - async firstUpdated(_changedProperties: PropertyValues): Promise { + firstUpdated(_changedProperties: PropertyValues): void { // todo improve code const models = getStoredModels(); if (models) { - await Promise.all( + Promise.all( models.map(async (m: StoredModel) => { const blob = await getBlobFromIndexedDB(m.name); const model = await instantiateModel({ @@ -625,18 +636,19 @@ export class NgvPluginCesiumModelInteract extends LitElement { ), id: { name: m.name, - min: new Cartesian3(...Object.values(m.min)), - max: new Cartesian3(...Object.values(m.max)), dimensions: new Cartesian3(...Object.values(m.dimensions)), }, }, }); this.primitiveCollection.add(model); }), - ); - this.onPrimitivesChanged(); - this.initEvents(); - this.viewer.scene.requestRender(); + ) + .then(() => { + this.onPrimitivesChanged(); + this.initEvents(); + this.viewer.scene.requestRender(); + }) + .catch((e) => console.error(e)); } else { this.initEvents(); } diff --git a/src/plugins/cesium/ngv-plugin-cesium-upload.ts b/src/plugins/cesium/ngv-plugin-cesium-upload.ts index 206719e..0e8e24d 100644 --- a/src/plugins/cesium/ngv-plugin-cesium-upload.ts +++ b/src/plugins/cesium/ngv-plugin-cesium-upload.ts @@ -3,7 +3,6 @@ import { Cartesian3, Cartographic, HeadingPitchRoll, - HeightReference, Math as CesiumMath, Matrix4, Quaternion, @@ -17,7 +16,7 @@ import {html, LitElement} from 'lit'; import {customElement, property} from 'lit/decorators.js'; import '../ui/ngv-upload.js'; import {instantiateModel} from './ngv-cesium-factories.js'; -import {FileUploadDetails} from '../ui/ngv-upload.js'; +import type {FileUploadDetails} from '../ui/ngv-upload.js'; import { storeBlobInIndexedDB, updateModelsInLocalStore, @@ -25,6 +24,25 @@ import { const cartographicScratch = new Cartographic(); +type GlbJson = { + bufferViews: {byteOffset: number; byteLength: number}[]; + accessors: {bufferView: number}[]; + meshes: { + primitives: { + attributes: { + POSITION: number; + }; + }[]; + }[]; +}; + +export interface UploadedModel extends Model { + id: { + dimensions?: Cartesian3; + name: string; + }; +} + @customElement('ngv-plugin-cesium-upload') export class NgvPluginCesiumUpload extends LitElement { @property({type: Object}) @@ -37,77 +55,17 @@ export class NgvPluginCesiumUpload extends LitElement { async upload(fileDetails: FileUploadDetails): Promise { const response = await fetch(fileDetails.url); const arrayBuffer = await response.arrayBuffer(); - const glb = new Uint8Array(arrayBuffer); - - // Extract JSON chunk and binary chunk - const jsonLength = new DataView(arrayBuffer, 12, 4).getUint32(0, true); - const jsonChunk = new TextDecoder().decode( - glb.subarray(20, 20 + jsonLength), - ); - const json = JSON.parse(jsonChunk); - - // Now access the binary data buffer view for vertex positions - const bufferView = - json.bufferViews[ - json.accessors[json.meshes[0].primitives[0].attributes.POSITION] - .bufferView - ]; - const byteOffset = bufferView.byteOffset; - const byteLength = bufferView.byteLength; - - // Extract the vertex data from the binary chunk (after JSON chunk) - const binaryData = new Float32Array( - arrayBuffer, - byteOffset + 20 + jsonLength, - byteLength / Float32Array.BYTES_PER_ELEMENT, - ); - - // Initialize AABB min/max values - const min = new Cartesian3( - Number.POSITIVE_INFINITY, - Number.POSITIVE_INFINITY, - Number.POSITIVE_INFINITY, - ); - const max = new Cartesian3( - Number.NEGATIVE_INFINITY, - Number.NEGATIVE_INFINITY, - Number.NEGATIVE_INFINITY, - ); - - // Iterate over the vertex positions and compute the AABB - for (let i = 0; i < binaryData.length; i += 3) { - const vertex = new Cartesian3( - binaryData[i], - binaryData[i + 2], - binaryData[i + 1], - ); - - // Update AABB min and max - Cartesian3.minimumByComponent(min, vertex, min); - Cartesian3.maximumByComponent(max, vertex, max); - } - - // Compute dimensions (width, height, depth) - const dimensions = Cartesian3.subtract(max, min, new Cartesian3()); + const dimensions = parseGlbModelDimensions(arrayBuffer); - const modelOrientation = [90, 0, 0]; const modelMatrix = Matrix4.fromTranslationRotationScale( new TranslationRotationScale( Cartesian3.ZERO, Quaternion.fromHeadingPitchRoll( - new HeadingPitchRoll(...modelOrientation.map(CesiumMath.toRadians)), + new HeadingPitchRoll(CesiumMath.toRadians(90), 0, 0), ), ), ); - // const modelMatrix = new Matrix4(); - // - // json.nodes.forEach((node) => { - // if (!node.matrix) return; - // const matrix = new Matrix4(...node.matrix); - // Matrix4.add(modelMatrix, matrix, modelMatrix); - // }); - this.uploadedModel = await instantiateModel({ type: 'model', options: { @@ -117,8 +75,6 @@ export class NgvPluginCesiumUpload extends LitElement { id: { name: fileDetails.name, dimensions, - min, - max, }, }, }); @@ -174,13 +130,62 @@ export class NgvPluginCesiumUpload extends LitElement { render(): HTMLTemplateResult { return html` `; } } +function parseGlbModelDimensions(arrayBuffer: ArrayBuffer): Cartesian3 { + const glb = new Uint8Array(arrayBuffer); + + const jsonLength = new DataView(arrayBuffer, 12, 4).getUint32(0, true); + const jsonChunk = new TextDecoder().decode(glb.subarray(20, 20 + jsonLength)); + const json: GlbJson = JSON.parse(jsonChunk) as GlbJson; + + const bufferView = + json.bufferViews[ + json.accessors[json.meshes[0].primitives[0].attributes.POSITION] + .bufferView + ]; + const byteOffset = bufferView.byteOffset; + const byteLength = bufferView.byteLength; + + const binaryData = new Float32Array( + arrayBuffer, + byteOffset + 20 + jsonLength, + byteLength / Float32Array.BYTES_PER_ELEMENT, + ); + + const min = new Cartesian3( + Number.POSITIVE_INFINITY, + Number.POSITIVE_INFINITY, + Number.POSITIVE_INFINITY, + ); + const max = new Cartesian3( + Number.NEGATIVE_INFINITY, + Number.NEGATIVE_INFINITY, + Number.NEGATIVE_INFINITY, + ); + + for (let i = 0; i < binaryData.length; i += 3) { + const vertex = new Cartesian3( + binaryData[i], + binaryData[i + 2], + binaryData[i + 1], + ); + + Cartesian3.minimumByComponent(min, vertex, min); + Cartesian3.maximumByComponent(max, vertex, max); + } + + return Cartesian3.subtract(max, min, new Cartesian3()); +} + declare global { interface HTMLElementTagNameMap { 'ngv-plugin-cesium-upload': NgvPluginCesiumUpload; diff --git a/src/plugins/ui/ngv-upload.ts b/src/plugins/ui/ngv-upload.ts index 49716ee..a4c35ad 100644 --- a/src/plugins/ui/ngv-upload.ts +++ b/src/plugins/ui/ngv-upload.ts @@ -25,14 +25,6 @@ export type FileUploadDetails = { name: string; }; -/** - * TODO - * * type check - * * error handling - * * multiple files upload - * * drag & drop ? - * * URL input - */ @customElement('ngv-upload') export class NgvUpload extends LitElement { @property({type: Object}) options: NgvUploadOptions; @@ -102,13 +94,9 @@ export class NgvUpload extends LitElement { } override willUpdate(): void { - const options: NgvUploadOptions = {...defaultOptions}; + let options: NgvUploadOptions = {...defaultOptions}; if (this.options) { - Object.keys((key: keyof NgvUploadOptions) => { - if (this.options[key] !== undefined && this.options[key] !== null) { - options[key] = this.options[key]; - } - }); + options = {...options, ...this.options}; } this.options = options; } @@ -124,12 +112,18 @@ export class NgvUpload extends LitElement { await this.onFileUpload(target.files[0]); }} /> - + From c1fc70eb327e058b9645213131246c3006c70207 Mon Sep 17 00:00:00 2001 From: vladyslavtk Date: Thu, 14 Nov 2024 00:10:51 +0200 Subject: [PATCH 08/15] Split UI --- .../ngv-plugin-cesium-model-interact.ts | 118 ++++++------------ src/plugins/ui/ngv-layer-details.ts | 65 ++++++++++ src/plugins/ui/ngv-layers-list.ts | 99 +++++++++++++++ 3 files changed, 203 insertions(+), 79 deletions(-) create mode 100644 src/plugins/ui/ngv-layer-details.ts create mode 100644 src/plugins/ui/ngv-layers-list.ts diff --git a/src/plugins/cesium/ngv-plugin-cesium-model-interact.ts b/src/plugins/cesium/ngv-plugin-cesium-model-interact.ts index 0420e0e..83ce312 100644 --- a/src/plugins/cesium/ngv-plugin-cesium-model-interact.ts +++ b/src/plugins/cesium/ngv-plugin-cesium-model-interact.ts @@ -1,4 +1,4 @@ -import {css, html, LitElement} from 'lit'; +import {html, LitElement} from 'lit'; import type {HTMLTemplateResult, PropertyValues} from 'lit'; import {customElement, property, state} from 'lit/decorators.js'; import type { @@ -40,6 +40,8 @@ import { updateModelsInLocalStore, } from '../../apps/permits/localStore.js'; import type {UploadedModel} from './ngv-plugin-cesium-upload.js'; +import '../ui/ngv-layer-details.js'; +import '../ui/ngv-layers-list.js'; const SIDE_PLANES: Plane[] = [ new Plane(new Cartesian3(0, 0, 1), 0.5), @@ -108,52 +110,6 @@ export class NgvPluginCesiumModelInteract extends LitElement { private grabType: GrabType; private hoveredEdge: Entity | undefined; - // todo move in UI plugin - static styles = css` - .model-list, - .model-info { - background-color: white; - display: flex; - flex-direction: column; - z-index: 1; - margin-left: auto; - margin-right: auto; - padding: 10px; - gap: 10px; - border-radius: 4px; - border: 1px solid rgba(0, 0, 0, 0.16); - box-shadow: 0 1px 0 rgba(0, 0, 0, 0.05); - } - - .model-item { - text-overflow: ellipsis; - display: flex; - align-items: center; - column-gap: 10px; - } - - .model-item span { - overflow: hidden; - text-overflow: ellipsis; - } - - button, - input[type='text'] { - border-radius: 4px; - padding: 0 16px; - height: 40px; - cursor: pointer; - background-color: white; - border: 1px solid rgba(0, 0, 0, 0.16); - box-shadow: 0 1px 0 rgba(0, 0, 0, 0.05); - transition: background-color 200ms; - } - - input[type='text'] { - cursor: text; - } - `; - initEvents(): void { this.eventHandler = new ScreenSpaceEventHandler(this.viewer.canvas); this.eventHandler.setInputAction( @@ -677,39 +633,43 @@ export class NgvPluginCesiumModelInteract extends LitElement { } render(): HTMLTemplateResult | string { if (!this.chosenModel && !this.models?.length) return ''; - // todo move in UI plugin return this.chosenModel - ? html`
- ${this.chosenModel.id.name} - -
` - : html`

Uploaded models:

-
- ${this.models.map( - (m) => - html`
- ${m.id.name} - -
`, - )} -
`; + ? html` ` + : html` { + return {name: m.id.name}; + })} + @remove="${(evt: {detail: number}) => { + const model = this.primitiveCollection.get( + evt.detail, + ) as UploadedModel; + if (model) this.primitiveCollection.remove(model); + }}" + @zoom="${(evt: {detail: number}) => { + const model = this.primitiveCollection.get( + evt.detail, + ) as UploadedModel; + if (model) + this.viewer.camera.flyToBoundingSphere(model.boundingSphere, { + duration: 2, + }); + }}" + >`; } disconnectedCallback(): void { diff --git a/src/plugins/ui/ngv-layer-details.ts b/src/plugins/ui/ngv-layer-details.ts new file mode 100644 index 0000000..1d90dcc --- /dev/null +++ b/src/plugins/ui/ngv-layer-details.ts @@ -0,0 +1,65 @@ +import {css, html, LitElement} from 'lit'; +import type {HTMLTemplateResult} from 'lit'; +import {customElement, property} from 'lit/decorators.js'; + +export type LayerDetails = { + name: string; +}; + +@customElement('ngv-layer-details') +export class NgvLayerDetails extends LitElement { + @property({type: Object}) + private layer: LayerDetails; + + static styles = css` + .info { + background-color: white; + display: flex; + flex-direction: column; + z-index: 1; + margin-left: auto; + margin-right: auto; + padding: 10px; + gap: 10px; + border-radius: 4px; + border: 1px solid rgba(0, 0, 0, 0.16); + box-shadow: 0 1px 0 rgba(0, 0, 0, 0.05); + } + + button, + input[type='text'] { + border-radius: 4px; + padding: 0 16px; + height: 40px; + cursor: pointer; + background-color: white; + border: 1px solid rgba(0, 0, 0, 0.16); + box-shadow: 0 1px 0 rgba(0, 0, 0, 0.05); + transition: background-color 200ms; + } + + input[type='text'] { + cursor: text; + } + `; + + render(): HTMLTemplateResult | string { + if (!this.layer) return ''; + return html`
+ ${this.layer.name} + +
`; + } +} + +declare global { + interface HTMLElementTagNameMap { + 'ngv-layer-details': NgvLayerDetails; + } +} diff --git a/src/plugins/ui/ngv-layers-list.ts b/src/plugins/ui/ngv-layers-list.ts new file mode 100644 index 0000000..a5cfa40 --- /dev/null +++ b/src/plugins/ui/ngv-layers-list.ts @@ -0,0 +1,99 @@ +import {css, html, LitElement} from 'lit'; +import type {HTMLTemplateResult} from 'lit'; +import {customElement, property} from 'lit/decorators.js'; + +export type LayerListItem = { + name: string; +}; + +export type LayerListOptions = { + title?: string; + showDeleteBtns?: boolean; + showZoomBtns?: boolean; +}; + +@customElement('ngv-layers-list') +export class NgvLayersList extends LitElement { + @property({type: Object}) + private layers: LayerListItem[]; + @property({type: Object}) + private options?: LayerListOptions; + + static styles = css` + .list { + background-color: white; + display: flex; + flex-direction: column; + z-index: 1; + margin-left: auto; + margin-right: auto; + padding: 10px; + gap: 10px; + border-radius: 4px; + border: 1px solid rgba(0, 0, 0, 0.16); + box-shadow: 0 1px 0 rgba(0, 0, 0, 0.05); + } + + .item { + text-overflow: ellipsis; + display: flex; + align-items: center; + column-gap: 10px; + } + + .item span { + overflow: hidden; + text-overflow: ellipsis; + } + + button { + border-radius: 4px; + padding: 0 16px; + height: 40px; + cursor: pointer; + background-color: white; + border: 1px solid rgba(0, 0, 0, 0.16); + box-shadow: 0 1px 0 rgba(0, 0, 0, 0.05); + transition: background-color 200ms; + } + `; + + render(): HTMLTemplateResult | string { + if (!this.layers?.length) return ''; + return html`

${this.options.title}

+
+ ${this.layers.map( + (l, i) => + html`
+ ${this.options?.showZoomBtns + ? html`` + : ''} + ${l.name} + ${this.options?.showDeleteBtns + ? html`` + : ''} +
`, + )} +
`; + } +} + +declare global { + interface HTMLElementTagNameMap { + 'ngv-layers-list': NgvLayersList; + } +} From d1a472cb3962d599b65e3897ffa2f847e63a7391 Mon Sep 17 00:00:00 2001 From: vladyslavtk Date: Thu, 14 Nov 2024 02:33:54 +0200 Subject: [PATCH 09/15] refactoring --- src/apps/permits/demoPermitConfig.ts | 4 + src/apps/permits/index.ts | 2 + src/interfaces/cesium/ingv-cesium-context.ts | 4 + src/plugins/cesium/interactionHelpers.ts | 466 ++++++++++++++++++ .../permits => plugins/cesium}/localStore.ts | 43 +- .../ngv-plugin-cesium-model-interact.ts | 421 ++++------------ .../cesium/ngv-plugin-cesium-upload.ts | 29 +- 7 files changed, 617 insertions(+), 352 deletions(-) create mode 100644 src/plugins/cesium/interactionHelpers.ts rename src/{apps/permits => plugins/cesium}/localStore.ts (79%) diff --git a/src/apps/permits/demoPermitConfig.ts b/src/apps/permits/demoPermitConfig.ts index 6abcb46..ecc501e 100644 --- a/src/apps/permits/demoPermitConfig.ts +++ b/src/apps/permits/demoPermitConfig.ts @@ -46,6 +46,10 @@ export const config: IPermitsConfig = { globeOptions: { depthTestAgainstTerrain: true, }, + storeOptions: { + localStoreKey: 'localStoreModels', + indexDbName: 'uploadedModelsStore', + }, }, }, }; diff --git a/src/apps/permits/index.ts b/src/apps/permits/index.ts index 6d4c537..da8d104 100644 --- a/src/apps/permits/index.ts +++ b/src/apps/permits/index.ts @@ -70,11 +70,13 @@ export class NgvAppPermits extends ABaseApp {
[1]; globeOptions?: Partial; + storeOptions?: { + localStoreKey: string; + indexDbName: string; + }; } diff --git a/src/plugins/cesium/interactionHelpers.ts b/src/plugins/cesium/interactionHelpers.ts new file mode 100644 index 0000000..62acc99 --- /dev/null +++ b/src/plugins/cesium/interactionHelpers.ts @@ -0,0 +1,466 @@ +import type {CustomDataSource, Model, Scene} from '@cesium/engine'; +import { + BoundingSphere, + ArcType, + Axis, + CallbackProperty, + Cartesian2, + Cartesian3, + Cartographic, + Color, + Ellipsoid, + HeadingPitchRoll, + IntersectionTests, + Matrix3, + Matrix4, + Plane, + Quaternion, + Ray, + TranslationRotationScale, +} from '@cesium/engine'; +import type {UploadedModel} from './ngv-plugin-cesium-upload.js'; + +export type PlaneColorOptions = { + material?: Color; + outline?: boolean; + outlineColor?: Color; +}; + +export type EdgeStyleOptions = { + width?: number; + material?: Color; +}; + +export type CornerPointStyleOptions = { + radiusPx?: number; + material?: Color; +}; + +export type BBoxStyles = { + planeColorOptions?: PlaneColorOptions; + edgeStyleOptions?: EdgeStyleOptions; + cornerPointStyleOptions: CornerPointStyleOptions; +}; + +const DefaultPlaneColorOptions: PlaneColorOptions = { + material: Color.RED.withAlpha(0.1), + outline: true, + outlineColor: Color.WHITE, +}; + +const DefaultEdgeStyles: EdgeStyleOptions = { + width: 10, + material: Color.WHITE.withAlpha(0.3), +}; + +const DefaultCornerPointStyles: CornerPointStyleOptions = { + radiusPx: 10, + material: Color.BROWN, +}; + +const SIDE_PLANES: Plane[] = [ + new Plane(new Cartesian3(0, 0, 1), 0.5), + new Plane(new Cartesian3(0, 0, -1), 0.5), + new Plane(new Cartesian3(0, 1, 0), 0.5), + new Plane(new Cartesian3(0, -1, 0), 0.5), + new Plane(new Cartesian3(1, 0, 0), 0.5), + new Plane(new Cartesian3(-1, 0, 0), 0.5), +]; + +const CORNER_POINT_VECTORS = [ + new Cartesian3(0.5, 0.5, 0.5), + new Cartesian3(0.5, -0.5, 0.5), + new Cartesian3(-0.5, -0.5, 0.5), + new Cartesian3(-0.5, 0.5, 0.5), +]; + +const LOCAL_EDGES: [Cartesian3, Cartesian3][] = []; +CORNER_POINT_VECTORS.forEach((vector, i) => { + const upPoint = vector; + const downPoint = Cartesian3.clone(upPoint, new Cartesian3()); + downPoint.z *= -1; + const nextUpPoint = CORNER_POINT_VECTORS[(i + 1) % 4]; + const nextDownPoint = Cartesian3.clone(nextUpPoint, new Cartesian3()); + nextDownPoint.z *= -1; + const verticalEdge: [Cartesian3, Cartesian3] = [upPoint, downPoint]; + // const topEdge: [Cartesian3, Cartesian3] = [nextUpPoint, upPoint]; + // const bottomEdge: [Cartesian3, Cartesian3] = [nextDownPoint, downPoint]; + LOCAL_EDGES.push(verticalEdge); +}); + +const scaleScratch = new Cartesian3(); +export function getScaleFromMatrix(matrix: Matrix4): Cartesian3 { + return Matrix4.getScale(matrix, scaleScratch); +} + +const dimensionsScratch = new Cartesian3(); +function getScaledDimensions(model: UploadedModel): Cartesian3 { + Cartesian3.clone(model.id.dimensions, dimensionsScratch); + Cartesian3.multiplyComponents( + getScaleFromMatrix(model.modelMatrix), + dimensionsScratch, + dimensionsScratch, + ); + return dimensionsScratch; +} + +const scratchRotationMatrix = new Matrix3(); +const scratchRotationQuaternion = new Quaternion(); +export function getRotationQuaternionFromMatrix(matrix: Matrix4): Quaternion { + return Quaternion.fromRotationMatrix( + Matrix4.getRotation(matrix, scratchRotationMatrix), + scratchRotationQuaternion, + ); +} + +const scratchTranslation = new Cartesian3(); +export function getTranslationFromMatrix(matrix: Matrix4): Cartesian3 { + return Matrix4.getTranslation(matrix, scratchTranslation); +} + +const scratchTranslationRotationDimensionsMatrix = new Matrix4(); +export function getTranslationRotationDimensionsMatrix( + model: UploadedModel, + result = scratchTranslationRotationDimensionsMatrix, +): Matrix4 { + return Matrix4.fromTranslationRotationScale( + new TranslationRotationScale( + getTranslationFromMatrix(model.modelMatrix), + getRotationQuaternionFromMatrix(model.modelMatrix), + getScaledDimensions(model), + ), + result, + ); +} + +const scratchTranslationRotationScaleMatrix = new Matrix4(); +export function getTranslationRotationScaleMatrix( + matrix: Matrix4, + result = scratchTranslationRotationScaleMatrix, +): Matrix4 { + return Matrix4.fromTranslationRotationScale( + new TranslationRotationScale( + getTranslationFromMatrix(matrix), + getRotationQuaternionFromMatrix(matrix), + getScaleFromMatrix(matrix), + ), + result, + ); +} + +const centerDiffScratch = new Cartesian3(); +export function getModelCenterDiff(model: Model): Cartesian3 { + return Cartesian3.subtract( + model.boundingSphere.center, + getTranslationFromMatrix(model.modelMatrix), + centerDiffScratch, + ); +} + +const scaleMatrixScratch = new Matrix4(); +const planeScaleScratch = new Cartesian3(); +function getPlaneScale(model: UploadedModel, normalAxis: Axis) { + const dimensions = getScaledDimensions(model); + if (normalAxis === Axis.Y) { + dimensions.clone(planeScaleScratch); + } else { + let scaleArray: number[] = []; + if (normalAxis === Axis.X) { + scaleArray = [dimensions.x, dimensions.y, dimensions.z]; + } else if (normalAxis === Axis.Z) { + scaleArray = [dimensions.y, dimensions.x, dimensions.z]; + } + Cartesian3.fromArray(scaleArray, 0, planeScaleScratch); + } + + return Matrix4.fromScale(planeScaleScratch, scaleMatrixScratch); +} + +const planeDimensionsScratch = new Cartesian2(); +function getPlaneDimensions(model: UploadedModel, normalAxis: Axis) { + const dimensions = getScaledDimensions(model); + let dimensionsArray: number[] = []; + + if (normalAxis === Axis.X) { + dimensionsArray = [dimensions.y, dimensions.z]; + } else if (normalAxis === Axis.Y) { + dimensionsArray = [dimensions.x, dimensions.z]; + } else if (normalAxis === Axis.Z) { + dimensionsArray = [dimensions.x, dimensions.y]; + } + + return Cartesian2.fromArray(dimensionsArray, 0, planeDimensionsScratch); +} + +export function createPlaneEntity( + dataSource: CustomDataSource, + plane: Plane, + model: UploadedModel, + colorOptions: PlaneColorOptions = DefaultPlaneColorOptions, +): void { + const normalAxis: Axis = plane.normal.x + ? Axis.X + : plane.normal.y + ? Axis.Y + : Axis.Z; + + dataSource.entities.add({ + position: new CallbackProperty(() => model.boundingSphere.center, false), + orientation: new CallbackProperty( + () => getRotationQuaternionFromMatrix(model.modelMatrix), + false, + ), + plane: { + plane: new CallbackProperty( + () => Plane.transform(plane, getPlaneScale(model, normalAxis)), + false, + ), + dimensions: new CallbackProperty( + () => getPlaneDimensions(model, normalAxis), + false, + ), + ...colorOptions, + }, + }); +} +export function createEdge( + dataSource: CustomDataSource, + model: UploadedModel, + edge: Cartesian3[], + styles: EdgeStyleOptions = DefaultEdgeStyles, +): void { + const positions = [new Cartesian3(), new Cartesian3()]; + dataSource.entities.add({ + polyline: { + show: true, + positions: new CallbackProperty(() => { + const matrix = getTranslationRotationDimensionsMatrix(model); + Matrix4.multiplyByPoint(matrix, edge[0], positions[0]); + Matrix4.multiplyByPoint(matrix, edge[1], positions[1]); + const centerDiff = getModelCenterDiff(model); + Cartesian3.add(positions[0], centerDiff, positions[0]); + Cartesian3.add(positions[1], centerDiff, positions[1]); + return positions; + }, false), + width: styles.width, + material: styles.material, + arcType: ArcType.NONE, + }, + }); +} + +export function createCornerPoint( + dataSource: CustomDataSource, + model: UploadedModel, + edges: Cartesian3[], + scene: Scene, + styles: CornerPointStyleOptions = DefaultCornerPointStyles, +): void { + const position = new Cartesian3(); + const boundingSphere = new BoundingSphere(); + edges.forEach((localEdge) => { + dataSource.entities.add({ + position: new CallbackProperty(() => { + const matrix = getTranslationRotationDimensionsMatrix(model); + Matrix4.multiplyByPoint(matrix, localEdge, position); + const centerDiff = getModelCenterDiff(model); + Cartesian3.add(position, centerDiff, position); + boundingSphere.center = position; + return position; + }, false), + ellipsoid: { + show: true, + radii: new CallbackProperty(() => { + const pixelSize = getPixelSize(scene, boundingSphere); + const worldRadius = pixelSize * styles.radiusPx; + return new Cartesian3(worldRadius, worldRadius, worldRadius); + }, false), + material: styles.material, + }, + }); + }); +} + +export function showModelBBox( + dataSources: { + topDownPlanesDataSource?: CustomDataSource; + sidePlanesDataSource?: CustomDataSource; + edgeLinesDataSource?: CustomDataSource; + cornerPointsDataSource?: CustomDataSource; + }, + model: UploadedModel, + scene: Scene, + styles: BBoxStyles = { + planeColorOptions: DefaultPlaneColorOptions, + edgeStyleOptions: DefaultEdgeStyles, + cornerPointStyleOptions: DefaultCornerPointStyles, + }, +): void { + const topDownPlanesDataSource = dataSources.topDownPlanesDataSource; + const sidePlanesDataSource = dataSources.sidePlanesDataSource; + if (topDownPlanesDataSource || sidePlanesDataSource) { + SIDE_PLANES.forEach((plane) => { + const normalAxis: Axis = plane.normal.x + ? Axis.X + : plane.normal.y + ? Axis.Y + : Axis.Z; + + const dataSource = + normalAxis === Axis.Z ? topDownPlanesDataSource : sidePlanesDataSource; + if (dataSource) + createPlaneEntity(dataSource, plane, model, styles.planeColorOptions); + }); + } + if (dataSources.edgeLinesDataSource) { + LOCAL_EDGES.forEach((edge) => + createEdge( + dataSources.edgeLinesDataSource, + model, + edge, + styles.edgeStyleOptions, + ), + ); + } + if (dataSources.cornerPointsDataSource) { + LOCAL_EDGES.forEach((edge) => + createCornerPoint( + dataSources.cornerPointsDataSource, + model, + edge, + scene, + styles.cornerPointStyleOptions, + ), + ); + } +} + +const scratchQuaternionRotate = new Quaternion(); +const scratchMatrix3Rotate = new Matrix3(); +const scratchHpr = new HeadingPitchRoll(); +export function rotate( + startPosition: Cartesian2, + endPosition: Cartesian2, + matrixToRotate: Matrix4, +): void { + const dx = endPosition.x - startPosition.x; + const sensitivity = 0.5; + const heading = -dx * sensitivity; + HeadingPitchRoll.fromDegrees(heading, 0, 0, scratchHpr); + + Matrix3.fromQuaternion( + Quaternion.fromHeadingPitchRoll(scratchHpr, scratchQuaternionRotate), + scratchMatrix3Rotate, + ); + + Matrix4.multiplyByMatrix3( + matrixToRotate, + scratchMatrix3Rotate, + matrixToRotate, + ); +} + +const scratchScale = new Cartesian3(); +const scratchScaleMatrix = new Matrix4(); +// todo use axis to have correct scale direction +export function scale( + startPosition: Cartesian2, + endPosition: Cartesian2, + matrix: Matrix4, +): void { + const dx = endPosition.x - startPosition.x; + const sensitivity = 0.01; + const scaleAmount = 1 + dx * sensitivity; + + Matrix4.fromScale( + Cartesian3.fromArray( + [scaleAmount, scaleAmount, scaleAmount], + 0, + scratchScale, + ), + scratchScaleMatrix, + ); + Matrix4.multiply(matrix, scratchScaleMatrix, matrix); +} + +const scratchPickedPositionCart = new Cartographic(); +const scratchPickedPosition = new Cartesian3(); +const scratchUpDirection = new Cartesian3(); +const scratchTop2d = new Cartesian2(); +const scratchBottom2d = new Cartesian2(); +const scratchAxis2D = new Cartesian2(); +const scratchMouseMoveVector = new Cartesian2(); +export function getVerticalMoveVector( + scene: Scene, + pickedPosition: Cartesian3, + endPosition: Cartesian2, + model: UploadedModel, + result: Cartesian3 = new Cartesian3(), +): Cartesian3 { + const cartPickedPosition = Cartographic.fromCartesian( + pickedPosition, + Ellipsoid.default, + scratchPickedPositionCart, + ); + pickedPosition.clone(scratchPickedPosition); + const bottomPos = Cartesian3.fromRadians( + cartPickedPosition.longitude, + cartPickedPosition.latitude, + cartPickedPosition.height - model.id.dimensions.y, + ); + scene.cartesianToCanvasCoordinates(scratchPickedPosition, scratchTop2d); + scene.cartesianToCanvasCoordinates(bottomPos, scratchBottom2d); + Cartesian2.subtract(scratchTop2d, scratchBottom2d, scratchAxis2D); + Cartesian2.subtract(endPosition, scratchTop2d, scratchMouseMoveVector); + const scalar2d = + Cartesian2.dot(scratchMouseMoveVector, scratchAxis2D) / + Cartesian2.dot(scratchAxis2D, scratchAxis2D); + + const scalar3d = + getPixelSize(scene, model.boundingSphere) * + scalar2d * + model.id.dimensions.y; + + Cartesian3.normalize( + getTranslationFromMatrix(model.modelMatrix), + scratchUpDirection, + ); + return Cartesian3.multiplyByScalar(scratchUpDirection, scalar3d, result); +} + +const scratchCameraRay = new Ray(); +const scratchRayPlane = new Cartesian3(); +export function getHorizontalMoveVector( + scene: Scene, + pickedPosition: Cartesian3, + endPosition: Cartesian2, + movePlane: Plane, + result: Cartesian3 = new Cartesian3(), +): Cartesian3 { + const cameraRay = scene.camera.getPickRay(endPosition, scratchCameraRay); + if (!cameraRay) { + return; + } + const nextPosition = IntersectionTests.rayPlane( + cameraRay, + movePlane, + scratchRayPlane, + ); + + if (!nextPosition) { + return; + } + + return Cartesian3.subtract(nextPosition, pickedPosition, result); +} + +export function getPixelSize( + scene: Scene, + boundingSphere: BoundingSphere, +): number { + return scene.camera.getPixelSize( + boundingSphere, + scene.drawingBufferWidth, + scene.drawingBufferHeight, + ); +} diff --git a/src/apps/permits/localStore.ts b/src/plugins/cesium/localStore.ts similarity index 79% rename from src/apps/permits/localStore.ts rename to src/plugins/cesium/localStore.ts index 41149ff..c5382e2 100644 --- a/src/apps/permits/localStore.ts +++ b/src/plugins/cesium/localStore.ts @@ -1,13 +1,15 @@ // todo Just for easier testing. Better place should be find and structure improved. import {Cartesian3, Matrix3, Matrix4, Quaternion} from '@cesium/engine'; -import type {UploadedModel} from '../../plugins/cesium/ngv-plugin-cesium-upload.js'; +import type {UploadedModel} from './ngv-plugin-cesium-upload.js'; -const DB_NAME = 'uploadedModelsStore'; - -export function storeBlobInIndexedDB(blob: Blob, name: string): Promise { +export function storeBlobInIndexedDB( + dbName: string, + blob: Blob, + name: string, +): Promise { return new Promise((resolve, reject) => { - const request = indexedDB.open(DB_NAME, 2); + const request = indexedDB.open(dbName, 2); request.onupgradeneeded = () => { const db = request.result; @@ -34,9 +36,12 @@ export function storeBlobInIndexedDB(blob: Blob, name: string): Promise { }); } -export function getBlobFromIndexedDB(name: string): Promise { +export function getBlobFromIndexedDB( + dbName: string, + name: string, +): Promise { return new Promise((resolve, reject) => { - const request = indexedDB.open(DB_NAME, 2); + const request = indexedDB.open(dbName, 2); request.onupgradeneeded = () => { const db = request.result; @@ -54,7 +59,7 @@ export function getBlobFromIndexedDB(name: string): Promise { getRequest.onsuccess = () => { const blob = getRequest.result; if (blob) { - resolve(blob); // Return the Blob object directly + resolve(blob); } else { reject(new Error('Blob not found')); } @@ -68,9 +73,12 @@ export function getBlobFromIndexedDB(name: string): Promise { }); } -export function deleteFromIndexedDB(name: string): Promise { +export function deleteFromIndexedDB( + dbName: string, + name: string, +): Promise { return new Promise((resolve, reject) => { - const request = indexedDB.open(DB_NAME, 2); + const request = indexedDB.open(dbName, 2); request.onsuccess = () => { const db = request.result; @@ -104,9 +112,10 @@ export type StoredModel = { scale: number[]; }; -export const STORE_KEY = 'localStoreModels'; - -export function updateModelsInLocalStore(models: UploadedModel[]): void { +export function updateModelsInLocalStore( + storeKey: string, + models: UploadedModel[], +): void { const localStoreModels: StoredModel[] = []; models.forEach((model) => { const translation = Matrix4.getTranslation( @@ -129,13 +138,13 @@ export function updateModelsInLocalStore(models: UploadedModel[]): void { scale: [scale.x, scale.y, scale.z], }); }); - localStorage.setItem(STORE_KEY, JSON.stringify(localStoreModels)); + localStorage.setItem(storeKey, JSON.stringify(localStoreModels)); } -export function getStoredModels(): StoredModel[] { - if (!localStorage.getItem(STORE_KEY)) return []; +export function getStoredModels(storeKey: string): StoredModel[] { + if (!localStorage.getItem(storeKey)) return []; try { - return JSON.parse(localStorage.getItem(STORE_KEY)); + return JSON.parse(localStorage.getItem(storeKey)); } catch (e) { console.error('Not possible to parse models from local storage', e); return []; diff --git a/src/plugins/cesium/ngv-plugin-cesium-model-interact.ts b/src/plugins/cesium/ngv-plugin-cesium-model-interact.ts index 83ce312..757ee28 100644 --- a/src/plugins/cesium/ngv-plugin-cesium-model-interact.ts +++ b/src/plugins/cesium/ngv-plugin-cesium-model-interact.ts @@ -6,72 +6,43 @@ import type { DataSource, PrimitiveCollection, DataSourceCollection, + Cartesian2, } from '@cesium/engine'; import { Model, - Axis, Cartesian3, Color, Matrix4, Plane, ScreenSpaceEventHandler, ScreenSpaceEventType, - Cartographic, - Cartesian2, Quaternion, - Matrix3, CustomDataSource, - CallbackProperty, Ellipsoid, - IntersectionTests, - Ray, - ArcType, TranslationRotationScale, - HeadingPitchRoll, Entity, ColorMaterialProperty, } from '@cesium/engine'; import {instantiateModel} from './ngv-cesium-factories.js'; -import type {StoredModel} from '../../apps/permits/localStore.js'; +import type {StoredModel} from './localStore.js'; import { deleteFromIndexedDB, getBlobFromIndexedDB, getStoredModels, updateModelsInLocalStore, -} from '../../apps/permits/localStore.js'; +} from './localStore.js'; import type {UploadedModel} from './ngv-plugin-cesium-upload.js'; import '../ui/ngv-layer-details.js'; import '../ui/ngv-layers-list.js'; - -const SIDE_PLANES: Plane[] = [ - new Plane(new Cartesian3(0, 0, 1), 0.5), - new Plane(new Cartesian3(0, 0, -1), 0.5), - new Plane(new Cartesian3(0, 1, 0), 0.5), - new Plane(new Cartesian3(0, -1, 0), 0.5), - new Plane(new Cartesian3(1, 0, 0), 0.5), - new Plane(new Cartesian3(-1, 0, 0), 0.5), -]; - -const CORNER_POINT_VECTORS = [ - new Cartesian3(0.5, 0.5, 0.5), - new Cartesian3(0.5, -0.5, 0.5), - new Cartesian3(-0.5, -0.5, 0.5), - new Cartesian3(-0.5, 0.5, 0.5), -]; - -const LOCAL_EDGES: [Cartesian3, Cartesian3][] = []; -CORNER_POINT_VECTORS.forEach((vector, i) => { - const upPoint = vector; - const downPoint = Cartesian3.clone(upPoint, new Cartesian3()); - downPoint.z *= -1; - const nextUpPoint = CORNER_POINT_VECTORS[(i + 1) % 4]; - const nextDownPoint = Cartesian3.clone(nextUpPoint, new Cartesian3()); - nextDownPoint.z *= -1; - const verticalEdge: [Cartesian3, Cartesian3] = [upPoint, downPoint]; - // const topEdge: [Cartesian3, Cartesian3] = [nextUpPoint, upPoint]; - // const bottomEdge: [Cartesian3, Cartesian3] = [nextDownPoint, downPoint]; - LOCAL_EDGES.push(verticalEdge); -}); +import type {BBoxStyles} from './interactionHelpers.js'; +import { + getHorizontalMoveVector, + getTranslationFromMatrix, + getVerticalMoveVector, + rotate, + scale, + showModelBBox, +} from './interactionHelpers.js'; type GrabType = 'side' | 'top' | 'edge' | 'corner' | undefined; @@ -83,6 +54,13 @@ export class NgvPluginCesiumModelInteract extends LitElement { private primitiveCollection: PrimitiveCollection; @property({type: Object}) private dataSourceCollection: DataSourceCollection; + @property({type: Object}) + private bboxStyle: BBoxStyles | undefined; + @property({type: Object}) + private storeOptions?: { + localStoreKey: string; + indexDbName: string; + }; @state() private cursor: | 'default' @@ -134,9 +112,13 @@ export class NgvPluginCesiumModelInteract extends LitElement { }); this.primitiveCollection.primitiveRemoved.addEventListener( (p: UploadedModel) => { - deleteFromIndexedDB(p.id.name) - .then(() => this.onPrimitivesChanged()) - .catch((e) => console.error(e)); + if (this.storeOptions) { + deleteFromIndexedDB(this.storeOptions.indexDbName, p.id.name) + .then(() => this.onPrimitivesChanged()) + .catch((e) => console.error(e)); + } else { + this.onPrimitivesChanged(); + } }, ); } @@ -159,164 +141,9 @@ export class NgvPluginCesiumModelInteract extends LitElement { this.models.push(model); } } - updateModelsInLocalStore(this.models); - } - - createPlaneEntity( - planeLocal: Plane, - model: UploadedModel, - color: Color, - ): void { - const normalAxis = planeLocal.normal.x - ? Axis.X - : planeLocal.normal.y - ? Axis.Y - : Axis.Z; - - const getDimensionsAndScale = () => { - const planeDimensions = new Cartesian2(); - const dimensions: Cartesian3 = Cartesian3.clone(model.id.dimensions); - Cartesian3.multiplyComponents( - Matrix4.getScale(this.chosenModel.modelMatrix, new Cartesian3()), - dimensions, - dimensions, - ); - let scale = new Cartesian3(); - - if (normalAxis === Axis.X) { - planeDimensions.x = dimensions.y; - planeDimensions.y = dimensions.z; - scale = new Cartesian3(dimensions.x, dimensions.y, dimensions.z); - } else if (normalAxis === Axis.Y) { - planeDimensions.x = dimensions.x; - planeDimensions.y = dimensions.z; - dimensions.clone(scale); - } else if (normalAxis === Axis.Z) { - planeDimensions.x = dimensions.x; - planeDimensions.y = dimensions.y; - scale = new Cartesian3(dimensions.y, dimensions.x, dimensions.z); - } - const scaleMatrix = Matrix4.fromScale(scale, new Matrix4()); - return { - scaleMatrix, - planeDimensions, - }; - }; - - const dataSource = - normalAxis === Axis.Z - ? this.topDownPlanesDataSource - : this.sidePlanesDataSource; - - dataSource.entities.add({ - position: new CallbackProperty( - () => this.chosenModel.boundingSphere.center, - false, - ), - orientation: new CallbackProperty( - () => - Quaternion.fromRotationMatrix( - Matrix4.getRotation(this.chosenModel.modelMatrix, new Matrix3()), - ), - false, - ), - plane: { - plane: new CallbackProperty(() => { - const {scaleMatrix} = getDimensionsAndScale(); - return Plane.transform(planeLocal, scaleMatrix); - }, false), - dimensions: new CallbackProperty(() => { - const {planeDimensions} = getDimensionsAndScale(); - return planeDimensions; - }, false), - material: color.withAlpha(0.5), - outline: true, - outlineColor: Color.WHITE, - }, - }); - } - - createEdge(localEdge: Cartesian3[]): void { - // todo improve - const positions = [new Cartesian3(), new Cartesian3()]; - this.edgeLinesDataSource.entities.add({ - polyline: { - show: true, - positions: new CallbackProperty(() => { - const modelMatrix = this.chosenModel.modelMatrix; - const matrix = Matrix4.fromTranslationRotationScale( - new TranslationRotationScale( - Matrix4.getTranslation(modelMatrix, new Cartesian3()), - Quaternion.fromRotationMatrix( - Matrix4.getRotation(modelMatrix, new Matrix3()), - ), - Cartesian3.multiplyComponents( - Matrix4.getScale( - this.chosenModel.modelMatrix, - new Cartesian3(), - ), - this.chosenModel.id.dimensions, - new Cartesian3(), - ), - ), - ); - Matrix4.multiplyByPoint(matrix, localEdge[0], positions[0]); - Matrix4.multiplyByPoint(matrix, localEdge[1], positions[1]); - const centerDiff = Cartesian3.subtract( - this.chosenModel.boundingSphere.center, - this.position, - new Cartesian3(), - ); - Cartesian3.add(positions[0], centerDiff, positions[0]); - Cartesian3.add(positions[1], centerDiff, positions[1]); - return positions; - }, false), - width: 10, - material: Color.WHITE.withAlpha(0.3), - arcType: ArcType.NONE, - }, - }); - } - - createCornerPoint(localEdges: Cartesian3[]): void { - // todo improve - const position = new Cartesian3(); - localEdges.forEach((localEdge) => { - this.cornerPointsDataSource.entities.add({ - position: new CallbackProperty(() => { - const modelMatrix = this.chosenModel.modelMatrix; - const matrix = Matrix4.fromTranslationRotationScale( - new TranslationRotationScale( - Matrix4.getTranslation(modelMatrix, new Cartesian3()), - Quaternion.fromRotationMatrix( - Matrix4.getRotation(modelMatrix, new Matrix3()), - ), - Cartesian3.multiplyComponents( - Matrix4.getScale( - this.chosenModel.modelMatrix, - new Cartesian3(), - ), - this.chosenModel.id.dimensions, - new Cartesian3(), - ), - ), - ); - Matrix4.multiplyByPoint(matrix, localEdge, position); - const centerDiff = Cartesian3.subtract( - this.chosenModel.boundingSphere.center, - this.position, - new Cartesian3(), - ); - Cartesian3.add(position, centerDiff, position); - return position; - }, false), - ellipsoid: { - show: true, - radii: new Cartesian3(10, 10, 10), - material: Color.BROWN, - }, - }); - }); + if (this.storeOptions) { + updateModelsInLocalStore(this.storeOptions.localStoreKey, this.models); + } } onClick(evt: ScreenSpaceEventHandler.PositionedEvent): void { @@ -325,9 +152,17 @@ export class NgvPluginCesiumModelInteract extends LitElement { if (!this.chosenModel) { this.chosenModel = model; Matrix4.getTranslation(this.chosenModel.modelMatrix, this.position); - SIDE_PLANES.forEach((p) => this.createPlaneEntity(p, model, Color.RED)); - LOCAL_EDGES.forEach((localEdge) => this.createEdge(localEdge)); - LOCAL_EDGES.forEach((localEdge) => this.createCornerPoint(localEdge)); + showModelBBox( + { + topDownPlanesDataSource: this.topDownPlanesDataSource, + sidePlanesDataSource: this.sidePlanesDataSource, + edgeLinesDataSource: this.edgeLinesDataSource, + cornerPointsDataSource: this.cornerPointsDataSource, + }, + this.chosenModel, + this.viewer.scene, + this.bboxStyle, + ); } } } @@ -351,53 +186,26 @@ export class NgvPluginCesiumModelInteract extends LitElement { } onMouseMove(evt: ScreenSpaceEventHandler.MotionEvent): void { - // todo split on smaller functions - // todo add scratches if (this.grabType && this.chosenModel) { const endPosition = this.viewer.scene.pickPosition(evt.endPosition); if (!endPosition) return; if (this.grabType === 'edge') { - const dx = evt.endPosition.x - evt.startPosition.x; - const sensitivity = 0.05; - const hpr = new HeadingPitchRoll(); - hpr.heading = -dx * sensitivity; - - const rotation = Matrix3.fromQuaternion( - Quaternion.fromHeadingPitchRoll(hpr), - ); - - Matrix4.multiplyByMatrix3( - this.chosenModel.modelMatrix, - rotation, + rotate( + evt.startPosition, + evt.endPosition, this.chosenModel.modelMatrix, ); - return; } if (this.grabType === 'corner') { - const dx = evt.endPosition.x - evt.startPosition.x; - const sensitivity = 0.01; - const scaleAmount = 1 + dx * sensitivity; - - // Apply scale to the model matrix of the primitive - const scaleMatrix = Matrix4.fromScale( - new Cartesian3(scaleAmount, scaleAmount, scaleAmount), - ); - Matrix4.multiply( - this.chosenModel.modelMatrix, - scaleMatrix, - this.chosenModel.modelMatrix, - ); + scale(evt.startPosition, evt.endPosition, this.chosenModel.modelMatrix); return; } if (this.dragStart) { Cartesian3.subtract( endPosition, - Matrix4.getTranslation( - this.chosenModel.modelMatrix, - new Cartesian3(), - ), + getTranslationFromMatrix(this.chosenModel.modelMatrix), this.pickedPointOffset, ); this.dragStart = false; @@ -410,76 +218,31 @@ export class NgvPluginCesiumModelInteract extends LitElement { ); if (this.grabType === 'top') { - const cartPickedPosition = Cartographic.fromCartesian(pickedPosition); - const topPos = pickedPosition.clone(); - const bottomPos = Cartesian3.fromRadians( - cartPickedPosition.longitude, - cartPickedPosition.latitude, - cartPickedPosition.height - this.chosenModel.id.dimensions.y, - ); - const top2d = this.viewer.scene.cartesianToCanvasCoordinates( - topPos, - new Cartesian2(), - ); - const bottom2d = this.viewer.scene.cartesianToCanvasCoordinates( - bottomPos, - new Cartesian2(), - ); - const axis2D = Cartesian2.subtract(top2d, bottom2d, new Cartesian2()); - const scratchMouseMoveVector = Cartesian2.subtract( + getVerticalMoveVector( + this.viewer.scene, + pickedPosition, evt.endPosition, - top2d, - new Cartesian2(), - ); - const scalar2d = - Cartesian2.dot(scratchMouseMoveVector, axis2D) / - Cartesian2.dot(axis2D, axis2D); - - const pixelSize = this.viewer.scene.camera.getPixelSize( - this.chosenModel.boundingSphere, - this.viewer.scene.drawingBufferWidth, - this.viewer.scene.drawingBufferHeight, + this.chosenModel, + this.moveStep, ); - const scalar3d = - pixelSize * scalar2d * this.chosenModel.id.dimensions.y; - - const upDirection = Cartesian3.normalize( - this.position, - new Cartesian3(), - ); - Cartesian3.multiplyByScalar(upDirection, scalar3d, this.moveStep); } else if (this.grabType === 'side') { - const cameraRay = this.viewer.scene.camera.getPickRay( + getHorizontalMoveVector( + this.viewer.scene, + pickedPosition, evt.endPosition, - new Ray(), - ); - if (!cameraRay) { - return; - } - const nextPosition = IntersectionTests.rayPlane( - cameraRay, this.movePlane, + this.moveStep, ); - - if (!nextPosition) { - return; - } - - Cartesian3.subtract(nextPosition, pickedPosition, this.moveStep); } Cartesian3.add(this.position, this.moveStep, this.position); - Matrix4.fromTranslationRotationScale( - new TranslationRotationScale( - this.position, - Quaternion.fromRotationMatrix( - Matrix4.getRotation(this.chosenModel.modelMatrix, new Matrix3()), - ), - Matrix4.getScale(this.chosenModel.modelMatrix, new Cartesian3()), - ), + Matrix4.setTranslation( + this.chosenModel.modelMatrix, + this.position, this.chosenModel.modelMatrix, ); + return; } this.updateCursor(evt.endPosition); @@ -571,13 +334,36 @@ export class NgvPluginCesiumModelInteract extends LitElement { return undefined; } - firstUpdated(_changedProperties: PropertyValues): void { - // todo improve code - const models = getStoredModels(); - if (models) { - Promise.all( + initDataSources(): void { + this.dataSourceCollection + .add(new CustomDataSource()) + .then((dataSource) => (this.sidePlanesDataSource = dataSource)) + .catch((e) => console.error(e)); + this.dataSourceCollection + .add(new CustomDataSource()) + .then((dataSource) => (this.topDownPlanesDataSource = dataSource)) + .catch((e) => console.error(e)); + this.dataSourceCollection + .add(new CustomDataSource()) + .then((dataSource) => (this.edgeLinesDataSource = dataSource)) + .catch((e) => console.error(e)); + this.dataSourceCollection + .add(new CustomDataSource()) + .then((dataSource) => (this.cornerPointsDataSource = dataSource)) + .catch((e) => console.error(e)); + } + + async initModelsAndEvents(): Promise { + const models = this.storeOptions + ? getStoredModels(this.storeOptions.localStoreKey) + : undefined; + if (models?.length) { + await Promise.all( models.map(async (m: StoredModel) => { - const blob = await getBlobFromIndexedDB(m.name); + const blob = await getBlobFromIndexedDB( + this.storeOptions.indexDbName, + m.name, + ); const model = await instantiateModel({ type: 'model', options: { @@ -598,33 +384,18 @@ export class NgvPluginCesiumModelInteract extends LitElement { }); this.primitiveCollection.add(model); }), - ) - .then(() => { - this.onPrimitivesChanged(); - this.initEvents(); - this.viewer.scene.requestRender(); - }) - .catch((e) => console.error(e)); + ); + this.onPrimitivesChanged(); + this.initEvents(); + this.viewer.scene.requestRender(); } else { this.initEvents(); } - // todo improve - this.dataSourceCollection - .add(new CustomDataSource()) - .then((dataSource) => (this.sidePlanesDataSource = dataSource)) - .catch((e) => console.error(e)); - this.dataSourceCollection - .add(new CustomDataSource()) - .then((dataSource) => (this.topDownPlanesDataSource = dataSource)) - .catch((e) => console.error(e)); - this.dataSourceCollection - .add(new CustomDataSource()) - .then((dataSource) => (this.edgeLinesDataSource = dataSource)) - .catch((e) => console.error(e)); - this.dataSourceCollection - .add(new CustomDataSource()) - .then((dataSource) => (this.cornerPointsDataSource = dataSource)) - .catch((e) => console.error(e)); + } + + firstUpdated(_changedProperties: PropertyValues): void { + this.initModelsAndEvents().catch((e) => console.error(e)); + this.initDataSources(); super.firstUpdated(_changedProperties); } diff --git a/src/plugins/cesium/ngv-plugin-cesium-upload.ts b/src/plugins/cesium/ngv-plugin-cesium-upload.ts index 0e8e24d..d10e53b 100644 --- a/src/plugins/cesium/ngv-plugin-cesium-upload.ts +++ b/src/plugins/cesium/ngv-plugin-cesium-upload.ts @@ -17,10 +17,7 @@ import {customElement, property} from 'lit/decorators.js'; import '../ui/ngv-upload.js'; import {instantiateModel} from './ngv-cesium-factories.js'; import type {FileUploadDetails} from '../ui/ngv-upload.js'; -import { - storeBlobInIndexedDB, - updateModelsInLocalStore, -} from '../../apps/permits/localStore.js'; +import {storeBlobInIndexedDB, updateModelsInLocalStore} from './localStore.js'; const cartographicScratch = new Cartographic(); @@ -49,8 +46,13 @@ export class NgvPluginCesiumUpload extends LitElement { private viewer: CesiumWidget; @property({type: Object}) private primitiveCollection: PrimitiveCollection; + @property({type: Object}) + private storeOptions?: { + localStoreKey: string; + indexDbName: string; + }; private eventHandler: ScreenSpaceEventHandler | null = null; - private uploadedModel: Model | undefined; + private uploadedModel: UploadedModel | undefined; async upload(fileDetails: FileUploadDetails): Promise { const response = await fetch(fileDetails.url); @@ -78,7 +80,13 @@ export class NgvPluginCesiumUpload extends LitElement { }, }, }); - await storeBlobInIndexedDB(new Blob([arrayBuffer]), fileDetails.name); + if (this.storeOptions) { + await storeBlobInIndexedDB( + this.storeOptions.indexDbName, + new Blob([arrayBuffer]), + fileDetails.name, + ); + } this.primitiveCollection.add(this.uploadedModel); this.viewer.scene.requestRender(); this.showControls(); @@ -101,12 +109,13 @@ export class NgvPluginCesiumUpload extends LitElement { this.viewer.canvas.style.cursor = 'default'; this.eventHandler.destroy(); this.eventHandler = null; - // todo improve - const models: Model[] = []; + const models: UploadedModel[] = []; for (let i = 0; i < this.primitiveCollection.length; i++) { - models.push(this.primitiveCollection.get(i)); + models.push(this.primitiveCollection.get(i)); + } + if (this.storeOptions) { + updateModelsInLocalStore(this.storeOptions.localStoreKey, models); } - updateModelsInLocalStore(models); } onMouseMove(event: ScreenSpaceEventHandler.MotionEvent): void { From 11189d2ec0fb3e154c18466640044477e0b0787e Mon Sep 17 00:00:00 2001 From: vladyslavtk Date: Thu, 14 Nov 2024 18:53:05 +0200 Subject: [PATCH 10/15] load models from catalog, some refactoring --- src/apps/permits/demoPermitConfig.ts | 6 +- src/apps/permits/index.ts | 51 ++++------ src/catalogs/demoCatalog.ts | 6 ++ src/interfaces/cesium/ingv-cesium-context.ts | 6 +- src/interfaces/cesium/ingv-layers.ts | 20 +++- src/plugins/cesium/interactionHelpers.ts | 95 +++++++++++++------ src/plugins/cesium/localStore.ts | 4 +- src/plugins/cesium/ngv-cesium-factories.ts | 81 +++++++++++++--- .../ngv-plugin-cesium-model-interact.ts | 27 +++--- .../cesium/ngv-plugin-cesium-upload.ts | 77 ++------------- 10 files changed, 209 insertions(+), 164 deletions(-) diff --git a/src/apps/permits/demoPermitConfig.ts b/src/apps/permits/demoPermitConfig.ts index ecc501e..6febb17 100644 --- a/src/apps/permits/demoPermitConfig.ts +++ b/src/apps/permits/demoPermitConfig.ts @@ -1,4 +1,5 @@ import type {IPermitsConfig} from './ingv-config-permits.js'; +import {PrimitiveCollection} from '@cesium/engine'; export const config: IPermitsConfig = { languages: ['de', 'fr', 'en', 'it'], @@ -28,10 +29,13 @@ export const config: IPermitsConfig = { }, layers: { // tilesets: ['@cesium/googlePhotorealistic'], - // models: ['@demo/sofa', '@demo/thatopensmall'], + models: ['@demo/sofa', '@demo/thatopensmall'], imageries: ['@geoadmin/pixel-karte-farbe'], // terrain: '@geoadmin/terrain', }, + collections: { + models: new PrimitiveCollection(), + }, camera: { position: [6.628484, 46.5, 100], orientation: { diff --git a/src/apps/permits/index.ts b/src/apps/permits/index.ts index da8d104..a405e64 100644 --- a/src/apps/permits/index.ts +++ b/src/apps/permits/index.ts @@ -13,15 +13,9 @@ import type {IPermitsConfig} from './ingv-config-permits.js'; import '../../plugins/cesium/ngv-plugin-cesium-widget'; import '../../plugins/cesium/ngv-plugin-cesium-upload'; import '../../plugins/cesium/ngv-plugin-cesium-model-interact'; -import type {CesiumWidget, DataSourceCollection, Model} from '@cesium/engine'; +import type {CesiumWidget, DataSourceCollection} from '@cesium/engine'; -import { - Math as CesiumMath, - Ellipsoid, - HeadingPitchRoll, - Transforms, - PrimitiveCollection, -} from '@cesium/engine'; +import {PrimitiveCollection} from '@cesium/engine'; import type {ViewerInitializedDetails} from '../../plugins/cesium/ngv-plugin-cesium-widget.js'; @customElement('ngv-app-permits') @@ -29,33 +23,14 @@ import type {ViewerInitializedDetails} from '../../plugins/cesium/ngv-plugin-ces export class NgvAppPermits extends ABaseApp { @state() private viewer: CesiumWidget; - private primitiveCollection: PrimitiveCollection = new PrimitiveCollection(); + private uploadedModelsCollection: PrimitiveCollection = + new PrimitiveCollection(); private dataSourceCollection: DataSourceCollection; constructor() { super(() => import('./demoPermitConfig.js')); } - modelCallback(name: string, model: Model): void { - // This position the model where the camera is - console.log('positioning', name); - const positionClone = this.viewer.camera.position.clone(); - - const fixedFrameTransform = Transforms.localFrameToFixedFrameGenerator( - 'north', - 'west', - ); - - const modelOrientation = [90, 0, 0]; - const modelMatrix = Transforms.headingPitchRollToFixedFrame( - positionClone, - new HeadingPitchRoll(...modelOrientation.map(CesiumMath.toRadians)), - Ellipsoid.WGS84, - fixedFrameTransform, - ); - model.modelMatrix = modelMatrix; - } - render(): HTMLTemplateResult { const r = super.render(); if (r && !this.config) { @@ -67,24 +42,34 @@ export class NgvAppPermits extends ABaseApp { slot="menu" style="display: flex; flex-direction: column; row-gap: 10px;" > + +
) => { this.viewer = evt.detail.viewer; - this.viewer.scene.primitives.add(this.primitiveCollection); + this.viewer.scene.primitives.add(this.uploadedModelsCollection); this.dataSourceCollection = evt.detail.dataSourceCollection; }} > diff --git a/src/catalogs/demoCatalog.ts b/src/catalogs/demoCatalog.ts index 9b23a64..9645de3 100644 --- a/src/catalogs/demoCatalog.ts +++ b/src/catalogs/demoCatalog.ts @@ -12,6 +12,9 @@ export const catalog: INGVCatalog = { credit: 'test', }, }, + position: [6.628484, 46.5], + height: 0, + rotation: 0, }, sofa: { type: 'model', @@ -19,6 +22,9 @@ export const catalog: INGVCatalog = { url: 'https://raw.GithubUserContent.com/KhronosGroup/glTF-Sample-Assets/main/./Models/SheenWoodLeatherSofa/glTF-Binary/SheenWoodLeatherSofa.glb', credit: 'Khonos', }, + position: [6.628484, 46.5], + height: 0, + rotation: 0, }, // to complete }, diff --git a/src/interfaces/cesium/ingv-cesium-context.ts b/src/interfaces/cesium/ingv-cesium-context.ts index 1fb9710..3de5c6d 100644 --- a/src/interfaces/cesium/ingv-cesium-context.ts +++ b/src/interfaces/cesium/ingv-cesium-context.ts @@ -1,4 +1,4 @@ -import type {CesiumWidget, Globe} from '@cesium/engine'; +import type {CesiumWidget, Globe, PrimitiveCollection} from '@cesium/engine'; import type {INGVCatalog} from './ingv-catalog.js'; export interface IngvCesiumContext { @@ -14,6 +14,10 @@ export interface IngvCesiumContext { models?: string[]; imageries: string[]; }; + collections?: { + models?: PrimitiveCollection; + tiles3d?: PrimitiveCollection; + }; /** * These are lists of selected layers. */ diff --git a/src/interfaces/cesium/ingv-layers.ts b/src/interfaces/cesium/ingv-layers.ts index a80d6e2..75525a8 100644 --- a/src/interfaces/cesium/ingv-layers.ts +++ b/src/interfaces/cesium/ingv-layers.ts @@ -6,6 +6,7 @@ import type { } from '@cesium/engine'; import type {CesiumTerrainProvider, Model} from '@cesium/engine'; +import type {Cartesian3} from '@cesium/engine'; export type INGVCesiumImageryTypes = | INGVCesiumWMSImagery @@ -18,7 +19,7 @@ export type INGVCesiumAllTypes = | INGVCesiumImageryTypes; export type INGVCesiumAllPrimitiveTypes = - | INGVCesiumModel + | INGVCesiumModelConfig | INGVIFC | INGVCesium3DTiles; @@ -29,17 +30,30 @@ export interface INGVCesium3DTiles { options?: ConstructorParameters[0]; } -export interface INGVCesiumModel { +export interface INGVCesiumModelConfig { type: 'model'; options?: Parameters[0]; + position?: [number, number]; + height?: number; + rotation?: number; +} + +export interface INGVCesiumModel extends Model { + id: { + dimensions?: Cartesian3; + name: string; + }; } export interface INGVIFC { type: 'ifc'; url: string; options?: { - modelOptions: Omit; + modelOptions: Omit; }; + position: [number, number]; + height: number; + rotation: number; } export interface INGVCesiumTerrain { diff --git a/src/plugins/cesium/interactionHelpers.ts b/src/plugins/cesium/interactionHelpers.ts index 62acc99..5e9b82c 100644 --- a/src/plugins/cesium/interactionHelpers.ts +++ b/src/plugins/cesium/interactionHelpers.ts @@ -1,8 +1,8 @@ import type {CustomDataSource, Model, Scene} from '@cesium/engine'; import { - BoundingSphere, ArcType, Axis, + BoundingSphere, CallbackProperty, Cartesian2, Cartesian3, @@ -18,7 +18,7 @@ import { Ray, TranslationRotationScale, } from '@cesium/engine'; -import type {UploadedModel} from './ngv-plugin-cesium-upload.js'; +import type {INGVCesiumModel} from '../../interfaces/cesium/ingv-layers.js'; export type PlaneColorOptions = { material?: Color; @@ -82,7 +82,7 @@ CORNER_POINT_VECTORS.forEach((vector, i) => { const nextUpPoint = CORNER_POINT_VECTORS[(i + 1) % 4]; const nextDownPoint = Cartesian3.clone(nextUpPoint, new Cartesian3()); nextDownPoint.z *= -1; - const verticalEdge: [Cartesian3, Cartesian3] = [upPoint, downPoint]; + const verticalEdge: [Cartesian3, Cartesian3] = [downPoint, upPoint]; // const topEdge: [Cartesian3, Cartesian3] = [nextUpPoint, upPoint]; // const bottomEdge: [Cartesian3, Cartesian3] = [nextDownPoint, downPoint]; LOCAL_EDGES.push(verticalEdge); @@ -94,7 +94,7 @@ export function getScaleFromMatrix(matrix: Matrix4): Cartesian3 { } const dimensionsScratch = new Cartesian3(); -function getScaledDimensions(model: UploadedModel): Cartesian3 { +function getScaledDimensions(model: INGVCesiumModel): Cartesian3 { Cartesian3.clone(model.id.dimensions, dimensionsScratch); Cartesian3.multiplyComponents( getScaleFromMatrix(model.modelMatrix), @@ -120,7 +120,7 @@ export function getTranslationFromMatrix(matrix: Matrix4): Cartesian3 { const scratchTranslationRotationDimensionsMatrix = new Matrix4(); export function getTranslationRotationDimensionsMatrix( - model: UploadedModel, + model: INGVCesiumModel, result = scratchTranslationRotationDimensionsMatrix, ): Matrix4 { return Matrix4.fromTranslationRotationScale( @@ -159,25 +159,19 @@ export function getModelCenterDiff(model: Model): Cartesian3 { const scaleMatrixScratch = new Matrix4(); const planeScaleScratch = new Cartesian3(); -function getPlaneScale(model: UploadedModel, normalAxis: Axis) { +function getPlaneScale(model: INGVCesiumModel) { const dimensions = getScaledDimensions(model); - if (normalAxis === Axis.Y) { - dimensions.clone(planeScaleScratch); - } else { - let scaleArray: number[] = []; - if (normalAxis === Axis.X) { - scaleArray = [dimensions.x, dimensions.y, dimensions.z]; - } else if (normalAxis === Axis.Z) { - scaleArray = [dimensions.y, dimensions.x, dimensions.z]; - } - Cartesian3.fromArray(scaleArray, 0, planeScaleScratch); - } + Cartesian3.fromArray( + [dimensions.x, dimensions.y, dimensions.z], + 0, + planeScaleScratch, + ); return Matrix4.fromScale(planeScaleScratch, scaleMatrixScratch); } const planeDimensionsScratch = new Cartesian2(); -function getPlaneDimensions(model: UploadedModel, normalAxis: Axis) { +function getPlaneDimensions(model: INGVCesiumModel, normalAxis: Axis) { const dimensions = getScaledDimensions(model); let dimensionsArray: number[] = []; @@ -195,7 +189,7 @@ function getPlaneDimensions(model: UploadedModel, normalAxis: Axis) { export function createPlaneEntity( dataSource: CustomDataSource, plane: Plane, - model: UploadedModel, + model: INGVCesiumModel, colorOptions: PlaneColorOptions = DefaultPlaneColorOptions, ): void { const normalAxis: Axis = plane.normal.x @@ -212,7 +206,7 @@ export function createPlaneEntity( ), plane: { plane: new CallbackProperty( - () => Plane.transform(plane, getPlaneScale(model, normalAxis)), + () => Plane.transform(plane, getPlaneScale(model)), false, ), dimensions: new CallbackProperty( @@ -225,7 +219,7 @@ export function createPlaneEntity( } export function createEdge( dataSource: CustomDataSource, - model: UploadedModel, + model: INGVCesiumModel, edge: Cartesian3[], styles: EdgeStyleOptions = DefaultEdgeStyles, ): void { @@ -251,7 +245,7 @@ export function createEdge( export function createCornerPoint( dataSource: CustomDataSource, - model: UploadedModel, + model: INGVCesiumModel, edges: Cartesian3[], scene: Scene, styles: CornerPointStyleOptions = DefaultCornerPointStyles, @@ -288,7 +282,7 @@ export function showModelBBox( edgeLinesDataSource?: CustomDataSource; cornerPointsDataSource?: CustomDataSource; }, - model: UploadedModel, + model: INGVCesiumModel, scene: Scene, styles: BBoxStyles = { planeColorOptions: DefaultPlaneColorOptions, @@ -394,7 +388,7 @@ export function getVerticalMoveVector( scene: Scene, pickedPosition: Cartesian3, endPosition: Cartesian2, - model: UploadedModel, + model: INGVCesiumModel, result: Cartesian3 = new Cartesian3(), ): Cartesian3 { const cartPickedPosition = Cartographic.fromCartesian( @@ -436,10 +430,10 @@ export function getHorizontalMoveVector( endPosition: Cartesian2, movePlane: Plane, result: Cartesian3 = new Cartesian3(), -): Cartesian3 { +): Cartesian3 | undefined { const cameraRay = scene.camera.getPickRay(endPosition, scratchCameraRay); if (!cameraRay) { - return; + return undefined; } const nextPosition = IntersectionTests.rayPlane( cameraRay, @@ -448,7 +442,7 @@ export function getHorizontalMoveVector( ); if (!nextPosition) { - return; + return undefined; } return Cartesian3.subtract(nextPosition, pickedPosition, result); @@ -464,3 +458,50 @@ export function getPixelSize( scene.drawingBufferHeight, ); } + +type GltfJson = { + bufferViews: {byteOffset: number; byteLength: number}[]; + accessors: Record; + meshes: { + primitives: { + attributes: { + POSITION: number; + }; + }[]; + }[]; +}; + +export function getDimensions(model: Model): Cartesian3 { + // @ts-expect-error loader is not part of API + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-assignment + const json: GltfJson = model.loader.gltfJson; + + const diameter = model.boundingSphere.radius * 2; + const min = new Cartesian3(diameter, diameter, diameter); + const max = new Cartesian3( + -Number.MAX_VALUE, + -Number.MAX_VALUE, + -Number.MAX_VALUE, + ); + + json.meshes.forEach(function (mesh) { + mesh.primitives.forEach(function (primitive) { + const positionAccessor = json.accessors[primitive.attributes.POSITION]; + + if (positionAccessor) { + if (positionAccessor.min) { + min.x = Math.min(min.x, positionAccessor.min[1]); + min.y = Math.min(min.y, positionAccessor.min[0]); + min.z = Math.min(min.z, positionAccessor.min[2]); + } + if (positionAccessor.max) { + max.x = Math.max(max.x, positionAccessor.max[1]); + max.y = Math.max(max.y, positionAccessor.max[0]); + max.z = Math.max(max.z, positionAccessor.max[2]); + } + } + }); + }); + + return Cartesian3.subtract(max, min, new Cartesian3()); +} diff --git a/src/plugins/cesium/localStore.ts b/src/plugins/cesium/localStore.ts index c5382e2..33334d4 100644 --- a/src/plugins/cesium/localStore.ts +++ b/src/plugins/cesium/localStore.ts @@ -1,7 +1,7 @@ // todo Just for easier testing. Better place should be find and structure improved. import {Cartesian3, Matrix3, Matrix4, Quaternion} from '@cesium/engine'; -import type {UploadedModel} from './ngv-plugin-cesium-upload.js'; +import type {INGVCesiumModel} from '../../interfaces/cesium/ingv-layers.js'; export function storeBlobInIndexedDB( dbName: string, @@ -114,7 +114,7 @@ export type StoredModel = { export function updateModelsInLocalStore( storeKey: string, - models: UploadedModel[], + models: INGVCesiumModel[], ): void { const localStoreModels: StoredModel[] = []; models.forEach((model) => { diff --git a/src/plugins/cesium/ngv-cesium-factories.ts b/src/plugins/cesium/ngv-cesium-factories.ts index 9eb93b1..dce795e 100644 --- a/src/plugins/cesium/ngv-cesium-factories.ts +++ b/src/plugins/cesium/ngv-cesium-factories.ts @@ -7,15 +7,20 @@ import { Model, DataSourceCollection, DataSourceDisplay, + Cartographic, + HeadingPitchRoll, + Transforms, + Ellipsoid, } from '@cesium/engine'; import type { INGVCesium3DTiles, - INGVCesiumModel, + INGVCesiumModelConfig, INGVCesiumAllTypes, INGVCesiumImageryTypes, INGVCesiumTerrain, INGVIFC, + INGVCesiumModel, } from '../../interfaces/cesium/ingv-layers.js'; import { Cesium3DTileset, @@ -26,6 +31,7 @@ import { } from '@cesium/engine'; import type {IngvCesiumContext} from '../../interfaces/cesium/ingv-cesium-context.js'; import type {INGVCatalog} from '../../interfaces/cesium/ingv-catalog.js'; +import {getDimensions} from './interactionHelpers.js'; function withExtra(options: T, extra: Record): T { if (!extra) { @@ -53,10 +59,16 @@ export async function instantiateTerrain( } export async function instantiateModel( - config: INGVCesiumModel, + config: INGVCesiumModelConfig, extraOptions?: Record, -): Promise { - return Model.fromGltfAsync(withExtra(config.options, extraOptions)); +): Promise { + const model: INGVCesiumModel = await Model.fromGltfAsync( + withExtra(config.options, extraOptions), + ); + model.readyEvent.addEventListener(() => { + model.id.dimensions = getDimensions(model); + }); + return model; } export async function instantiate3dTileset( @@ -153,7 +165,7 @@ export function is3dTilesetConfig( export function isModelConfig( config: INGVCesiumAllTypes, -): config is INGVCesiumModel { +): config is INGVCesiumModelConfig { return config?.type === 'model'; } @@ -254,6 +266,15 @@ export async function initCesiumWidget( Object.assign(viewer.scene.globe, cesiumContext.globeOptions); } + if (cesiumContext.collections) { + if (cesiumContext.collections.models) { + viewer.scene.primitives.add(cesiumContext.collections.models); + } + if (cesiumContext.collections.tiles3d) { + viewer.scene.primitives.add(cesiumContext.collections.tiles3d); + } + } + const dataSourceCollection = new DataSourceCollection(); const dataSourceDisplay = new DataSourceDisplay({ scene: viewer.scene, @@ -289,14 +310,18 @@ export async function initCesiumWidget( stuffToDo.push( instantiate3dTileset(config, cesiumContext.layerOptions[name]).then( (tileset) => { - viewer.scene.primitives.add(tileset); + if (cesiumContext.collections?.tiles3d) { + cesiumContext.collections.tiles3d.add(tileset); + } else { + viewer.scene.primitives.add(tileset); + } }, ), ); }); - const modelPromises = cesiumContext.layers.models?.map(async (name) => { - let config = resolvedLayers[name]; + const modelPromises = cesiumContext.layers.models?.map(async (path) => { + let config = resolvedLayers[path]; let toRevokeUrl: string; if (isIFCConfig(config)) { @@ -317,16 +342,27 @@ export async function initCesiumWidget( console.log('IFC transformed to glTF', metadata, coordinationMatrix); const glbBlob = new Blob([glb]); toRevokeUrl = URL.createObjectURL(glbBlob); - const modelConfig: INGVCesiumModel = { + const modelConfig: INGVCesiumModelConfig = { type: 'model', options: Object.assign({}, modelOptions, { url: toRevokeUrl, + id: { + name: ifcUrl, + }, }), + height: config.height, + position: config.position, + rotation: config.rotation, }; config = modelConfig; + } else if (isModelConfig(config)) { + config = { + ...config, + options: {...config.options, id: {name: config.options.url}}, + }; } if (isModelConfig(config)) { - const bmConfig: Omit = { + const bmConfig: Omit = { scene: viewer.scene, gltfCallback(gltf) { // FIXME: here we can enable animations, ... @@ -335,16 +371,33 @@ export async function initCesiumWidget( }, // heightReference: HeightReference.CLAMP_TO_GROUND, }; + const modelMatrix = Transforms.headingPitchRollToFixedFrame( + Cartographic.toCartesian( + Cartographic.fromDegrees( + config.position[0], + config.position[1], + config.height, + ), + ), + new HeadingPitchRoll(CesiumMath.toRadians(config.rotation)), + Ellipsoid.WGS84, + Transforms.localFrameToFixedFrameGenerator('north', 'west'), + ); stuffToDo.push( instantiateModel( config, - Object.assign(bmConfig, cesiumContext.layerOptions[name]), + Object.assign(bmConfig, cesiumContext.layerOptions[path], { + modelMatrix, + }), ) .then( (model) => { - console.log('Got model!', config); - modelCallback(name, model); - viewer.scene.primitives.add(model); + modelCallback(path, model); + if (cesiumContext.collections?.models) { + cesiumContext.collections.models.add(model); + } else { + viewer.scene.primitives.add(model); + } }, (e) => { console.error('o', e); diff --git a/src/plugins/cesium/ngv-plugin-cesium-model-interact.ts b/src/plugins/cesium/ngv-plugin-cesium-model-interact.ts index 757ee28..0a369e9 100644 --- a/src/plugins/cesium/ngv-plugin-cesium-model-interact.ts +++ b/src/plugins/cesium/ngv-plugin-cesium-model-interact.ts @@ -31,7 +31,6 @@ import { getStoredModels, updateModelsInLocalStore, } from './localStore.js'; -import type {UploadedModel} from './ngv-plugin-cesium-upload.js'; import '../ui/ngv-layer-details.js'; import '../ui/ngv-layers-list.js'; import type {BBoxStyles} from './interactionHelpers.js'; @@ -43,6 +42,7 @@ import { scale, showModelBBox, } from './interactionHelpers.js'; +import type {INGVCesiumModel} from '../../interfaces/cesium/ingv-layers.js'; type GrabType = 'side' | 'top' | 'edge' | 'corner' | undefined; @@ -61,6 +61,10 @@ export class NgvPluginCesiumModelInteract extends LitElement { localStoreKey: string; indexDbName: string; }; + @property({type: Object}) + private options?: { + listTitle: string; + }; @state() private cursor: | 'default' @@ -70,11 +74,11 @@ export class NgvPluginCesiumModelInteract extends LitElement { | 'ew-resize' | 'nesw-resize' = 'default'; @state() - private chosenModel: UploadedModel | undefined; + private chosenModel: INGVCesiumModel | undefined; @state() private position: Cartesian3 = new Cartesian3(); @state() - private models: UploadedModel[] = []; + private models: INGVCesiumModel[] = []; private eventHandler: ScreenSpaceEventHandler | undefined; private sidePlanesDataSource: DataSource | undefined; private topDownPlanesDataSource: DataSource | undefined; @@ -111,7 +115,7 @@ export class NgvPluginCesiumModelInteract extends LitElement { this.onPrimitivesChanged(); }); this.primitiveCollection.primitiveRemoved.addEventListener( - (p: UploadedModel) => { + (p: INGVCesiumModel) => { if (this.storeOptions) { deleteFromIndexedDB(this.storeOptions.indexDbName, p.id.name) .then(() => this.onPrimitivesChanged()) @@ -136,7 +140,7 @@ export class NgvPluginCesiumModelInteract extends LitElement { onPrimitivesChanged(): void { this.models = []; for (let i = 0; i < this.primitiveCollection.length; i++) { - const model = this.primitiveCollection.get(i) as UploadedModel; + const model = this.primitiveCollection.get(i) as INGVCesiumModel; if (model instanceof Model) { this.models.push(model); } @@ -418,7 +422,7 @@ export class NgvPluginCesiumModelInteract extends LitElement { >` : html` `; } diff --git a/src/plugins/cesium/ngv-plugin-cesium-upload.ts b/src/plugins/cesium/ngv-plugin-cesium-upload.ts index d10e53b..528deb1 100644 --- a/src/plugins/cesium/ngv-plugin-cesium-upload.ts +++ b/src/plugins/cesium/ngv-plugin-cesium-upload.ts @@ -1,4 +1,4 @@ -import type {CesiumWidget, Model, PrimitiveCollection} from '@cesium/engine'; +import type {CesiumWidget, PrimitiveCollection} from '@cesium/engine'; import { Cartesian3, Cartographic, @@ -18,28 +18,10 @@ import '../ui/ngv-upload.js'; import {instantiateModel} from './ngv-cesium-factories.js'; import type {FileUploadDetails} from '../ui/ngv-upload.js'; import {storeBlobInIndexedDB, updateModelsInLocalStore} from './localStore.js'; +import type {INGVCesiumModel} from '../../interfaces/cesium/ingv-layers.js'; const cartographicScratch = new Cartographic(); -type GlbJson = { - bufferViews: {byteOffset: number; byteLength: number}[]; - accessors: {bufferView: number}[]; - meshes: { - primitives: { - attributes: { - POSITION: number; - }; - }[]; - }[]; -}; - -export interface UploadedModel extends Model { - id: { - dimensions?: Cartesian3; - name: string; - }; -} - @customElement('ngv-plugin-cesium-upload') export class NgvPluginCesiumUpload extends LitElement { @property({type: Object}) @@ -52,12 +34,11 @@ export class NgvPluginCesiumUpload extends LitElement { indexDbName: string; }; private eventHandler: ScreenSpaceEventHandler | null = null; - private uploadedModel: UploadedModel | undefined; + private uploadedModel: INGVCesiumModel | undefined; async upload(fileDetails: FileUploadDetails): Promise { const response = await fetch(fileDetails.url); const arrayBuffer = await response.arrayBuffer(); - const dimensions = parseGlbModelDimensions(arrayBuffer); const modelMatrix = Matrix4.fromTranslationRotationScale( new TranslationRotationScale( @@ -76,7 +57,7 @@ export class NgvPluginCesiumUpload extends LitElement { modelMatrix, id: { name: fileDetails.name, - dimensions, + dimensions: Cartesian3.ZERO, }, }, }); @@ -109,9 +90,9 @@ export class NgvPluginCesiumUpload extends LitElement { this.viewer.canvas.style.cursor = 'default'; this.eventHandler.destroy(); this.eventHandler = null; - const models: UploadedModel[] = []; + const models: INGVCesiumModel[] = []; for (let i = 0; i < this.primitiveCollection.length; i++) { - models.push(this.primitiveCollection.get(i)); + models.push(this.primitiveCollection.get(i)); } if (this.storeOptions) { updateModelsInLocalStore(this.storeOptions.localStoreKey, models); @@ -149,52 +130,6 @@ export class NgvPluginCesiumUpload extends LitElement { } } -function parseGlbModelDimensions(arrayBuffer: ArrayBuffer): Cartesian3 { - const glb = new Uint8Array(arrayBuffer); - - const jsonLength = new DataView(arrayBuffer, 12, 4).getUint32(0, true); - const jsonChunk = new TextDecoder().decode(glb.subarray(20, 20 + jsonLength)); - const json: GlbJson = JSON.parse(jsonChunk) as GlbJson; - - const bufferView = - json.bufferViews[ - json.accessors[json.meshes[0].primitives[0].attributes.POSITION] - .bufferView - ]; - const byteOffset = bufferView.byteOffset; - const byteLength = bufferView.byteLength; - - const binaryData = new Float32Array( - arrayBuffer, - byteOffset + 20 + jsonLength, - byteLength / Float32Array.BYTES_PER_ELEMENT, - ); - - const min = new Cartesian3( - Number.POSITIVE_INFINITY, - Number.POSITIVE_INFINITY, - Number.POSITIVE_INFINITY, - ); - const max = new Cartesian3( - Number.NEGATIVE_INFINITY, - Number.NEGATIVE_INFINITY, - Number.NEGATIVE_INFINITY, - ); - - for (let i = 0; i < binaryData.length; i += 3) { - const vertex = new Cartesian3( - binaryData[i], - binaryData[i + 2], - binaryData[i + 1], - ); - - Cartesian3.minimumByComponent(min, vertex, min); - Cartesian3.maximumByComponent(max, vertex, max); - } - - return Cartesian3.subtract(max, min, new Cartesian3()); -} - declare global { interface HTMLElementTagNameMap { 'ngv-plugin-cesium-upload': NgvPluginCesiumUpload; From 6d39ad94c49c2b65a0f1262d7b58bbe02a3e26fc Mon Sep 17 00:00:00 2001 From: Guillaume Beraudo Date: Fri, 22 Nov 2024 15:55:49 +0100 Subject: [PATCH 11/15] Move out collections and store options from config --- src/apps/permits/demoPermitConfig.ts | 8 --- src/apps/permits/index.ts | 56 +++++++++++-------- src/interfaces/cesium/ingv-cesium-context.ts | 10 +--- src/plugins/cesium/ngv-cesium-factories.ts | 40 +++++++------ .../cesium/ngv-plugin-cesium-widget.ts | 23 ++++++-- 5 files changed, 71 insertions(+), 66 deletions(-) diff --git a/src/apps/permits/demoPermitConfig.ts b/src/apps/permits/demoPermitConfig.ts index 6febb17..0829e9e 100644 --- a/src/apps/permits/demoPermitConfig.ts +++ b/src/apps/permits/demoPermitConfig.ts @@ -1,5 +1,4 @@ import type {IPermitsConfig} from './ingv-config-permits.js'; -import {PrimitiveCollection} from '@cesium/engine'; export const config: IPermitsConfig = { languages: ['de', 'fr', 'en', 'it'], @@ -33,9 +32,6 @@ export const config: IPermitsConfig = { imageries: ['@geoadmin/pixel-karte-farbe'], // terrain: '@geoadmin/terrain', }, - collections: { - models: new PrimitiveCollection(), - }, camera: { position: [6.628484, 46.5, 100], orientation: { @@ -50,10 +46,6 @@ export const config: IPermitsConfig = { globeOptions: { depthTestAgainstTerrain: true, }, - storeOptions: { - localStoreKey: 'localStoreModels', - indexDbName: 'uploadedModelsStore', - }, }, }, }; diff --git a/src/apps/permits/index.ts b/src/apps/permits/index.ts index a405e64..f925f43 100644 --- a/src/apps/permits/index.ts +++ b/src/apps/permits/index.ts @@ -27,6 +27,13 @@ export class NgvAppPermits extends ABaseApp { new PrimitiveCollection(); private dataSourceCollection: DataSourceCollection; + private storeOptions = { + localStoreKey: 'permits-localStoreModels', + indexDbName: 'permits-uploadedModelsStore', + }; + + private collections: ViewerInitializedDetails['primitiveCollections']; + constructor() { super(() => import('./demoPermitConfig.js')); } @@ -42,35 +49,40 @@ export class NgvAppPermits extends ABaseApp { slot="menu" style="display: flex; flex-direction: column; row-gap: 10px;" > - -
- - + ${this.viewer + ? html` + +
+ + + ` + : ''} + ) => { this.viewer = evt.detail.viewer; this.viewer.scene.primitives.add(this.uploadedModelsCollection); this.dataSourceCollection = evt.detail.dataSourceCollection; + this.collections = evt.detail.primitiveCollections; }} > diff --git a/src/interfaces/cesium/ingv-cesium-context.ts b/src/interfaces/cesium/ingv-cesium-context.ts index 3de5c6d..bf6089c 100644 --- a/src/interfaces/cesium/ingv-cesium-context.ts +++ b/src/interfaces/cesium/ingv-cesium-context.ts @@ -1,4 +1,4 @@ -import type {CesiumWidget, Globe, PrimitiveCollection} from '@cesium/engine'; +import type {CesiumWidget, Globe} from '@cesium/engine'; import type {INGVCatalog} from './ingv-catalog.js'; export interface IngvCesiumContext { @@ -14,10 +14,6 @@ export interface IngvCesiumContext { models?: string[]; imageries: string[]; }; - collections?: { - models?: PrimitiveCollection; - tiles3d?: PrimitiveCollection; - }; /** * These are lists of selected layers. */ @@ -37,8 +33,4 @@ export interface IngvCesiumContext { }; widgetOptions?: ConstructorParameters[1]; globeOptions?: Partial; - storeOptions?: { - localStoreKey: string; - indexDbName: string; - }; } diff --git a/src/plugins/cesium/ngv-cesium-factories.ts b/src/plugins/cesium/ngv-cesium-factories.ts index dce795e..1e1c70b 100644 --- a/src/plugins/cesium/ngv-cesium-factories.ts +++ b/src/plugins/cesium/ngv-cesium-factories.ts @@ -1,4 +1,4 @@ -import type {ImageryProvider} from '@cesium/engine'; +import {type ImageryProvider, PrimitiveCollection} from '@cesium/engine'; import { Ion, Math as CesiumMath, @@ -216,7 +216,14 @@ export async function initCesiumWidget( container: HTMLDivElement, cesiumContext: IngvCesiumContext, modelCallback: (name: string, model: Model) => void, -): Promise<{viewer: CesiumWidget; dataSourceCollection: DataSourceCollection}> { +): Promise<{ + viewer: CesiumWidget; + dataSourceCollection: DataSourceCollection; + primitiveCollections: { + models: PrimitiveCollection; + tiles3d: PrimitiveCollection; + }; +}> { modelCallback = modelCallback || (() => { @@ -266,14 +273,13 @@ export async function initCesiumWidget( Object.assign(viewer.scene.globe, cesiumContext.globeOptions); } - if (cesiumContext.collections) { - if (cesiumContext.collections.models) { - viewer.scene.primitives.add(cesiumContext.collections.models); - } - if (cesiumContext.collections.tiles3d) { - viewer.scene.primitives.add(cesiumContext.collections.tiles3d); - } - } + const primitiveCollections = { + models: new PrimitiveCollection(), + tiles3d: new PrimitiveCollection(), + }; + + viewer.scene.primitives.add(primitiveCollections.models); + viewer.scene.primitives.add(primitiveCollections.tiles3d); const dataSourceCollection = new DataSourceCollection(); const dataSourceDisplay = new DataSourceDisplay({ @@ -310,11 +316,7 @@ export async function initCesiumWidget( stuffToDo.push( instantiate3dTileset(config, cesiumContext.layerOptions[name]).then( (tileset) => { - if (cesiumContext.collections?.tiles3d) { - cesiumContext.collections.tiles3d.add(tileset); - } else { - viewer.scene.primitives.add(tileset); - } + primitiveCollections.tiles3d.add(tileset); }, ), ); @@ -393,11 +395,7 @@ export async function initCesiumWidget( .then( (model) => { modelCallback(path, model); - if (cesiumContext.collections?.models) { - cesiumContext.collections.models.add(model); - } else { - viewer.scene.primitives.add(model); - } + primitiveCollections.models.add(model); }, (e) => { console.error('o', e); @@ -444,5 +442,5 @@ export async function initCesiumWidget( duration: 0, }); - return {viewer, dataSourceCollection}; + return {viewer, dataSourceCollection, primitiveCollections}; } diff --git a/src/plugins/cesium/ngv-plugin-cesium-widget.ts b/src/plugins/cesium/ngv-plugin-cesium-widget.ts index e247503..20ca80d 100644 --- a/src/plugins/cesium/ngv-plugin-cesium-widget.ts +++ b/src/plugins/cesium/ngv-plugin-cesium-widget.ts @@ -5,12 +5,21 @@ import {customElement, property, query} from 'lit/decorators.js'; // @ts-expect-error Vite specific ?inline parameter import style from '@cesium/engine/Source/Widget/CesiumWidget.css?inline'; import type {IngvCesiumContext} from '../../interfaces/cesium/ingv-cesium-context.js'; -import type {CesiumWidget, DataSourceCollection, Model} from '@cesium/engine'; +import type { + CesiumWidget, + DataSourceCollection, + Model, + PrimitiveCollection, +} from '@cesium/engine'; import {initCesiumWidget} from './ngv-cesium-factories.js'; export type ViewerInitializedDetails = { viewer: CesiumWidget; dataSourceCollection: DataSourceCollection; + primitiveCollections: { + models: PrimitiveCollection; + tiles3d: PrimitiveCollection; + }; }; @customElement('ngv-plugin-cesium-widget') @@ -47,11 +56,12 @@ export class NgvPluginCesiumWidget extends LitElement { private element: HTMLDivElement; private async initCesiumViewer(): Promise { - const {viewer, dataSourceCollection} = await initCesiumWidget( - this.element, - this.cesiumContext, - this.modelCallback, - ); + const {viewer, dataSourceCollection, primitiveCollections} = + await initCesiumWidget( + this.element, + this.cesiumContext, + this.modelCallback, + ); this.viewer = viewer; this.dataSourceCollection = dataSourceCollection; this.dispatchEvent( @@ -59,6 +69,7 @@ export class NgvPluginCesiumWidget extends LitElement { detail: { viewer, dataSourceCollection, + primitiveCollections, }, }), ); From b12a5aa8ddd825fe5929f2978473abd4ac4860c5 Mon Sep 17 00:00:00 2001 From: Guillaume Beraudo Date: Fri, 22 Nov 2024 15:56:06 +0100 Subject: [PATCH 12/15] Translate upload defaults --- src/plugins/ui/ngv-upload.ts | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/src/plugins/ui/ngv-upload.ts b/src/plugins/ui/ngv-upload.ts index a4c35ad..903cdb3 100644 --- a/src/plugins/ui/ngv-upload.ts +++ b/src/plugins/ui/ngv-upload.ts @@ -1,6 +1,7 @@ import {customElement, property, query, state} from 'lit/decorators.js'; import type {HTMLTemplateResult} from 'lit'; import {css, html, LitElement} from 'lit'; +import {msg} from '@lit/localize'; export type NgvUploadOptions = { accept?: string; @@ -11,15 +12,6 @@ export type NgvUploadOptions = { uploadBtnText?: string; }; -const defaultOptions: NgvUploadOptions = { - mainBtnText: 'Upload', - uploadBtnText: 'Upload', - urlInput: true, - fileInput: true, - urlPlaceholderText: 'Put file URL here', - accept: '*/*', -}; - export type FileUploadDetails = { url: string; name: string; @@ -94,7 +86,14 @@ export class NgvUpload extends LitElement { } override willUpdate(): void { - let options: NgvUploadOptions = {...defaultOptions}; + let options: NgvUploadOptions = { + mainBtnText: msg('Upload'), + uploadBtnText: msg('Upload'), + urlInput: true, + fileInput: true, + urlPlaceholderText: msg('Put file URL here'), + accept: '*/*', + }; if (this.options) { options = {...options, ...this.options}; } From ccdc6ecfd4ef51ad023a74a848ab7cb3473b3089 Mon Sep 17 00:00:00 2001 From: Guillaume Beraudo Date: Fri, 22 Nov 2024 15:56:53 +0100 Subject: [PATCH 13/15] Only load ngv plugin auth when an auth context is present --- src/structure/ngv-structure-header.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/structure/ngv-structure-header.ts b/src/structure/ngv-structure-header.ts index 66da786..d5029ae 100644 --- a/src/structure/ngv-structure-header.ts +++ b/src/structure/ngv-structure-header.ts @@ -32,10 +32,13 @@ export class NgvStructureHeader extends LitElement { .searchContext=${headerConfig.searchContext} >` : ''} - - + ${this.config.authContext + ? html` + + ` + : ''}
From cf1277824494f4663e5c016557e7aa983aad0d1a Mon Sep 17 00:00:00 2001 From: Guillaume Beraudo Date: Fri, 22 Nov 2024 16:00:34 +0100 Subject: [PATCH 14/15] Support uploading glTF --- src/plugins/cesium/ngv-plugin-cesium-upload.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/cesium/ngv-plugin-cesium-upload.ts b/src/plugins/cesium/ngv-plugin-cesium-upload.ts index 528deb1..6f2386e 100644 --- a/src/plugins/cesium/ngv-plugin-cesium-upload.ts +++ b/src/plugins/cesium/ngv-plugin-cesium-upload.ts @@ -120,7 +120,7 @@ export class NgvPluginCesiumUpload extends LitElement { render(): HTMLTemplateResult { return html`