diff --git a/package-lock.json b/package-lock.json index 2183154..be3cc87 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,11 +19,13 @@ "@cloudflare/workers-types": "^4.20230821.0", "@netlify/functions": "^1.6.0", "@typed-mxgraph/typed-mxgraph": "^1.0.8", + "@types/file-saver": "^2.0.6", "@types/node": "^18.14.0", "dexie": "^3.2.4", "dexie-observable": "^4.0.1-beta.13", "earcut": "^2.2.4", "events": "^3.3.0", + "file-saver": "^2.0.5", "hls.js": "^1.1.4", "loglevel": "^1.8.1", "mxgraph": "^4.2.2", @@ -974,6 +976,11 @@ "resolved": "https://registry.npmjs.org/@types/emscripten/-/emscripten-1.39.6.tgz", "integrity": "sha512-H90aoynNhhkQP6DRweEjJp5vfUVdIj7tdPLsu7pq89vODD/lcugKfZOsfgwpvM6XUewEp2N5dCg1Uf3Qe55Dcg==" }, + "node_modules/@types/file-saver": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@types/file-saver/-/file-saver-2.0.6.tgz", + "integrity": "sha512-Mw671DVqoMHbjw0w4v2iiOro01dlT/WhWp5uwecBa0Wg8c+bcZOjgF1ndBnlaxhtvFCgTRBtsGivSVhrK/vnag==" + }, "node_modules/@types/http-cache-semantics": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz", @@ -2117,6 +2124,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/file-saver": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.5.tgz", + "integrity": "sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==" + }, "node_modules/fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", diff --git a/package.json b/package.json index 3c745ce..32d7d08 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,8 @@ "dexie": "^3.2.4", "dexie-observable": "^4.0.1-beta.13", "earcut": "^2.2.4", + "file-saver": "^2.0.5", + "@types/file-saver": "^2.0.6", "hls.js": "^1.1.4", "loglevel": "^1.8.1", "mxgraph": "^4.2.2", diff --git a/src/app.ts b/src/app.ts index 4cec016..0a73c5d 100644 --- a/src/app.ts +++ b/src/app.ts @@ -23,6 +23,7 @@ import {DiagramExporter} from "./util/diagramExporter"; import {Spinner} from "./util/spinner"; import {WebController} from "./controllers/webController"; import {PouchdbPersistenceManager} from "./integration/pouchdbPersistenceManager"; +import {addSceneInspector} from "./util/functions/sceneInspctor"; export class App { @@ -78,6 +79,7 @@ export class App { const diagramManager = new DiagramManager(scene, controllers, toolbox, config); const db = new PouchdbPersistenceManager("diagram"); + //diagramManager.setPersistenceManager(db); db.configObserver.add((newConfig) => { config.onConfigChangedObservable.notifyObservers(newConfig, 1); @@ -253,31 +255,13 @@ export class App { }); */ - window.addEventListener("keydown", (ev) => { - if (ev.key == "z") { - //voiceManager.startRecording(); - } - if (ev.key == "x") { - //voiceManager.stopRecording(); - } - if (ev.shiftKey && ev.ctrlKey && ev.altKey && ev.keyCode === 73) { - import("@babylonjs/core/Debug/debugLayer").then(() => { - import("@babylonjs/inspector").then(() => { - if (scene.debugLayer.isVisible()) { - scene.debugLayer.hide(); - } else { - scene.debugLayer.show(); - } - }); - }); - } - }); + addSceneInspector(scene); const exportLink = document.querySelector('#downloadLink'); if (exportLink) { exportLink.addEventListener('click', (ev) => { ev.preventDefault(); const exporter = new DiagramExporter(scene); - exporter.export(); + exporter.exportgltf(); }); } diff --git a/src/controllers/left.ts b/src/controllers/left.ts index 37a2460..5840373 100644 --- a/src/controllers/left.ts +++ b/src/controllers/left.ts @@ -1,9 +1,18 @@ -import {Scene, Vector3, WebXRControllerComponent, WebXRDefaultExperience, WebXRInputSource} from "@babylonjs/core"; +import { + Scene, + TransformNode, + Vector2, + Vector3, + WebXRControllerComponent, + WebXRDefaultExperience, + WebXRInputSource +} from "@babylonjs/core"; import {Base} from "./base"; import {ControllerEventType, Controllers} from "./controllers"; import log from "loglevel"; import {ConfigMenu} from "../menus/configMenu"; import {DiagramManager} from "../diagram/diagramManager"; +import {Button} from "../objects/button"; export class Left extends Base { @@ -25,8 +34,22 @@ export class Left extends Base { this.moveMovable(value); } }); + if (init.components['x-button']) { + const transform = new TransformNode('x-button', scene); + transform.parent = controller.grip; + transform.rotation.x = Math.PI / 2; + transform.scaling = new Vector3(.2, .2, .2); + const xbutton = new Button(transform, 'X', 'toggle toolbox menu', new Vector2(-.5, -.1)); + const ybutton = new Button(transform, 'Y', 'toggle settings menu', new Vector2(-.4, .1)); + + } this.initXButton(init.components['x-button']); this.initYButton(init.components['y-button']); + const buttonhome = new TransformNode('buttons', scene) + + //const xbutton = new Button(buttonhome, 'X', 'open edit menu', new Vector2(0,0)); + //const ybutton = new Button(buttonhome, 'Y', 'open edit menu', new Vector2(.4,0)); + this.initTrigger(init.components['xr-standard-trigger']); init.components['xr-standard-thumbstick'].onButtonStateChangedObservable.add((value) => { if (value.pressed) { @@ -58,6 +81,7 @@ export class Left extends Base { private initXButton(xbutton: WebXRControllerComponent) { if (xbutton) { + xbutton.onButtonStateChangedObservable.add((button) => { if (button.pressed) { this.controllers.controllerObserver.notifyObservers({ diff --git a/src/controllers/right.ts b/src/controllers/right.ts index 30d7414..0e51170 100644 --- a/src/controllers/right.ts +++ b/src/controllers/right.ts @@ -1,9 +1,18 @@ import {Base} from "./base"; -import {Scene, Vector3, WebXRControllerComponent, WebXRDefaultExperience, WebXRInputSource} from "@babylonjs/core"; +import { + Scene, + TransformNode, + Vector2, + Vector3, + WebXRControllerComponent, + WebXRDefaultExperience, + WebXRInputSource +} from "@babylonjs/core"; import {ControllerEventType, Controllers} from "./controllers"; import log from "loglevel"; import {DiagramManager} from "../diagram/diagramManager"; import {DiagramListingMenu} from "../menus/diagramListingMenu"; +import {Button} from "../objects/button"; export class Right extends Base { private listingMenu: DiagramListingMenu; @@ -36,6 +45,15 @@ export class Right extends Base { this.listingMenu = new DiagramListingMenu(this.scene, xr, this.controllers); this.controller.onMotionControllerInitObservable.add((init) => { this.initTrigger(init.components['xr-standard-trigger']); + if (init.components['a-button']) { + const transform = new TransformNode('a-button', scene); + transform.parent = controller.grip; + transform.rotation.x = Math.PI / 2; + transform.scaling = new Vector3(.2, .2, .2); + const abutton = new Button(transform, 'A', 'toggle edit menu', new Vector2(.5, -.1)); + const bbutton = new Button(transform, 'B', 'toggle diagram selector', new Vector2(.4, .1)); + + } this.initBButton(init.components['b-button']); this.initAButton(init.components['a-button']); this.initThumbstick(init.components['xr-standard-thumbstick']); diff --git a/src/integration/pouchdbPersistenceManager.ts b/src/integration/pouchdbPersistenceManager.ts index 1ab7122..632342f 100644 --- a/src/integration/pouchdbPersistenceManager.ts +++ b/src/integration/pouchdbPersistenceManager.ts @@ -21,7 +21,6 @@ export class PouchdbPersistenceManager implements IPersistenceManager { console.log(name); this.config = new PouchDB("config"); this.diagramListings = new PouchDB("diagramListings"); - } private _currentDiagramId: string; @@ -38,7 +37,6 @@ export class PouchdbPersistenceManager implements IPersistenceManager { this.diagramListings.put({_id: value, name: "New Diagram"}); } this.db = new PouchDB(value); - this.db.sync(this.remote, {live: true}); } @@ -108,11 +106,11 @@ export class PouchdbPersistenceManager implements IPersistenceManager { } public async getNewRelicData(): Promise { - + return []; } public async setNewRelicData(data: any[]): Promise { - + return data; } public async setConfig(config: any, initial: boolean = false): Promise { @@ -175,7 +173,6 @@ export class PouchdbPersistenceManager implements IPersistenceManager { } } - syncDoc = function (info) { console.log(info); if (info.direction == 'pull') { diff --git a/src/menus/cameraMenu.ts b/src/menus/cameraMenu.ts new file mode 100644 index 0000000..1ca5798 --- /dev/null +++ b/src/menus/cameraMenu.ts @@ -0,0 +1,80 @@ +import {Color3, DynamicTexture, Mesh, MeshBuilder, Scene, StandardMaterial, Vector3} from "@babylonjs/core"; + +export class CameraMenu { + private scene: Scene; + private xr; + private controllers; + + constructor(scene, xr, controllers) { + this.scene = scene; + this.xr = xr; + this.controllers = controllers; + this.buildMenu(1, new Vector3(0, 1, 0)); + //this.buildMenu(4, new Vector3(0,2,0)); + //this.buildMenu(5, new Vector3(1,2,0)); + //this.buildMenu(6, new Vector3(1,1,0)); + + } + + private buildMenu(camnum: number, position: Vector3) { + const camerasphere = MeshBuilder.CreateSphere("camerasphere", { + diameter: 10, + slice: .5, + sideOrientation: Mesh.DOUBLESIDE + }, this.scene); + //const camerasphere = MeshBuilder.CreatePlane("camerasphere", {width: 1.6, height: .6, sideOrientation: Mesh.DOUBLESIDE }, this.scene); + camerasphere.position = position; + const material = new StandardMaterial("cameramaterial", this.scene); + material.emissiveColor = new Color3(1, 1, 1); + //camerasphere.rotation.z = Math.PI; + /*const texture = new VideoTexture("video", + 'https://local.immersiveidea.com/', + this.scene); +*/ + + //const texture = new DynamicTexture("dynamic texture", {width: 512, height: 256}, this.scene, false); + const texture = new DynamicTexture('texture', {width: 1600, height: 1600}, this.scene); + //const img = document.createElement('img'); + //document.body.append(img); + //img.width=2592; + //img.height=1944; + + material.diffuseTexture = texture; + const img = new Image(); + img.src = 'https://cameras.immersiveidea.com/mjpg/video.mjpg?camera=' + camnum + '×tamp=1698497537140'; + const ctx = texture.getContext(); + img.onload = () => { + ctx.drawImage(img, 250, 0, 2112, 1940, 0, 0, 1600, 1600); + texture.update(); + window.setInterval((texture, img, ctx) => { + //const start = new Date(); + ctx.drawImage(img, 250, 0, 2112, 1940, 0, 0, 1600, 1600); + texture.update(); + //console.log(new Date() - start); + }, 60, texture, img, ctx); + } + //https://cameras.immersiveidea.com/mjpg/video.mjpg?camera=1×tamp=1698497537140'); + + //texture.getContext().drawImage(img, 0,0); + + + texture.onLoadObservable.add(() => { + console.log('texture loaded'); + }); + this.scene.onAfterRenderObservable.add(() => { + // texture._rebuild(); + }, 1, true, this); + camerasphere.material = material; + /*texture.video.play().then(() => { + console.log('video playing'); + }).catch((err) => { + console.log(err); + console.log("here"); + }); + */ + console.log('video loaded'); + //Material.diffuseColor = new Color3(0, 0, 0); + + //https://cameras.immersiveidea.com/mjpg/video.mjpg?camera=1&resolution=2592x1944&compression=30&mirror=0&rotation=0&textsize=medium&textposition=top&textbackgroundcolor=black&textcolor=white&text=0&clock=0&date=0&overlayimage=0&fps=0&videokeyframeinterval=13&videobitrate=0&maxframesize=0×tamp=1698491710423 + } +} \ No newline at end of file diff --git a/src/menus/editMenu.ts b/src/menus/editMenu.ts index f63d733..e9a862a 100644 --- a/src/menus/editMenu.ts +++ b/src/menus/editMenu.ts @@ -27,6 +27,7 @@ import {Controllers} from "../controllers/controllers"; import {setMenuPosition} from "../util/functions/setMenuPosition"; import {DiagramExporter} from "../util/diagramExporter"; import {SoccerMenu} from "../soccer/soccerMenu"; +import {CameraMenu} from "./cameraMenu"; export class EditMenu extends AbstractMenu { private state: EditMenuState = EditMenuState.NONE; @@ -37,6 +38,7 @@ export class EditMenu extends AbstractMenu { private readonly diagramManager: DiagramManager; private connection: DiagramConnection = null; private panel: PlanePanel; + private cameraMenu: CameraMenu = null; private sounds: DiaSounds; private get isVisible(): boolean { @@ -237,13 +239,13 @@ export class EditMenu extends AbstractMenu { panel.columns = 4; this.manager.addControl(panel); - + panel.addControl(this.makeButton("Cameras", "camera")); panel.addControl(this.makeButton("Modify", "modify")); panel.addControl(this.makeButton("Remove", "remove")); 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("Export GLTF", "exportgltf")); panel.addControl(this.makeButton("Recolor", "recolor")); panel.addControl(this.makeButton("New Relic", "newrelic")); panel.addControl(this.makeButton("Soccer", "soccer")); @@ -303,12 +305,16 @@ export class EditMenu extends AbstractMenu { case "newrelic": this.showNewRelic(); break; - case "export": - this.download(); + case "exportgltf": + this.downloadgltf(); break; case "soccer": this.createSoccerField(); break; + case "camera": + if (!this.cameraMenu) { + this.cameraMenu = new CameraMenu(this.scene, null, null); + } default: this.logger.error("Unknown button"); return; @@ -324,8 +330,8 @@ export class EditMenu extends AbstractMenu { } } - private download() { + private downloadgltf() { const exporter = new DiagramExporter(this.scene); - exporter.export(); + exporter.exportgltf(); } } \ No newline at end of file diff --git a/src/menus/integrationMenu.ts b/src/menus/integrationMenu.ts index 106945a..232817a 100644 --- a/src/menus/integrationMenu.ts +++ b/src/menus/integrationMenu.ts @@ -30,6 +30,4 @@ export class IntegrationMenu extends AbstractMenu { grid.addControl(labelText2, 1, 0); setMenuPosition(this.plane, this.scene); } - - } \ No newline at end of file diff --git a/src/objects/button.ts b/src/objects/button.ts new file mode 100644 index 0000000..8d0faee --- /dev/null +++ b/src/objects/button.ts @@ -0,0 +1,43 @@ +import {DynamicTexture, MeshBuilder, StandardMaterial, TransformNode, Vector2, Vector3} from "@babylonjs/core"; + +export class Button { + private parent: TransformNode; + + constructor(parent: TransformNode, label: string, description: string, position: Vector2) { + this.parent = parent; + this.buildButton(label, description, position); + } + + private buildButton(label: string, description: string, position: Vector2) { + const button = MeshBuilder.CreateSphere(label, {diameter: .1}, this.parent.getScene()); + const descriptionPlane = MeshBuilder.CreatePlane(label, {width: .3, height: .1}, this.parent.getScene()); + button.parent = this.parent; + button.position.y = position.y; + button.position.x = position.x; + descriptionPlane.parent = this.parent; + descriptionPlane.position.y = position.y - .1; + descriptionPlane.position.x = position.x; + + const descTexture = new DynamicTexture('texture_desc_' + label, { + width: 768, + height: 256 + }, this.parent.getScene()); + const descMaterial = new StandardMaterial('button_desc_' + label) + descriptionPlane.material = descMaterial; + descMaterial.diffuseTexture = descTexture; + descTexture.drawText(description, null, null, 'bold 64px Arial', + '#000000', '#ffffff', true); + + const texture = new DynamicTexture('texture_' + label, {width: 256, height: 256}, this.parent.getScene()); + const material = new StandardMaterial('button_' + label) + button.material = material; + material.diffuseTexture = texture; + texture.drawText(label, null, null, 'bold 128px Arial', + '#000000', '#ffffff', true); + + button.scaling = new Vector3(.1, 1, 1); + button.rotation.y = Math.PI / 2; + button.rotation.z = Math.PI; + } + +} \ No newline at end of file diff --git a/src/util/diagramExporter.ts b/src/util/diagramExporter.ts index 993f0bf..05e45dd 100644 --- a/src/util/diagramExporter.ts +++ b/src/util/diagramExporter.ts @@ -7,7 +7,7 @@ export class DiagramExporter { this.scene = scene; } - public export() { + public exportgltf() { import("@babylonjs/serializers").then((serializers) => { serializers.GLTF2Export.GLBAsync(this.scene, 'diagram.glb', { shouldExportNode: function (node) { diff --git a/src/util/functions/sceneInspctor.ts b/src/util/functions/sceneInspctor.ts new file mode 100644 index 0000000..b7578c6 --- /dev/null +++ b/src/util/functions/sceneInspctor.ts @@ -0,0 +1,21 @@ +export function addSceneInspector(scene) { + window.addEventListener("keydown", (ev) => { + if (ev.key == "z") { + //voiceManager.startRecording(); + } + if (ev.key == "x") { + //voiceManager.stopRecording(); + } + if (ev.shiftKey && ev.ctrlKey && ev.altKey && ev.keyCode === 73) { + import("@babylonjs/core/Debug/debugLayer").then(() => { + import("@babylonjs/inspector").then(() => { + if (scene.debugLayer.isVisible()) { + scene.debugLayer.hide(); + } else { + scene.debugLayer.show(); + } + }); + }); + } + }); +} \ No newline at end of file