diff --git a/index.html b/index.html index 7957f56..9464f6e 100644 --- a/index.html +++ b/index.html @@ -15,7 +15,7 @@ align-content: center; flex-direction: column; z-index: -1; - background: url("/loading-loading-forever.gif"); + background: url("/spinner.gif"); background-position: center; background-repeat: no-repeat; text-align: center; diff --git a/package-lock.json b/package-lock.json index df53abc..a04d981 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,10 +10,10 @@ "hasInstallScript": true, "dependencies": { "@auth0/auth0-spa-js": "^2.0.8", - "@babylonjs/core": "^6.8.0", - "@babylonjs/gui": "^6.9.0", - "@babylonjs/havok": "^1.0.1", - "@babylonjs/inspector": "^6.8.0", + "@babylonjs/core": "^6.12.3", + "@babylonjs/gui": "^6.12.3", + "@babylonjs/havok": "^1.1.0", + "@babylonjs/inspector": "^6.12.3", "@maptiler/client": "^1.5.0", "axios": "^1.4.0", "express": "^4.18.2", @@ -48,14 +48,14 @@ } }, "node_modules/@babylonjs/core": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/@babylonjs/core/-/core-6.8.0.tgz", - "integrity": "sha512-wTWj9TsnVGqfXt+tXKi7+SIWi4MzBMwIq+jcylRR1qzHTHFfMuKrRRLZJ5jQtpAhcDPI2TOuJ3/NOccPyawlgQ==" + "version": "6.12.3", + "resolved": "https://registry.npmjs.org/@babylonjs/core/-/core-6.12.3.tgz", + "integrity": "sha512-p1di605M2Pa5+YiHbydGJ3PA4nUWSlmu79agL3mcsq7s8zC5VN/HaK1uNSicYI9LhVfPF6bDsVGHqCXxEkRsFQ==" }, "node_modules/@babylonjs/gui": { - "version": "6.9.0", - "resolved": "https://registry.npmjs.org/@babylonjs/gui/-/gui-6.9.0.tgz", - "integrity": "sha512-RcVorxsj6n2EbwkBJYPBqF88Rybd3OHbx55runbMSehb9rKhc6d2QIiXTi7yI2oBNyuQBsDxx+ky58Rur2UXww==", + "version": "6.12.3", + "resolved": "https://registry.npmjs.org/@babylonjs/gui/-/gui-6.12.3.tgz", + "integrity": "sha512-Yh9rVWwAymjy23g8dC5PMjeI2c71HWMZ6Lw2G0yjTZS9gf0st2A/OLsv/4ofK51u0fqvHZFBTfFTL5ZOFYNMow==", "peerDependencies": { "@babylonjs/core": "^6.0.0" } @@ -73,17 +73,17 @@ } }, "node_modules/@babylonjs/havok": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@babylonjs/havok/-/havok-1.0.1.tgz", - "integrity": "sha512-J41CIAbL9WOQkPRdMADEVKDKZsU4iOlesBg0C/LP1GPxUncGVylLPkyyGPcQPLd6ifV9cZcnkEJrkiE8xVahTw==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@babylonjs/havok/-/havok-1.1.0.tgz", + "integrity": "sha512-BNo2d+gfkoCbbEGYOVZgdPWG6NRdo5Tjvd9rpjMs0sWV/EaKQ6kL8hnJUn+HVZx4hdyKrTkq8ThTGFt/nTediA==", "dependencies": { "@types/emscripten": "^1.39.6" } }, "node_modules/@babylonjs/inspector": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/@babylonjs/inspector/-/inspector-6.8.0.tgz", - "integrity": "sha512-tu3nb0l7xSYXkTaKAnP7zBL34qR9J0op+NzOn4Y1C9RcY91yH3YfLTdoo4pFA0K5NaS99W0nAIjCcrS2BPBmMQ==", + "version": "6.12.3", + "resolved": "https://registry.npmjs.org/@babylonjs/inspector/-/inspector-6.12.3.tgz", + "integrity": "sha512-REW+BF4LQhOK0cRRTVfSaOGnjh1LVE1lgwel7BBjjWeRcIjCVBimAprPY8rE0K+EAGMmPlxt9rDIpGoA0PUTqQ==", "dependencies": { "@fortawesome/fontawesome-svg-core": "^6.1.0", "@fortawesome/free-regular-svg-icons": "^6.0.0", diff --git a/package.json b/package.json index c3ef175..361feca 100644 --- a/package.json +++ b/package.json @@ -12,10 +12,10 @@ "postinstall": "cp ./node_modules/@babylonjs/havok/lib/esm/HavokPhysics.wasm ./node_modules/.vite/deps" }, "dependencies": { - "@babylonjs/core": "^6.8.0", - "@babylonjs/gui": "^6.9.0", - "@babylonjs/havok": "^1.0.1", - "@babylonjs/inspector": "^6.8.0", + "@babylonjs/core": "^6.12.3", + "@babylonjs/gui": "^6.12.3", + "@babylonjs/havok": "^1.1.0", + "@babylonjs/inspector": "^6.12.3", "express": "^4.18.2", "@auth0/auth0-spa-js": "^2.0.8", "ring-client-api": "^11.8.0", diff --git a/public/spinner.gif b/public/spinner.gif new file mode 100644 index 0000000..116ffcb Binary files /dev/null and b/public/spinner.gif differ diff --git a/src/app.ts b/src/app.ts index 98fe0be..b3c7c3d 100644 --- a/src/app.ts +++ b/src/app.ts @@ -19,15 +19,16 @@ import { ///import {havokModule} from "./util/havok"; import HavokPhysics from "@babylonjs/havok"; import {Rigplatform} from "./controllers/rigplatform"; - import {DiagramManager} from "./diagram/diagramManager"; -class App { +export class App { //preTasks = [havokModule]; private token: string; - + public static scene: Scene; + public static xr: WebXRDefaultExperience; + public static rig: Rigplatform; constructor() { const canvas = document.createElement("canvas"); canvas.style.width = "100%"; @@ -40,38 +41,46 @@ class App { } async initialize(canvas) { - + if (App.xr) { + App.xr.dispose(); + App.xr=null; + } + if (App.scene) { + App.scene.dispose(); + App.scene = null; + } + if (DiagramManager.onDiagramEventObservable) { + DiagramManager.onDiagramEventObservable.clear(); + DiagramManager.onDiagramEventObservable = null; + } const engine = new Engine(canvas, true); const scene = new Scene(engine); - const diagramManager = new DiagramManager(scene); + + App.scene = scene; + + + const havokInstance = await HavokPhysics(); + const havokPlugin = new HavokPlugin(true, havokInstance); scene.enablePhysics(new Vector3(0, -9.8, 0), havokPlugin); const camera: ArcRotateCamera = new ArcRotateCamera("Camera", Math.PI / 2, Math.PI / 2, 2, new Vector3(0, 1.6, 0), scene); camera.attachControl(canvas, true); new HemisphericLight("light1", new Vector3(1, 1, 0), scene); - - - //const envTexture = new CubeTexture("/assets/textures/SpecularHDR.dds", scene); - //scene.createDefaultSkybox(envTexture, true, 1000); - const photoDome = new PhotoDome('sky', './outdoor_field.jpeg', {}, scene); - const xr = await WebXRDefaultExperience.CreateAsync(scene, { - floorMeshes: [this.createGround(scene)], + App.xr = await WebXRDefaultExperience.CreateAsync(scene, { + floorMeshes: [this.createGround()], disableTeleportation: true, optionalFeatures: true }); - const rig = new Rigplatform(scene, xr); - //const ring = new Cameras(scene, this.token); - //ring.getCameras().then(() => ring.createCameras()); - //xr.teleportation.detach(); + const diagramManager = new DiagramManager(App.scene, App.xr.baseExperience); + App.rig = new Rigplatform(App.scene, App.xr); - // hide/show the Inspector window.addEventListener("keydown", (ev) => { // Shift+Ctrl+Alt+I if (ev.shiftKey && ev.ctrlKey && ev.altKey && ev.keyCode === 73) { @@ -89,19 +98,19 @@ class App { }); } - createGround(scene) { - const groundMaterial = new PBRMetallicRoughnessMaterial("groundMaterial", scene); - const gText = new Texture("./grass1.jpeg", scene); + createGround() { + const groundMaterial = new PBRMetallicRoughnessMaterial("groundMaterial", App.scene); + const gText = new Texture("./grass1.jpeg", App.scene); gText.uScale = 40; gText.vScale = 40; groundMaterial.baseTexture = gText; groundMaterial.metallic = 0; groundMaterial.roughness = 1; - const ground = MeshBuilder.CreateGround("ground", {width: 100, height: 100, subdivisions: 1}, scene); + const ground = MeshBuilder.CreateGround("ground", {width: 100, height: 100, subdivisions: 1}, App.scene); ground.material = groundMaterial; - const groundAggregate = new PhysicsAggregate(ground, PhysicsShapeType.BOX, {mass: 0}, scene); + new PhysicsAggregate(ground, PhysicsShapeType.BOX, {mass: 0}, App.scene); return ground; } } diff --git a/src/controllers/base.ts b/src/controllers/base.ts index 319c414..1213944 100644 --- a/src/controllers/base.ts +++ b/src/controllers/base.ts @@ -1,10 +1,8 @@ import {Vector3, WebXRInputSource} from "@babylonjs/core"; -import {Rigplatform} from "./rigplatform"; export class Base { static stickVector = Vector3.Zero(); protected controller: WebXRInputSource; - protected rig: Rigplatform; protected speedFactor = 4; constructor(controller: @@ -22,8 +20,7 @@ export class Base { } }); } - - setRig(rig: Rigplatform) { - this.rig = rig; + public mesh() { + return this.controller.grip; } } \ No newline at end of file diff --git a/src/controllers/controllers.ts b/src/controllers/controllers.ts new file mode 100644 index 0000000..73882eb --- /dev/null +++ b/src/controllers/controllers.ts @@ -0,0 +1,20 @@ + +import {AbstractMesh, Observable, TransformNode} from "@babylonjs/core"; + +export enum ControllerMovementMode { + ROTATE, + TRANSLATE +} +export class Controllers { + public static movable: TransformNode | AbstractMesh; + public static controllerObserver = new Observable(); + public static movementMode: ControllerMovementMode = ControllerMovementMode.ROTATE; + public static toggleMovementMode() { + if (this.movementMode == ControllerMovementMode.ROTATE) { + this.movementMode = ControllerMovementMode.TRANSLATE; + } else { + this.movementMode = ControllerMovementMode.ROTATE; + } + } + +} \ No newline at end of file diff --git a/src/controllers/left.ts b/src/controllers/left.ts index ff7e110..8a582fb 100644 --- a/src/controllers/left.ts +++ b/src/controllers/left.ts @@ -1,40 +1,65 @@ import {Vector3, WebXRInputSource} from "@babylonjs/core"; import {Base} from "./base"; +import {Controllers} from "./controllers"; + export class Left extends Base { - + public static instance: Left; constructor(controller: WebXRInputSource) { super(controller); + + Left.instance = this; this.controller.onMotionControllerInitObservable.add((init) => { if (init.components['xr-standard-thumbstick']) { init.components['xr-standard-thumbstick'] .onAxisValueChangedObservable.add((value) => { - - if (Math.abs(value.x) > .1) { - this.rig.leftright(value.x * this.speedFactor); - Base.stickVector.x = 1; + if (!Controllers.movable) { + this.moveRig(value); } else { - Base.stickVector.x = 0; - } - if (Math.abs(value.y) > .1) { - this.rig.updown(value.y * this.speedFactor); - Base.stickVector.y = 1; - } else { - Base.stickVector.y = 0; + this.moveMovable(value); } - if (Base.stickVector.equals(Vector3.Zero())) { - this.rig.updown(0); - this.rig.leftright(0) - } else { - - } }); } }); } + + private moveMovable(value: { x: number, y: number }) { + if (Math.abs(value.x) > .1) { + Controllers.movable.position.x += .005 * Math.sign(value.x); + } else { + + } + if (Math.abs(value.y) > .1) { + Controllers.movable.position.y += -.005 * Math.sign(value.y); + } else { + + } + } + + private moveRig(value: { x: number, y: number }) { + if (Math.abs(value.x) > .1) { + Controllers.controllerObserver.notifyObservers({type: 'leftright', value: value.x * this.speedFactor}); + Base.stickVector.x = 1; + } else { + Base.stickVector.x = 0; + } + if (Math.abs(value.y) > .1) { + Controllers.controllerObserver.notifyObservers({type: 'updown', value: value.y * this.speedFactor}); + Base.stickVector.y = 1; + } else { + Base.stickVector.y = 0; + } + + if (Base.stickVector.equals(Vector3.Zero())) { + Controllers.controllerObserver.notifyObservers({type: 'leftright', value: 0}); + Controllers.controllerObserver.notifyObservers({type: 'updown', value: 0}); + } else { + + } + } } \ No newline at end of file diff --git a/src/controllers/right.ts b/src/controllers/right.ts index 16bf432..54bb12f 100644 --- a/src/controllers/right.ts +++ b/src/controllers/right.ts @@ -1,71 +1,160 @@ import {Base} from "./base"; -import {Vector3, WebXRInputSource} from "@babylonjs/core"; -import {Bmenu, BmenuState} from "../menus/bmenu"; -import {DiagramEvent, DiagramEventType, DiagramManager} from "../diagram/diagramManager"; +import {Angle, Observable, Vector3, WebXRControllerComponent, WebXRInputSource} from "@babylonjs/core"; +import {Bmenu} from "../menus/bmenu"; +import {DiagramManager} from "../diagram/diagramManager"; +import {ControllerMovementMode, Controllers} from "./controllers"; +import {BmenuState} from "../menus/MenuState"; +import {DiagramEvent, DiagramEventType} from "../diagram/diagramEntity"; + + export class Right extends Base { private bmenu: Bmenu; + public static instance: Right; + private down: boolean = false; + private initBButton(bbutton: WebXRControllerComponent) { + if (bbutton) { + bbutton.onButtonStateChangedObservable.add((value) => { + if (value.pressed) { + this.bmenu.toggle(this.controller.grip); + } + }); + } + } + + private initTrigger(trigger: WebXRControllerComponent) { + if (trigger) { + trigger + .onButtonStateChangedObservable + .add((value) => { + if (value.value > .4 && !this.down) { + this.down = true; + if (this.bmenu.getState() == BmenuState.ADDING) { + this.bmenu.setState(BmenuState.DROPPING); + const event: DiagramEvent = { + type: DiagramEventType.DROP, + entity: null + } + DiagramManager.onDiagramEventObservable.notifyObservers(event); + } + } + if (value.value < .05) { + this.down = false; + } + }); + } + } + + private initAButton(abutton: WebXRControllerComponent) { + if (abutton) { + abutton.onButtonStateChangedObservable.add((value) => { + if (value.pressed) { + if (DiagramManager.currentMesh) { + if (Controllers.movable) { + Controllers.movable = null; + } else { + Controllers.movable = DiagramManager.currentMesh; + } + + } + } + }); + } + } + + private initThumbstick(thumbstick: WebXRControllerComponent) { + if (thumbstick) { + thumbstick.onAxisValueChangedObservable.add((value) => { + if (!Controllers.movable) { + this.moveRig(value); + } else { + if (Controllers.movementMode == ControllerMovementMode.ROTATE) { + this.rotateMovable(value); + } else { + this.moveMovable(value); + } + } + }); + thumbstick.onButtonStateChangedObservable.add((value) => { + if (value.pressed) { + Controllers.toggleMovementMode(); + } + }); + } + } + + private moveRig(value) { + if (Math.abs(value.x) > .1) { + Controllers.controllerObserver.notifyObservers({type: 'turn', value: value.x}); + } else { + Controllers.controllerObserver.notifyObservers({type: 'turn', value: 0}); + } + if (Math.abs(value.y) > .1) { + Controllers.controllerObserver.notifyObservers({type: 'forwardback', value: value.y * this.speedFactor}); + Base.stickVector.z = 1; + } else { + Controllers.controllerObserver.notifyObservers({type: 'forwardback', value: 0}); + Base.stickVector.z = 0; + } + if (Base.stickVector.equals(Vector3.Zero())) { + Controllers.controllerObserver.notifyObservers({type: 'forwardback', value: 0}); + } + } + constructor(controller: WebXRInputSource) { super(controller); + Right.instance = this; this.controller.onMotionControllerInitObservable.add((init) => { - const trigger = init.components['xr-standard-trigger']; - if (trigger) { - trigger - .onButtonStateChangedObservable - .add((value) => { - if (value.value > .4 && !this.down) { - this.down = true; - if (this.bmenu.getState() == BmenuState.ADDING) { - this.bmenu.setState(BmenuState.DROPPING); - const event: DiagramEvent = { - type: DiagramEventType.DROP, - entity: null - } - DiagramManager.onDiagramEventObservable.notifyObservers(event); - } - } - if (value.value < .05) { - this.down = false; - } - }); - } - if (init.components['b-button']) { - init.components['b-button'].onButtonStateChangedObservable.add((value) => { - if (value.pressed) { - this.bmenu.toggle(); - } - }); - } - - if (init.components['xr-standard-thumbstick']) { - init.components['xr-standard-thumbstick'] - .onAxisValueChangedObservable.add((value) => { - if (Math.abs(value.x) > .1) { - this.rig.turn(value.x); - } else { - this.rig.turn(0); - } - - if (Math.abs(value.y) > .1) { - this.rig.forwardback(value.y * this.speedFactor); - Base.stickVector.z = 1; - } else { - Base.stickVector.z = 0; - } - if (Base.stickVector.equals(Vector3.Zero())) { - this.rig.forwardback(0); - } - }); - } + this.initTrigger(init.components['xr-standard-trigger']); + this.initBButton(init.components['b-button']); + this.initAButton(init.components['a-button']); + this.initThumbstick(init.components['xr-standard-thumbstick']); + this.initGrip(init.components['xr-standard-squeeze']); }); } + private initGrip(grip: WebXRControllerComponent) { + grip.onButtonStateChangedObservable.add((value) => { + if (value.value > .5) { + if (this.controller.pointer.collider.collidedMesh) { + console.log(this.controller.pointer.collider.collidedMesh.id); + } + } + }); + } public setBMenu(menu: Bmenu) { this.bmenu = menu; this.bmenu.setController(this.controller); } + private rotateMovable(value: { x: number; y: number }) { + if (Math.abs(value.y) > .1) { + Controllers.movable.rotation.x += + Angle.FromDegrees(Math.sign(value.y) * 1).radians(); + Controllers.movable.rotation.x = this.fixRadians(Controllers.movable.rotation.x); + } + if (Math.abs(value.x) > .1) { + Controllers.movable.rotation.z += + Angle.FromDegrees(Math.sign(value.x) * 1).radians(); + Controllers.movable.rotation.z = this.fixRadians(Controllers.movable.rotation.z); + } + } + private fixRadians(value: number) { + if (value > 2 * Math.PI) { + return value - 2 * Math.PI; + } else { + return value; + } + } + private moveMovable(value: { x: number; y: number }) { + if (Math.abs(value.y) > .1) { + Controllers.movable.position.z += Math.sign(value.y) * -.005; + } + if (Math.abs(value.x) > .1) { + Controllers.movable.position.x += Math.sign(value.x) * .005; + } + } } \ No newline at end of file diff --git a/src/controllers/rigplatform.ts b/src/controllers/rigplatform.ts index 1f4aeec..fd85c98 100644 --- a/src/controllers/rigplatform.ts +++ b/src/controllers/rigplatform.ts @@ -17,33 +17,36 @@ import { import {Right} from "./right"; import {Left} from "./left"; import {Bmenu} from "../menus/bmenu"; +import {Hud} from "../information/hud"; +import {Controllers} from "./controllers"; + export class Rigplatform { static LINEAR_VELOCITY = 4; static ANGULAR_VELOCITY = 3; static x90 = Quaternion.RotationAxis(Vector3.Up(), 1.5708); public bMenu: Bmenu; + private scene: Scene; + public static instance: Rigplatform; + private static xr: WebXRDefaultExperience; private yRotation: number = 0; - public right: Right; - public left: Left; public body: PhysicsBody; public rigMesh: Mesh; private camera: Camera; - private scene: Scene; - private xr: WebXRDefaultExperience; private turning: boolean = false; constructor(scene: Scene, xr: WebXRDefaultExperience) { - this.xr = xr; - this.bMenu = new Bmenu(scene, this.xr.baseExperience); - this.camera = scene.activeCamera; - this.scene = scene; + Rigplatform.xr = xr; + Rigplatform.instance = this; - this.rigMesh = MeshBuilder.CreateCylinder("platform", {diameter: 1.5, height: .01}, scene); - - for (const cam of this.scene.cameras) { + this.bMenu = new Bmenu(scene, xr.baseExperience); + this.camera = scene.activeCamera; + this.rigMesh = MeshBuilder.CreateBox("platform", {width: 2, height: .02, depth: 2}, scene); + const hud = new Hud(this.rigMesh, scene); + for (const cam of scene.cameras) { cam.parent = this.rigMesh; + cam.position = new Vector3(0, 1.6, 0); } const myMaterial = new StandardMaterial("myMaterial", scene); @@ -55,31 +58,28 @@ export class Rigplatform { new PhysicsAggregate( this.rigMesh, PhysicsShapeType.CYLINDER, - {friction: 1, center: Vector3.Zero(), radius: .5, mass: .1, restitution: .1}, + {friction: 1, center: Vector3.Zero(), radius: .5, mass: 10, restitution: .01}, scene); - rigAggregate.body.setMotionType(PhysicsMotionType.ANIMATED); - rigAggregate.body.setGravityFactor(0); + rigAggregate.body.setMotionType(PhysicsMotionType.DYNAMIC); + rigAggregate.body.setGravityFactor(.001); + this.#fixRotation(); this.body = rigAggregate.body; this.#setupKeyboard(); this.#initializeControllers(); - this.scene.onActiveCameraChanged.add((s) => { + scene.onActiveCameraChanged.add((s) => { this.camera = s.activeCamera; this.camera.parent = this.rigMesh; - console.log('camera changed'); }); } public forwardback(val: number) { const ray = this.camera.getForwardRay(); - this.body.setLinearVelocity(ray.direction.scale(val * -1)); } public leftright(val: number) { - - const ray = this.camera.getForwardRay(); const direction = ray.direction.applyRotationQuaternion(Rigplatform.x90).scale(val); this.body.setLinearVelocity(direction); @@ -125,28 +125,46 @@ export class Rigplatform { } #initializeControllers() { - this.xr.input.onControllerAddedObservable.add((source, state) => { + Rigplatform.xr.input.onControllerAddedObservable.add((source) => { let controller; switch (source.inputSource.handedness) { case "right": - controller = new Right(source); - this.right = controller; - controller.setBMenu(this.bMenu); + Right.instance = new Right(source); + Right.instance.setBMenu(this.bMenu); + Controllers.controllerObserver.add((event: { type: string, value: number }) => { + switch (event.type) { + case "turn": + this.turn(event.value); + break; + case "forwardback": + this.forwardback(event.value); + break; + case "leftright": + this.leftright(event.value); + break; + case "updown": + this.updown(event.value); + break; + case "stop": + this.stop(); + break; + + } + + }); break; case "left": - controller = new Left(source); - this.left = controller; + Left.instance = new Left(source); break; } - this.xr.baseExperience.camera.position = new Vector3(0, 1.6, 0); + Rigplatform.xr.baseExperience.camera.position = new Vector3(0, 1.6, 0); if (controller) { controller.setRig(this); } - - console.log(source); - console.log(state); }); + + } //create a method to set the camera to the rig @@ -181,7 +199,7 @@ export class Rigplatform { this.updown(1 * Rigplatform.LINEAR_VELOCITY); break; case " ": - this.bMenu.toggle() + this.bMenu.toggle(this.rigMesh) } }); @@ -197,6 +215,7 @@ export class Rigplatform { #fixRotation() { this.scene.registerBeforeRender(() => { const q = this.rigMesh.rotationQuaternion; + this.body.setAngularVelocity(Vector3.Zero()); const e = q.toEulerAngles(); e.y += this.yRotation; q.copyFrom(Quaternion.FromEulerAngles(0, e.y, 0)); diff --git a/src/diagram/diagramEntity.ts b/src/diagram/diagramEntity.ts index 139597f..a794dcf 100644 --- a/src/diagram/diagramEntity.ts +++ b/src/diagram/diagramEntity.ts @@ -1,2 +1,31 @@ +import {Vector3} from "@babylonjs/core"; +import {BmenuState} from "../menus/MenuState"; +export enum DiagramEventType { + ADD, + REMOVE, + MODIFY, + DROP, + DROPPED, + CLEAR, + + +} +export type DiagramEvent = { + type: DiagramEventType; + menustate?: BmenuState; + entity?: DiagramEntity; + +} +export type DiagramEntity = { + color?: string; + id?: string; + last_seen?: Date; + position?: Vector3; + rotation?: Vector3; + template?: string; + text?: string; + scale?: Vector3; + parent?: string; +} diff --git a/src/diagram/diagramManager.ts b/src/diagram/diagramManager.ts index 1d6d444..ec0f633 100644 --- a/src/diagram/diagramManager.ts +++ b/src/diagram/diagramManager.ts @@ -1,77 +1,70 @@ -import {AbstractMesh, Color3, Mesh, MeshBuilder, Observable, Scene, StandardMaterial, Vector3} from "@babylonjs/core"; +import { + AbstractMesh, + Angle, + Color3, + Mesh, + MeshBuilder, + Observable, Scene, + StandardMaterial, + Vector3, WebXRExperienceHelper +} from "@babylonjs/core"; import {v4 as uuidv4} from 'uuid'; -import {BmenuState} from "../menus/bmenu"; - - -export enum DiagramEventType { - ADD, - REMOVE, - MODIFY, - DROP, - DROPPED, - - -} - -export type DiagramEvent = { - type: DiagramEventType; - menustate?: BmenuState; - entity?: DiagramEntity; - -} - -export type DiagramEntity = { - color?: string; - id?: string; - last_seen?: Date; - position?: Vector3; - rotation?: Vector3; - template?: string; - text?: string; - scale?: Vector3; - parent?: string; -} +import {DiagramEntity, DiagramEvent, DiagramEventType} from "./diagramEntity"; +import {PersistenceManager} from "./persistenceManager"; export class DiagramManager { + private persistenceManager: PersistenceManager = new PersistenceManager(); static onDiagramEventObservable = new Observable(); - static leftController: Mesh; + private scene: Scene; + private xr: WebXRExperienceHelper; static currentMesh: AbstractMesh; - static rightController: Mesh; - static state: BmenuState; - private readonly scene: Scene; - constructor(scene: Scene) { + constructor(scene: Scene, xr: WebXRExperienceHelper) { this.scene = scene; + this.xr = xr; + this.persistenceManager.updateObserver.add(this.#onRemoteEvent, -1, true, this); + this.persistenceManager.initialize(); + if (!DiagramManager.onDiagramEventObservable) { + DiagramManager.onDiagramEventObservable = new Observable(); + } if (DiagramManager.onDiagramEventObservable.hasObservers()) { } else { DiagramManager.onDiagramEventObservable.add(this.#onDiagramEvent, -1, true, this); } } + #onRemoteEvent(event: DiagramEntity) { + const mesh = this.#createMesh(event); + const material = new StandardMaterial("material-" + event.id, this.scene); + material.diffuseColor = Color3.FromHexString(event.color); + mesh.material = material; + } #onDiagramEvent(event: DiagramEvent) { - console.log(event); const entity = event.entity; - let mesh; - let material if (entity) { mesh = this.scene.getMeshByName(entity.id); - material = this.scene.getMaterialByName("material-" + entity.id); - if (!material) { - material = new StandardMaterial("material-" + event.entity.id, this.scene); - material.ambientColor = Color3.FromHexString(event.entity.color.replace("#", "")); + if (mesh) { + material = mesh.material; } } - switch (event.type) { + case DiagramEventType.CLEAR: + DiagramManager.currentMesh.dispose(); + DiagramManager.currentMesh = null; + break; case DiagramEventType.DROPPED: break; case DiagramEventType.DROP: if (DiagramManager.currentMesh) { - const newMesh = DiagramManager.currentMesh.clone(DiagramManager.currentMesh.name = "id" + uuidv4(), DiagramManager.currentMesh.parent); + this.persistenceManager.add(DiagramManager.currentMesh); + const newName = uuidv4(); + const newMesh = DiagramManager.currentMesh.clone("id"+newName, DiagramManager.currentMesh.parent); + const newMaterial = DiagramManager.currentMesh.material.clone("material"+newName); + newMesh.material=newMaterial; DiagramManager.currentMesh.setParent(null); DiagramManager.currentMesh = newMesh; DiagramManager.onDiagramEventObservable.notifyObservers({ @@ -81,44 +74,51 @@ export class DiagramManager { } break; case DiagramEventType.ADD: - if (DiagramManager.currentMesh){ + if (DiagramManager.currentMesh) { DiagramManager.currentMesh.dispose(); } - if (mesh) { return; } else { mesh = this.#createMesh(entity); + if (!material) { + material = new StandardMaterial("material-" + event.entity.id, this.scene); + material.diffuseColor = Color3.FromHexString(event.entity.color); + mesh.material = material; + + } if (!mesh) { return; } - } - + DiagramManager.currentMesh = mesh; + break; case DiagramEventType.MODIFY: if (!mesh) { } else { - const rotation = entity.rotation; - const scale = entity.scale; - const position = entity.position; + if (!material) { + material = new StandardMaterial("material-" + event.entity.id, this.scene); + material.diffuseColor = Color3.FromHexString(event.entity.color); + if (mesh) { + mesh.material = material; + } + } mesh.material = material; - mesh.position = new Vector3(position.x, position.y, position.z); - mesh.rotation = new Vector3(rotation.x, rotation.y, rotation.z); - + mesh.position = entity.position; + mesh.rotation = entity.rotation; if (entity.parent) { mesh.parent = this.scene.getMeshByName(entity.parent); + } else { + } } DiagramManager.currentMesh = mesh; break; case DiagramEventType.REMOVE: break; - - } - } #createMesh(entity: DiagramEntity) { @@ -130,28 +130,41 @@ export class DiagramManager { case "#box-template": mesh = MeshBuilder.CreateBox(entity.id, { - width: entity.scale.x, - height: entity.scale.y, - depth: entity.scale.z + width: 1, + height: 1, + depth: 1 }, this.scene); + break; - case "#sphere-template": - - mesh = MeshBuilder.CreateSphere(entity.id, {diameter: entity.scale.x}, this.scene); + mesh = MeshBuilder.CreateSphere(entity.id, {diameter: 1}, this.scene); break case "#cylinder-template": mesh = MeshBuilder.CreateCylinder(entity.id, { - diameter: entity.scale.x, - height: entity.scale.y + diameter: 1, + height: 1 }, this.scene); break; default: mesh = null; } + if (mesh) { + mesh.metadata = {template: entity.template}; + + if (entity.position) { + mesh.position = entity.position; + } + if (entity.rotation) { + mesh.rotation = entity.rotation; + } + if (entity.parent) { + mesh.parent = this.scene.getMeshByName(entity.parent); + } + if (entity.scale) { + mesh.scaling = entity.scale; + } + } + return mesh; } - -} - - +} \ No newline at end of file diff --git a/src/diagram/persistenceManager.ts b/src/diagram/persistenceManager.ts new file mode 100644 index 0000000..52a3c02 --- /dev/null +++ b/src/diagram/persistenceManager.ts @@ -0,0 +1,48 @@ +import {AbstractMesh, Mesh, Observable, StandardMaterial, Vector3} from "@babylonjs/core"; +import {DiagramEntity} from "./diagramEntity"; + +export class PersistenceManager { + public updateObserver: Observable = new Observable(); + + constructor() { + + } + public add(mesh: AbstractMesh) { + const entity = {}; + entity.id = mesh.id; + entity.position = mesh.position.toString(); + entity.rotation = mesh.rotation.toString(); + entity.last_seen = new Date().getDate(); + entity.template = "default"; + entity.text = ""; + entity.scale = mesh.scaling.toString(); + if (mesh.material) { + entity.color = (mesh.material as StandardMaterial).diffuseColor.toHexString(); + } + + console.log(entity); + } + + public remove() { + + } + public modify() { + + } + public initialize() { + const entity: DiagramEntity = {}; + entity.id = "test"; + entity.position = new Vector3(0,2,-2); + entity.rotation = Vector3.Zero(); + entity.last_seen = new Date(); + entity.scale = Vector3.One(); + entity.color = "#ff0000"; + entity.text = "test"; + entity.parent = null; + entity.template = "#box-template"; + + this.updateObserver.notifyObservers(entity); + } + + +} \ No newline at end of file diff --git a/src/information/hud.ts b/src/information/hud.ts new file mode 100644 index 0000000..74f4fac --- /dev/null +++ b/src/information/hud.ts @@ -0,0 +1,56 @@ +import {AbstractMesh, Color3, MeshBuilder, Scene, Vector3} from "@babylonjs/core"; +import {AdvancedDynamicTexture, StackPanel, TextBlock} from "@babylonjs/gui"; +import {Controllers} from "../controllers/controllers"; + +export class Hud { + private scene: Scene; + private parent: AbstractMesh; + private hudPlane: AbstractMesh; + constructor(parent: AbstractMesh, scene: Scene) { + this.scene = scene; + this.parent = parent; + this.hudPlane = MeshBuilder.CreatePlane("hudPlane", {width: 1, height: .5}, this.parent.getScene()); + this.hudPlane.parent=this.parent.getScene().activeCamera + this.parent.getScene().onActiveCameraChanged.add((scene) => { + this.hudPlane.parent = scene.activeCamera; + }); + this.hudPlane.position = new Vector3(.5, .75, 3); + + this.hudPlane.outlineColor = Color3.FromHexString("#ffffff"); + + + const textPosition = this.createTextBlock(); + const textRotation = this.createTextBlock(); + + const hudTexture = AdvancedDynamicTexture.CreateForMesh(this.hudPlane, 1024, 512); + + hudTexture.background = "black"; + const stackPanel = new StackPanel(); + hudTexture.addControl(stackPanel); + + + stackPanel.addControl(textPosition); + stackPanel.addControl(textRotation); + + this.scene.onBeforeRenderObservable.add(() => { + if (Controllers.movable) { + textPosition.text = 'position: '+ this.formatVector3(Controllers.movable.position); + textRotation.text = 'rotation: '+ this.formatVector3(Controllers.movable.rotation); + } + }); + } + createTextBlock(): TextBlock { + const text = new TextBlock(); + text.isHitTestVisible = false; + text.text = ""; + text.height="20%"; + text.resizeToFit=true; + + text.color="white"; + text.fontSize = 64; + return text; + } + private formatVector3(v: Vector3): string { + return `(${v.x.toFixed(2)},${v.y.toFixed(2)},${v.z.toFixed(2)})`; + } +} diff --git a/src/menus/MenuState.ts b/src/menus/MenuState.ts new file mode 100644 index 0000000..125c074 --- /dev/null +++ b/src/menus/MenuState.ts @@ -0,0 +1,6 @@ +export enum BmenuState { + NONE, + ADDING, // Adding a new entity + DROPPING, // Dropping an entity + +} \ No newline at end of file diff --git a/src/menus/bmenu.ts b/src/menus/bmenu.ts index 01e83b8..e9cf293 100644 --- a/src/menus/bmenu.ts +++ b/src/menus/bmenu.ts @@ -1,43 +1,36 @@ import { AbstractMesh, - Angle, Color3, Scene, StandardMaterial, - TransformNode, Vector3, WebXRExperienceHelper, WebXRInputSource } from "@babylonjs/core"; -import {GUI3DManager, HolographicButton, PlanePanel} from "@babylonjs/gui"; -import {DiagramEntity, DiagramEvent, DiagramEventType, DiagramManager} from "../diagram/diagramManager"; +import {GUI3DManager, NearMenu, TouchHolographicButton} from "@babylonjs/gui"; +import {DiagramManager} from "../diagram/diagramManager"; +import {BmenuState} from "./MenuState"; +import {DiagramEntity, DiagramEvent, DiagramEventType} from "../diagram/diagramEntity"; -export enum BmenuState { - NONE, - ADDING, // Adding a new entity - DROPPING, // Dropping an entity - -} export class Bmenu { - private readonly scene; private state: BmenuState = BmenuState.NONE; + private manager: GUI3DManager; + private scene: Scene; - private xr; - private manager; - private panel; private rightController: AbstractMesh; + private xr: WebXRExperienceHelper; constructor(scene: Scene, xr: WebXRExperienceHelper) { this.scene = scene; - this.manager = new GUI3DManager(scene); - this.xr = xr; + DiagramManager.onDiagramEventObservable.add((event: DiagramEvent) => { if (event.type === DiagramEventType.DROPPED) { this.state = BmenuState.ADDING; } }); + } setController(controller: WebXRInputSource) { @@ -45,7 +38,7 @@ export class Bmenu { } makeButton(name: string, id: string) { - const button = new HolographicButton(name); + const button = new TouchHolographicButton(name); button.text = name; button.name = id; button.onPointerClickObservable.add(this.#clickhandler, -1, false, this); @@ -59,76 +52,94 @@ export class Bmenu { public setState(state: BmenuState) { this.state = state; } - - toggle() { - if (this.panel) { - this.panel.dispose(); - this.panel = null; - + toggle(mesh: AbstractMesh) { + console.log(mesh.name); + if (this.manager) { + this.manager.dispose(); + this.manager = null; } else { - const anchor = new TransformNode("bMenuAnchor"); - anchor.rotation.y = Angle.FromDegrees(180).radians(); - const cam = this.xr.camera.getFrontPosition(1); - cam.y = cam.y - .5; - anchor.position = cam; - const panel = new PlanePanel(); - panel.margin = .06; + this.manager = new GUI3DManager(this.scene); + const panel = new NearMenu(); this.manager.addControl(panel); - panel.linkToTransformNode(anchor); - panel.addControl(this.makeButton("Add Box", "addBox")); - panel.addControl(this.makeButton("Add Sphere", "addSphere")); - panel.addControl(this.makeButton("Add Cylinder", "addCylinder")); - panel.addControl(this.makeButton("Done Adding", "doneAdding")); - for (const control of panel.children) { - control.scaling = new Vector3(.1, .1, .1); - } - this.panel = panel; - } + const follower = panel.defaultBehavior.followBehavior; + follower.maxViewHorizontalDegrees = 45; + follower.useFixedVerticalOffset = true; + follower.fixedVerticalOffset = 1; + follower.defaultDistance = 2; + follower.maximumDistance = 3; + follower.minimumDistance = 1; + panel.backPlateMargin = .01; + panel.scaling= new Vector3(.5, .5, .1); + panel.margin = .01; + //panel.scaling.x = .5; + //panel.scaling.y = .5; + //const camdir = panel.mesh.getDirection(this.xr.camera.globalPosition); + //panel.mesh.lookAt(this.xr.camera.globalPosition); + panel.addButton(this.makeButton("Add Box", "addBox")); + panel.addButton(this.makeButton("Add Sphere", "addSphere")); + panel.addButton(this.makeButton("Add Cylinder", "addCylinder")); + panel.addButton(this.makeButton("Done Adding", "doneAdding")); + this.manager.controlScaling = .5; + + } } #clickhandler(_info, state) { console.log(state.currentTarget.name); + const id = this?.rightController?.id || null; let entity: DiagramEntity = { template: null, - position: new Vector3(0, -.040, .13), - rotation: new Vector3(), + position: new Vector3(-.01, -.1, .14), + rotation: new Vector3(76.04, 0, 0), scale: new Vector3(.1, .1, .1), - color: "#CEE", + color: "#CC0000", text: "test", last_seen: new Date(), - parent: this.rightController.id - + parent: id }; switch (state.currentTarget.name) { case "addBox": entity.template = "#box-template"; + this.state = BmenuState.ADDING; break; case "addSphere": entity.template = "#sphere-template"; + this.state = BmenuState.ADDING; break; case "addCylinder": entity.template = "#cylinder-template"; + this.state = BmenuState.ADDING; break; case "doneAdding": this.state = BmenuState.NONE; + break; default: console.log("Unknown button"); return; } - const event: DiagramEvent = { - type: DiagramEventType.ADD, - entity: entity + if (this.state === BmenuState.ADDING) { + const event: DiagramEvent = { + type: DiagramEventType.ADD, + entity: entity + } + DiagramManager.onDiagramEventObservable.notifyObservers(event); + } else { + const event: DiagramEvent = { + type: DiagramEventType.CLEAR + } + DiagramManager.onDiagramEventObservable.notifyObservers(event); } - this.state = BmenuState.ADDING; - DiagramManager.onDiagramEventObservable.notifyObservers(event); + + + } #createDefaultMaterial() { - const myMaterial = new StandardMaterial("myMaterial", this.scene); + const myMaterial = new StandardMaterial("myMaterial", this.scene); myMaterial.diffuseColor = Color3.FromHexString("#CEE"); return myMaterial; }