diff --git a/index.html b/index.html index 9464f6e..cd24058 100644 --- a/index.html +++ b/index.html @@ -26,6 +26,7 @@
Loading...
+
diff --git a/package-lock.json b/package-lock.json index 6729492..87164b1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,14 +8,17 @@ "name": "immersive", "version": "0.0.1", "dependencies": { - "@babylonjs/core": "^6.14.0", - "@babylonjs/gui": "^6.14.0", + "@babylonjs/core": "^6.15.0", + "@babylonjs/gui": "^6.15.0", "@babylonjs/havok": "1.1.1", - "@babylonjs/inspector": "^6.14.0", + "@babylonjs/inspector": "^6.15.0", + "@babylonjs/serializers": "^6.15.0", + "@typed-mxgraph/typed-mxgraph": "^1.0.8", "dexie": "^3.2.4", "dexie-observable": "^4.0.1-beta.13", "earcut": "^2.2.4", "loglevel": "^1.8.1", + "mxgraph": "^4.2.2", "p2p-data-channel": "^1.10.7", "query-string": "^8.1.0", "ring-client-api": "11.7.7", @@ -41,14 +44,14 @@ } }, "node_modules/@babylonjs/core": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/@babylonjs/core/-/core-6.14.0.tgz", - "integrity": "sha512-ciIfWMMtV5jsnqxqTn+v/CS65yji6CXTP2drmvLlzk+k+IZjE8RfkpMqZgZozN/KNkOmIVn2Li7qRMjg4ZUGlw==" + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/@babylonjs/core/-/core-6.15.0.tgz", + "integrity": "sha512-eoeJ3RB6xYC63ZuGGuceF7rrVA1OFIlO3cvBvAxsoBIzxTSLxyhmG4wwktEvCAePxZ7W7CDYNtA061edGWhxUw==" }, "node_modules/@babylonjs/gui": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/@babylonjs/gui/-/gui-6.14.0.tgz", - "integrity": "sha512-rkqPEVDaeza4agyd5xSLSjnWgI1spqcNI/kmNke4gREmweEcZZFxNxjGQd5m/JQMbx7qrj9jtEp8COZ7wNSiWw==", + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/@babylonjs/gui/-/gui-6.15.0.tgz", + "integrity": "sha512-WkR/r8wLbKyzUuXUgcWVaTZO5drxQ8YjkstDRz/ZrhfOTIEFDpidd7onDhaQ9wmekQEpFHcKJejJ1/MLXYLwAg==", "peerDependencies": { "@babylonjs/core": "^6.0.0" } @@ -74,9 +77,9 @@ } }, "node_modules/@babylonjs/inspector": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/@babylonjs/inspector/-/inspector-6.14.0.tgz", - "integrity": "sha512-8wntNbVb9Yhj0q8Ll4kFGDDJPHk7IeosqVwXTPToTfDYC67pwkVQFXFViEXr1R0HrG0l1FmZzPVLY6szbFSwrw==", + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/@babylonjs/inspector/-/inspector-6.15.0.tgz", + "integrity": "sha512-wN1jVC+gxQDcLL/dyuIMbedPZpxTHNW9gto82jvgyjXLIUKOWiQUVJS7moaLhTM27BMWBrHMfG+K4RnsnKEW9A==", "dependencies": { "@fortawesome/fontawesome-svg-core": "^6.1.0", "@fortawesome/free-regular-svg-icons": "^6.0.0", @@ -113,10 +116,9 @@ } }, "node_modules/@babylonjs/serializers": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/@babylonjs/serializers/-/serializers-6.8.0.tgz", - "integrity": "sha512-/deOpTX3Lnll3PM9Um7l+e33mVPe65BgAOlgskrcH6XmT5BaltedYwQktTyblrZs8nQEs90elPpdOb0bsoY4HA==", - "peer": true, + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/@babylonjs/serializers/-/serializers-6.15.0.tgz", + "integrity": "sha512-qUKqMe18wlEQpyh5cAa/u/B4xMZ2tKh5K1CurnN9TXaM6i8wImRL4mmXqlwy/Tq18zUGmfmPyU1p1qPuD1ytcg==", "peerDependencies": { "@babylonjs/core": "^6.0.0", "babylonjs-gltf2interface": "^6.0.0" @@ -901,6 +903,11 @@ "node": ">=10" } }, + "node_modules/@typed-mxgraph/typed-mxgraph": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@typed-mxgraph/typed-mxgraph/-/typed-mxgraph-1.0.8.tgz", + "integrity": "sha512-rzTbmD/XofRq0YZMY/BU9cjbCTw9q8rpIvWRhQO0DcgCx3+rpHTsVOk3pfuhcnUigUYNFkljmDkRuVjbl7zZoQ==" + }, "node_modules/@types/cacheable-request": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz", @@ -2826,6 +2833,12 @@ "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==" }, + "node_modules/mxgraph": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/mxgraph/-/mxgraph-4.2.2.tgz", + "integrity": "sha512-FrJc5AxzXSqiQNF+8CyJk6VxuKO4UVPgw32FZuFZ3X9W+JqOAQBTokZhh0ZkEqGpEOyp3z778ssmBTvdrTAdqw==", + "deprecated": "Package no longer supported. Use at your own risk" + }, "node_modules/nano-time": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/nano-time/-/nano-time-1.0.0.tgz", diff --git a/package.json b/package.json index 37cc278..b7270e7 100644 --- a/package.json +++ b/package.json @@ -11,15 +11,18 @@ "havok": "cp ./node_modules/@babylonjs/havok/lib/esm/HavokPhysics.wasm ./node_modules/.vite/deps" }, "dependencies": { - "@babylonjs/core": "^6.14.0", - "@babylonjs/gui": "^6.14.0", + "@babylonjs/core": "^6.15.0", + "@babylonjs/gui": "^6.15.0", "@babylonjs/havok": "1.1.1", - "@babylonjs/inspector": "^6.14.0", + "@babylonjs/inspector": "^6.15.0", + "@babylonjs/serializers": "^6.15.0", "ring-client-api": "11.7.7", "dexie": "^3.2.4", "dexie-observable": "^4.0.1-beta.13", "query-string": "^8.1.0", "loglevel": "^1.8.1", + "mxgraph": "^4.2.2", + "@typed-mxgraph/typed-mxgraph": "^1.0.8", "round": "^2.0.1", "earcut": "^2.2.4", "uuid": "^9.0.0", diff --git a/src/app.ts b/src/app.ts index d7fa613..4beb10b 100644 --- a/src/app.ts +++ b/src/app.ts @@ -17,18 +17,22 @@ import {PeerjsNetworkConnection} from "./integration/peerjsNetworkConnection"; import {InputTextView} from "./information/inputTextView"; import {GamepadManager} from "./controllers/gamepadManager"; import {CustomEnvironment} from "./util/customEnvironment"; +import {DrawioManager} from "./integration/drawioManager"; export class App { //preTasks = [havokModule]; constructor() { const config = AppConfig.config; const logger = log.getLogger('App'); - log.setLevel('info'); - log.getLogger('App').setLevel('info'); - log.getLogger('IndexdbPersistenceManager').setLevel('info'); - log.getLogger('DiagramManager').setLevel('info'); + log.disableAll(); + log.setDefaultLevel('info'); - log.getLogger('DiagramConnection').setLevel('debug'); + log.getLogger('App').setLevel('info'); + //log.getLogger('IndexdbPersistenceManager').setLevel('info'); + //log.getLogger('DiagramManager').setLevel('info'); + + //log.getLogger('DiagramConnection').setLevel('debug'); + log.getLogger('DrawioManager').setLevel('debug'); const canvas = document.createElement("canvas"); canvas.style.width = "100%"; canvas.style.height = "100%"; @@ -41,6 +45,7 @@ export class App { } async initialize(canvas) { + const logger = log.getLogger('App'); const engine = new Engine(canvas, true); const scene = new Scene(engine); @@ -69,6 +74,7 @@ export class App { camera.radius = 0; camera.attachControl(canvas, true); new HemisphericLight("light1", new Vector3(1, 1, 0), scene); + environment.groundMeshObservable.add(async (ground) => { const xr = await WebXRDefaultExperience.CreateAsync(scene, { floorMeshes: [ground], @@ -99,6 +105,7 @@ export class App { const diagramManager = new DiagramManager(scene, xr.baseExperience); const rig = new Rigplatform(scene, xr, diagramManager); const toolbox = new Toolbox(scene, xr.baseExperience, diagramManager); + const dioManager = new DrawioManager(scene, diagramManager); import ('./integration/indexdbPersistenceManager').then((module) => { const persistenceManager = new module.IndexdbPersistenceManager("diagram"); diagramManager.setPersistenceManager(persistenceManager); diff --git a/src/controllers/rigplatform.ts b/src/controllers/rigplatform.ts index 3c8894c..c8827d0 100644 --- a/src/controllers/rigplatform.ts +++ b/src/controllers/rigplatform.ts @@ -20,6 +20,7 @@ import {EditMenu} from "../menus/editMenu"; import {Controllers} from "./controllers"; import log from "loglevel"; import {DiagramManager} from "../diagram/diagramManager"; +import {AppConfig} from "../util/appConfig"; export class Rigplatform { @@ -35,6 +36,7 @@ export class Rigplatform { private camera: Camera; private turning: boolean = false; private velocity: Vector3 = Vector3.Zero(); + private turnVelocity: number = 0; private logger = log.getLogger('Rigplatform'); private readonly diagramManager: DiagramManager; @@ -92,12 +94,13 @@ export class Rigplatform { this.velocity.y = (val * this.velocityArray[this.velocityIndex])*-1; } public turn(val: number) { - const snap = true; - if (snap) { + const snap = AppConfig.config.currentTurnSnap.value; + + if (snap > 0) { if (!this.turning) { if (Math.abs(val) > .1) { this.turning = true; - this.yRotation += Angle.FromDegrees(Math.sign(val) * 22.5).radians(); + this.yRotation += Angle.FromDegrees(Math.sign(val) * snap).radians(); } } else { if (Math.abs(val) < .1) { @@ -106,9 +109,9 @@ export class Rigplatform { } } else { if (Math.abs(val) > .1) { - this.body.setAngularVelocity(Vector3.Up().scale(val)); + this.turnVelocity = val; } else { - this.body.setAngularVelocity(Vector3.Zero()); + this.turnVelocity = 0; } } } @@ -169,13 +172,18 @@ export class Rigplatform { } private fixRotation() { this.scene.registerBeforeRender(() => { - const q = this.rigMesh.rotationQuaternion; - this.body.setAngularVelocity(Vector3.Zero()); - if (q) { - const e = q.toEulerAngles(); - e.y += this.yRotation; - q.copyFrom(Quaternion.FromEulerAngles(0, e.y, 0)); + if (AppConfig?.config?.currentTurnSnap?.value > 0) { + const q = this.rigMesh.rotationQuaternion; + this.body.setAngularVelocity(Vector3.Zero()); + if (q) { + const e = q.toEulerAngles(); + e.y += this.yRotation; + q.copyFrom(Quaternion.FromEulerAngles(0, e.y, 0)); + } + } else { + this.body.setAngularVelocity(Vector3.Up().scale(this.turnVelocity)); } + }); } } \ No newline at end of file diff --git a/src/diagram/diagramManager.ts b/src/diagram/diagramManager.ts index 8890c6d..90d53d8 100644 --- a/src/diagram/diagramManager.ts +++ b/src/diagram/diagramManager.ts @@ -136,6 +136,20 @@ export class DiagramManager { if (entity) { mesh = this.scene.getMeshById(entity.id); } + if (!mesh) { + const toolMesh = this.scene.getMeshById("tool-" + event.entity.template + "-" + event.entity.color); + if (!toolMesh) { + log.debug('no mesh found for ' + event.entity.template + "-" + event.entity.color, 'adding it'); + this.onDiagramEventObservable.notifyObservers({ + type: DiagramEventType.CHANGECOLOR, + entity: event.entity + }); + } + + mesh = MeshConverter.fromDiagramEntity(event.entity, this.scene); + mesh.actionManager = this.actionManager; + DiagramShapePhysics.applyPhysics(mesh, this.scene, PhysicsMotionType.DYNAMIC); + } switch (event.type) { case DiagramEventType.CLEAR: break; diff --git a/src/integration/drawioManager.ts b/src/integration/drawioManager.ts new file mode 100644 index 0000000..770de6e --- /dev/null +++ b/src/integration/drawioManager.ts @@ -0,0 +1,235 @@ +import log from "loglevel"; +import { + AbstractMesh, + Axis, + Color3, + DynamicTexture, + MeshBuilder, + Scene, + Space, + StandardMaterial, + TransformNode, + Vector3 +} from "@babylonjs/core"; +import {DiagramManager} from "../diagram/diagramManager"; +import {DiagramEventType} from "../diagram/diagramEntity"; + +export class DrawioManager { + private diagramManager: DiagramManager; + private readonly zdepth: Map = new Map(); + private scene: Scene; + private readonly logger = log.getLogger('DrawioManager'); + private minY = 0; + private minX = 0; + private maxX = 0; + private maxY = 0; + + constructor(scene: Scene, diagramManager: DiagramManager) { + this.scene = scene; + this.diagramManager = diagramManager; + this.getGraph(); + } + + public static updateTextNode(mesh: AbstractMesh, text: string): AbstractMesh { + //Set font + const height = 0.1; + const font_size = 24; + const font = "bold " + font_size + "px Arial"; + //Set height for dynamic texture + const DTHeight = 1.5 * font_size; //or set as wished + //Calc Ratio + const ratio = height / DTHeight; + + //Use a temporary dynamic texture to calculate the length of the text on the dynamic texture canvas + const temp = new DynamicTexture("DynamicTexture", 32, mesh.getScene()); + const tmpctx = temp.getContext(); + tmpctx.font = font; + const DTWidth = tmpctx.measureText(text).width + 8; + + //Calculate width the plane has to be + const planeWidth = DTWidth * ratio; + + //Create dynamic texture and write the text + const dynamicTexture = new DynamicTexture("DynamicTexture", { + width: DTWidth, + height: DTHeight + }, mesh.getScene(), false); + const mat = new StandardMaterial("mat", mesh.getScene()); + mat.diffuseTexture = dynamicTexture; + dynamicTexture.drawText(text, null, null, font, "#000000", "#ffffff", true); + + //Create plane and set dynamic texture as material + const plane = MeshBuilder.CreatePlane("text", {width: planeWidth, height: height}, mesh.getScene()); + plane.material = mat; + //plane.billboardMode = Mesh.BILLBOARDMODE_ALL; + plane.parent = mesh; + plane.position.z = .51; + plane.scaling.y = 1 / mesh.scaling.y; + plane.scaling.x = 1 / mesh.scaling.x; + plane.scaling.z = 1 / mesh.scaling.z; + plane.rotate(Axis.Z, Math.PI, Space.LOCAL); + plane.rotate(Axis.Y, Math.PI, Space.LOCAL); + //plane.rotate(Axis.X, Math.PI, Space.LOCAL); + + return plane; + } + + private async getGraph() { + this.logger.debug("starting to get graph"); + const entities: Array<{ text: string, id: string, geometry: { x: number, y: number, width: number, height: number } }> + = new Array<{ text: string; id: string, geometry: { x: number; y: number; width: number; height: number } }>(); + + const graph = await fetch('/arch_demo.xml'); + this.logger.debug('got graph'); + const graphXml = await graph.text(); + const doc = new DOMParser().parseFromString(graphXml, 'text/html'); + //this.logger.debug(doc); + const firstDiagram = doc.querySelectorAll('diagram')[0]; + const mxDiagram = firstDiagram.querySelector('mxGraphModel'); + const width = mxDiagram.getAttribute('pageWidth'); + const height = mxDiagram.getAttribute('pageHeight'); + this.logger.debug('begin parse'); + mxDiagram.querySelectorAll('mxCell').forEach((cell) => { + + const value = cell.getAttribute('value'); + if (!value) { + //this.logger.warn('no value for :' , cell); + } else { + const ent = new DOMParser().parseFromString(value, 'text/html'); + const errorNode = ent.querySelector("parsererror"); + this.logger.debug(value); + if (errorNode) { + //this.logger.debug(value); + } else { + const text = this.getText(ent, ''); + const id = cell.getAttribute('id'); + const parent = cell.getAttribute('parent'); + if (this.zdepth.has(parent)) { + this.zdepth.set(id, this.zdepth.get(parent) + .2); + } else { + this.zdepth.set(cell.getAttribute('id'), 0); + } + const geo = cell.querySelector('mxGeometry'); + const geometry = { + x: geo.getAttribute('x'), + y: geo.getAttribute('y'), + width: geo.getAttribute('width'), + height: geo.getAttribute('height'), + } + entities.push({text: text, id: id, geometry: this.fixMinMax(geometry)}); + if (text) { + this.logger.debug('Text' + text); + this.logger.debug('Geometry' + JSON.stringify(geometry)); + } + } + } + }); + this.logger.debug('done parsing'); + + this.logger.debug('MinX' + this.minX); + this.logger.debug('MinY' + this.minY); + this.logger.debug('MaxX' + this.maxX); + this.logger.debug('MaxY' + this.maxY); + const diagramWidth = this.maxX - this.minX; + const diagramHeight = this.maxY - this.minY; + let scale = 1; + if (diagramHeight > diagramWidth) { + scale = 20 / diagramHeight; + } else { + scale = 20 / diagramWidth; + } + const anchor = new TransformNode('anchor', this.scene); + + if (entities.length > 0) { + //const basebox = MeshBuilder.CreateBox("box", { + // height: 1, + // width: 1, depth: 1 + //}, this.scene); + + //const material: StandardMaterial = new StandardMaterial("mat", this.scene); + //material.diffuseColor = new Color3(0.5, 0.5, 0.5); + //material.alpha = .4; + //basebox.material = material; + entities.forEach((entity) => { + /* const box = new InstancedMesh("box", basebox); + box.scaling.z = .1; + box.scaling.y = entity.geometry.height * scale; + box.scaling.x = entity.geometry.width * scale; + box.position.x = (entity.geometry.x - this.minX) * scale + (entity.geometry.width * scale / 2); + box.position.y = (entity.geometry.y - this.minY) * scale + (entity.geometry.height * scale / 2); + box.position.z = 2 + this.zdepth.get(entity.id); */ + this.diagramManager.onDiagramEventObservable.notifyObservers( + { + type: DiagramEventType.ADD, + entity: { + text: entity.text, + id: entity.id, + position: new Vector3((entity.geometry.x - this.minX) * scale + (entity.geometry.width * scale / 2), + (entity.geometry.y - this.minY) * scale + (entity.geometry.height * scale / 2), + 2 + this.zdepth.get(entity.id)), + scale: new Vector3(entity.geometry.width * scale, entity.geometry.height * scale, .1), + color: Color3.Blue().toHexString(), + template: '#box-template' + } + } + ); + + //box.metadata = {text: entity.text}; + //box.setParent(anchor); + //DrawioManager.updateTextNode(box, entity.text); + }); + anchor.position.y = 20; + anchor.rotation.x = Math.PI; + + } + + this.logger.debug('Scale' + scale); + + + } + + private fixMinMax(geometry: { x: string; y: string; width: string; height: string; }): + { x: number, y: number, width: number, height: number } { + let x = 0; + if (geometry.x) { + x = parseFloat(geometry.x); + if (x < this.minX) { + this.minX = x; + } + if (x > this.maxX) { + this.maxX = x; + } + } + let y = 0; + if (geometry.y) { + y = parseFloat(geometry.y); + if (y < this.minY) { + this.minY = y; + } + if (y > this.maxY) { + this.maxY = y; + } + } + return ({x: x, y: y, width: parseFloat(geometry.width), height: parseFloat(geometry.height)}); + } + + private getText(obj: Node, text: string): string { + if (obj.nodeType == Node.TEXT_NODE) { + if (obj.textContent) { + return obj.textContent.trim(); + } else { + return ''; + } + } else { + if (obj.childNodes) { + let t = ''; + obj.childNodes.forEach((child) => { + t += ' ' + this.getText(child, ''); + }); + return text.trim() + ' ' + t.trim(); + } else { + return ''; + } + } + } +} diff --git a/src/menus/configMenu.ts b/src/menus/configMenu.ts index 15133f0..e014730 100644 --- a/src/menus/configMenu.ts +++ b/src/menus/configMenu.ts @@ -30,22 +30,24 @@ export class ConfigMenu { } DiaSounds.instance.enter.play(); const width = .25; - const height = .55; + const height = .75; const res = 256; const heightPixels = Math.round((height / width) * res); this.configPlane = MeshBuilder .CreatePlane("gridSizePlane", { width: .25, - height: .5 + height: .75 }, this.scene); const configTexture = AdvancedDynamicTexture.CreateForMesh(this.configPlane, res, heightPixels); configTexture.background = "white"; const selectionPanel = new SelectionPanel("selectionPanel"); configTexture.addControl(selectionPanel) this.buildGridSizeControl(selectionPanel); - this.buildRotationSnapControl(selectionPanel); this.buildCreateScaleControl(selectionPanel); + this.buildRotationSnapControl(selectionPanel); + this.buildTurnSnapControl(selectionPanel); + CameraHelper.setMenuPosition(this.configPlane, this.scene); } @@ -85,11 +87,26 @@ export class ConfigMenu { return radio; } + private buildTurnSnapControl(selectionPanel: SelectionPanel): RadioGroup { + const radio = new RadioGroup("Turn Snap"); + selectionPanel.addGroup(radio); + for (const [index, snap] of AppConfig.config.turnSnaps().entries()) { + const selected = AppConfig.config.currentTurnSnapIndex == index; + radio.addRadio(snap.label, this.turnVal, selected); + } + return radio; + } + private rotateVal(value) { AppConfig.config.currentRotateSnapIndex = value; log.debug("configMenu", "rotate Snap", value); } + private turnVal(value) { + AppConfig.config.currentTurnSnapIndex = value; + log.debug("configMenu", "turn Snap", value); + } + private gridVal(value) { AppConfig.config.currentGridSnapIndex = value; log.debug("configMenu", "grid Snap", value); diff --git a/src/menus/editMenu.ts b/src/menus/editMenu.ts index 4794cdb..7152614 100644 --- a/src/menus/editMenu.ts +++ b/src/menus/editMenu.ts @@ -18,6 +18,7 @@ import {DiaSounds} from "../util/diaSounds"; import {CameraHelper} from "../util/cameraHelper"; import {TextLabel} from "../diagram/textLabel"; import {DiagramConnection} from "../diagram/diagramConnection"; +import {GLTF2Export} from "@babylonjs/serializers"; export class EditMenu { private state: EditMenuState = EditMenuState.NONE; @@ -44,14 +45,17 @@ export class EditMenu { this.scene.onPointerObservable.add((pointerInfo) => { switch (pointerInfo.type) { case PointerEventTypes.POINTERPICK: - if (pointerInfo.pickInfo?.pickedMesh?.metadata?.template && - pointerInfo.pickInfo?.pickedMesh?.parent?.parent?.id != "toolbox") { + const pickedMesh = pointerInfo.pickInfo?.pickedMesh; + if (pickedMesh.metadata?.template && + pickedMesh?.parent?.parent?.id != "toolbox") { this.diagramEntityPicked(pointerInfo).then(() => { this.logger.debug("handled"); }).catch((e) => { this.logger.error(e); }); break; + } else { + } } }); @@ -72,6 +76,7 @@ export class EditMenu { panel.addControl(this.makeButton("Add Label", "label")); panel.addControl(this.makeButton("Copy", "copy")); panel.addControl(this.makeButton("Connect", "connect")); + panel.addControl(this.makeButton("Export", "export")); //panel.addControl(this.makeButton("Add Ring Cameras", "addRingCameras")); this.manager.controlScaling = .5; @@ -211,9 +216,23 @@ export class EditMenu { case "connect": this.state = EditMenuState.CONNECTING; break; + case "export": + GLTF2Export.GLTFAsync(this.scene, 'diagram.gltf', { + shouldExportNode: function (node) { + if (node?.metadata?.template) { + return true; + } else { + return false; + } + + } + }).then((gltf) => { + gltf.downloadFiles(); + }); default: this.logger.error("Unknown button"); return; + } this.manager.dispose(); this.manager = null; diff --git a/src/util/appConfig.ts b/src/util/appConfig.ts index 88495c9..35a0ada 100644 --- a/src/util/appConfig.ts +++ b/src/util/appConfig.ts @@ -13,6 +13,7 @@ export type SnapValue = { export class AppConfig { private readonly logger = log.getLogger('AppConfig'); private gridSnap = 1; + private _turnSnap = 0; private rotateSnap = 0; private createSnap = 0; _physicsEnabled = false; @@ -35,6 +36,11 @@ export class AppConfig { {value: 22.5, label: "22.5 Degrees"}, {value: 45, label: "45 Degrees"}, {value: 90, label: "90 Degrees"}]; + private turnSnapArray: SnapValue[] = + [{value: 0, label: "Off"}, + {value: 22.5, label: "22.5 Degrees"}, + {value: 45, label: "45 Degrees"}, + {value: 90, label: "90 Degrees"}]; public get currentGridSnap(): SnapValue { return this.gridSnapArray[this.gridSnap]; @@ -66,10 +72,19 @@ export class AppConfig { return this.createSnapArray[this.createSnap]; } + public get currentTurnSnap(): SnapValue { + return this.turnSnapArray[this._turnSnap]; + } + public get currentGridSnapIndex(): number { return this.gridSnap; } + public set currentTurnSnapIndex(val: number) { + this._turnSnap = val; + this.save(); + } + public set currentGridSnapIndex(val: number) { this.gridSnap = val; this.save(); @@ -110,6 +125,10 @@ export class AppConfig { return this.gridSnapArray; } + public turnSnaps(): SnapValue[] { + return this.turnSnapArray; + } + public createSnaps(): SnapValue[] { return this.createSnapArray; } @@ -153,6 +172,7 @@ export class AppConfig { gridSnap: this.currentGridSnap.value, rotateSnap: this.currentRotateSnap.value, createSnap: this.currentCreateSnap.value, + turnSnap: this.currentTurnSnap.value, physicsEnabled: this._physicsEnabled }); } @@ -167,7 +187,10 @@ export class AppConfig { config.gridSnap != this.currentGridSnap.value || config.rotateSnap != this.currentRotateSnap.value) { this.logger.debug("Config changed", config); - + this._turnSnap = this.turnSnapArray.findIndex((snap) => snap.value == config.turnSnap); + if (!this._turnSnap || this._turnSnap == -1) { + this._turnSnap = 0; + } this.rotateSnap = this.rotateSnapArray.findIndex((snap) => snap.value == config.rotateSnap); this.createSnap = this.createSnapArray.findIndex((snap) => snap.value == config.createSnap); const gridSnap = this.gridSnapArray.findIndex((snap) => snap.value == config.gridSnap); diff --git a/src/util/appConfigType.ts b/src/util/appConfigType.ts index 838d1f2..99d24ef 100644 --- a/src/util/appConfigType.ts +++ b/src/util/appConfigType.ts @@ -3,5 +3,6 @@ export type AppConfigType = { gridSnap: number, rotateSnap: number, createSnap: number, + turnSnap: number, physicsEnabled: boolean } \ No newline at end of file