Cleaned up circular dependencies. Added persistence manager.

This commit is contained in:
Michael Mainguy 2023-07-19 06:50:43 -05:00
parent 8ab433e687
commit afd8040108
16 changed files with 592 additions and 270 deletions

View File

@ -15,7 +15,7 @@
align-content: center; align-content: center;
flex-direction: column; flex-direction: column;
z-index: -1; z-index: -1;
background: url("/loading-loading-forever.gif"); background: url("/spinner.gif");
background-position: center; background-position: center;
background-repeat: no-repeat; background-repeat: no-repeat;
text-align: center; text-align: center;

32
package-lock.json generated
View File

@ -10,10 +10,10 @@
"hasInstallScript": true, "hasInstallScript": true,
"dependencies": { "dependencies": {
"@auth0/auth0-spa-js": "^2.0.8", "@auth0/auth0-spa-js": "^2.0.8",
"@babylonjs/core": "^6.8.0", "@babylonjs/core": "^6.12.3",
"@babylonjs/gui": "^6.9.0", "@babylonjs/gui": "^6.12.3",
"@babylonjs/havok": "^1.0.1", "@babylonjs/havok": "^1.1.0",
"@babylonjs/inspector": "^6.8.0", "@babylonjs/inspector": "^6.12.3",
"@maptiler/client": "^1.5.0", "@maptiler/client": "^1.5.0",
"axios": "^1.4.0", "axios": "^1.4.0",
"express": "^4.18.2", "express": "^4.18.2",
@ -48,14 +48,14 @@
} }
}, },
"node_modules/@babylonjs/core": { "node_modules/@babylonjs/core": {
"version": "6.8.0", "version": "6.12.3",
"resolved": "https://registry.npmjs.org/@babylonjs/core/-/core-6.8.0.tgz", "resolved": "https://registry.npmjs.org/@babylonjs/core/-/core-6.12.3.tgz",
"integrity": "sha512-wTWj9TsnVGqfXt+tXKi7+SIWi4MzBMwIq+jcylRR1qzHTHFfMuKrRRLZJ5jQtpAhcDPI2TOuJ3/NOccPyawlgQ==" "integrity": "sha512-p1di605M2Pa5+YiHbydGJ3PA4nUWSlmu79agL3mcsq7s8zC5VN/HaK1uNSicYI9LhVfPF6bDsVGHqCXxEkRsFQ=="
}, },
"node_modules/@babylonjs/gui": { "node_modules/@babylonjs/gui": {
"version": "6.9.0", "version": "6.12.3",
"resolved": "https://registry.npmjs.org/@babylonjs/gui/-/gui-6.9.0.tgz", "resolved": "https://registry.npmjs.org/@babylonjs/gui/-/gui-6.12.3.tgz",
"integrity": "sha512-RcVorxsj6n2EbwkBJYPBqF88Rybd3OHbx55runbMSehb9rKhc6d2QIiXTi7yI2oBNyuQBsDxx+ky58Rur2UXww==", "integrity": "sha512-Yh9rVWwAymjy23g8dC5PMjeI2c71HWMZ6Lw2G0yjTZS9gf0st2A/OLsv/4ofK51u0fqvHZFBTfFTL5ZOFYNMow==",
"peerDependencies": { "peerDependencies": {
"@babylonjs/core": "^6.0.0" "@babylonjs/core": "^6.0.0"
} }
@ -73,17 +73,17 @@
} }
}, },
"node_modules/@babylonjs/havok": { "node_modules/@babylonjs/havok": {
"version": "1.0.1", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/@babylonjs/havok/-/havok-1.0.1.tgz", "resolved": "https://registry.npmjs.org/@babylonjs/havok/-/havok-1.1.0.tgz",
"integrity": "sha512-J41CIAbL9WOQkPRdMADEVKDKZsU4iOlesBg0C/LP1GPxUncGVylLPkyyGPcQPLd6ifV9cZcnkEJrkiE8xVahTw==", "integrity": "sha512-BNo2d+gfkoCbbEGYOVZgdPWG6NRdo5Tjvd9rpjMs0sWV/EaKQ6kL8hnJUn+HVZx4hdyKrTkq8ThTGFt/nTediA==",
"dependencies": { "dependencies": {
"@types/emscripten": "^1.39.6" "@types/emscripten": "^1.39.6"
} }
}, },
"node_modules/@babylonjs/inspector": { "node_modules/@babylonjs/inspector": {
"version": "6.8.0", "version": "6.12.3",
"resolved": "https://registry.npmjs.org/@babylonjs/inspector/-/inspector-6.8.0.tgz", "resolved": "https://registry.npmjs.org/@babylonjs/inspector/-/inspector-6.12.3.tgz",
"integrity": "sha512-tu3nb0l7xSYXkTaKAnP7zBL34qR9J0op+NzOn4Y1C9RcY91yH3YfLTdoo4pFA0K5NaS99W0nAIjCcrS2BPBmMQ==", "integrity": "sha512-REW+BF4LQhOK0cRRTVfSaOGnjh1LVE1lgwel7BBjjWeRcIjCVBimAprPY8rE0K+EAGMmPlxt9rDIpGoA0PUTqQ==",
"dependencies": { "dependencies": {
"@fortawesome/fontawesome-svg-core": "^6.1.0", "@fortawesome/fontawesome-svg-core": "^6.1.0",
"@fortawesome/free-regular-svg-icons": "^6.0.0", "@fortawesome/free-regular-svg-icons": "^6.0.0",

View File

@ -12,10 +12,10 @@
"postinstall": "cp ./node_modules/@babylonjs/havok/lib/esm/HavokPhysics.wasm ./node_modules/.vite/deps" "postinstall": "cp ./node_modules/@babylonjs/havok/lib/esm/HavokPhysics.wasm ./node_modules/.vite/deps"
}, },
"dependencies": { "dependencies": {
"@babylonjs/core": "^6.8.0", "@babylonjs/core": "^6.12.3",
"@babylonjs/gui": "^6.9.0", "@babylonjs/gui": "^6.12.3",
"@babylonjs/havok": "^1.0.1", "@babylonjs/havok": "^1.1.0",
"@babylonjs/inspector": "^6.8.0", "@babylonjs/inspector": "^6.12.3",
"express": "^4.18.2", "express": "^4.18.2",
"@auth0/auth0-spa-js": "^2.0.8", "@auth0/auth0-spa-js": "^2.0.8",
"ring-client-api": "^11.8.0", "ring-client-api": "^11.8.0",

BIN
public/spinner.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 630 KiB

View File

@ -19,15 +19,16 @@ import {
///import {havokModule} from "./util/havok"; ///import {havokModule} from "./util/havok";
import HavokPhysics from "@babylonjs/havok"; import HavokPhysics from "@babylonjs/havok";
import {Rigplatform} from "./controllers/rigplatform"; import {Rigplatform} from "./controllers/rigplatform";
import {DiagramManager} from "./diagram/diagramManager"; import {DiagramManager} from "./diagram/diagramManager";
class App { export class App {
//preTasks = [havokModule]; //preTasks = [havokModule];
private token: string; private token: string;
public static scene: Scene;
public static xr: WebXRDefaultExperience;
public static rig: Rigplatform;
constructor() { constructor() {
const canvas = document.createElement("canvas"); const canvas = document.createElement("canvas");
canvas.style.width = "100%"; canvas.style.width = "100%";
@ -40,38 +41,46 @@ class App {
} }
async initialize(canvas) { 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 engine = new Engine(canvas, true);
const scene = new Scene(engine); const scene = new Scene(engine);
const diagramManager = new DiagramManager(scene);
App.scene = scene;
const havokInstance = await HavokPhysics(); const havokInstance = await HavokPhysics();
const havokPlugin = new HavokPlugin(true, havokInstance); const havokPlugin = new HavokPlugin(true, havokInstance);
scene.enablePhysics(new Vector3(0, -9.8, 0), havokPlugin); scene.enablePhysics(new Vector3(0, -9.8, 0), havokPlugin);
const camera: ArcRotateCamera = new ArcRotateCamera("Camera", Math.PI / 2, Math.PI / 2, 2, const camera: ArcRotateCamera = new ArcRotateCamera("Camera", Math.PI / 2, Math.PI / 2, 2,
new Vector3(0, 1.6, 0), scene); new Vector3(0, 1.6, 0), scene);
camera.attachControl(canvas, true); camera.attachControl(canvas, true);
new HemisphericLight("light1", new Vector3(1, 1, 0), scene); 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', const photoDome = new PhotoDome('sky',
'./outdoor_field.jpeg', {}, './outdoor_field.jpeg', {},
scene); scene);
const xr = await WebXRDefaultExperience.CreateAsync(scene, { App.xr = await WebXRDefaultExperience.CreateAsync(scene, {
floorMeshes: [this.createGround(scene)], floorMeshes: [this.createGround()],
disableTeleportation: true, disableTeleportation: true,
optionalFeatures: true optionalFeatures: true
}); });
const rig = new Rigplatform(scene, xr); const diagramManager = new DiagramManager(App.scene, App.xr.baseExperience);
//const ring = new Cameras(scene, this.token); App.rig = new Rigplatform(App.scene, App.xr);
//ring.getCameras().then(() => ring.createCameras());
//xr.teleportation.detach();
// hide/show the Inspector
window.addEventListener("keydown", (ev) => { window.addEventListener("keydown", (ev) => {
// Shift+Ctrl+Alt+I // Shift+Ctrl+Alt+I
if (ev.shiftKey && ev.ctrlKey && ev.altKey && ev.keyCode === 73) { if (ev.shiftKey && ev.ctrlKey && ev.altKey && ev.keyCode === 73) {
@ -89,19 +98,19 @@ class App {
}); });
} }
createGround(scene) { createGround() {
const groundMaterial = new PBRMetallicRoughnessMaterial("groundMaterial", scene); const groundMaterial = new PBRMetallicRoughnessMaterial("groundMaterial", App.scene);
const gText = new Texture("./grass1.jpeg", scene); const gText = new Texture("./grass1.jpeg", App.scene);
gText.uScale = 40; gText.uScale = 40;
gText.vScale = 40; gText.vScale = 40;
groundMaterial.baseTexture = gText; groundMaterial.baseTexture = gText;
groundMaterial.metallic = 0; groundMaterial.metallic = 0;
groundMaterial.roughness = 1; 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; ground.material = groundMaterial;
const groundAggregate = new PhysicsAggregate(ground, PhysicsShapeType.BOX, {mass: 0}, scene); new PhysicsAggregate(ground, PhysicsShapeType.BOX, {mass: 0}, App.scene);
return ground; return ground;
} }
} }

View File

@ -1,10 +1,8 @@
import {Vector3, WebXRInputSource} from "@babylonjs/core"; import {Vector3, WebXRInputSource} from "@babylonjs/core";
import {Rigplatform} from "./rigplatform";
export class Base { export class Base {
static stickVector = Vector3.Zero(); static stickVector = Vector3.Zero();
protected controller: WebXRInputSource; protected controller: WebXRInputSource;
protected rig: Rigplatform;
protected speedFactor = 4; protected speedFactor = 4;
constructor(controller: constructor(controller:
@ -22,8 +20,7 @@ export class Base {
} }
}); });
} }
public mesh() {
setRig(rig: Rigplatform) { return this.controller.grip;
this.rig = rig;
} }
} }

View File

@ -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;
}
}
}

View File

@ -1,40 +1,65 @@
import {Vector3, WebXRInputSource} from "@babylonjs/core"; import {Vector3, WebXRInputSource} from "@babylonjs/core";
import {Base} from "./base"; import {Base} from "./base";
import {Controllers} from "./controllers";
export class Left extends Base { export class Left extends Base {
public static instance: Left;
constructor(controller: constructor(controller:
WebXRInputSource) { WebXRInputSource) {
super(controller); super(controller);
Left.instance = this;
this.controller.onMotionControllerInitObservable.add((init) => { this.controller.onMotionControllerInitObservable.add((init) => {
if (init.components['xr-standard-thumbstick']) { if (init.components['xr-standard-thumbstick']) {
init.components['xr-standard-thumbstick'] init.components['xr-standard-thumbstick']
.onAxisValueChangedObservable.add((value) => { .onAxisValueChangedObservable.add((value) => {
if (!Controllers.movable) {
if (Math.abs(value.x) > .1) { this.moveRig(value);
this.rig.leftright(value.x * this.speedFactor);
Base.stickVector.x = 1;
} else { } else {
Base.stickVector.x = 0; this.moveMovable(value);
}
if (Math.abs(value.y) > .1) {
this.rig.updown(value.y * this.speedFactor);
Base.stickVector.y = 1;
} else {
Base.stickVector.y = 0;
} }
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 {
}
}
} }

View File

@ -1,71 +1,160 @@
import {Base} from "./base"; import {Base} from "./base";
import {Vector3, WebXRInputSource} from "@babylonjs/core"; import {Angle, Observable, Vector3, WebXRControllerComponent, WebXRInputSource} from "@babylonjs/core";
import {Bmenu, BmenuState} from "../menus/bmenu"; import {Bmenu} from "../menus/bmenu";
import {DiagramEvent, DiagramEventType, DiagramManager} from "../diagram/diagramManager"; 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 { export class Right extends Base {
private bmenu: Bmenu; private bmenu: Bmenu;
public static instance: Right;
private down: boolean = false; 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: constructor(controller:
WebXRInputSource) { WebXRInputSource) {
super(controller); super(controller);
Right.instance = this;
this.controller.onMotionControllerInitObservable.add((init) => { this.controller.onMotionControllerInitObservable.add((init) => {
const trigger = init.components['xr-standard-trigger']; this.initTrigger(init.components['xr-standard-trigger']);
if (trigger) { this.initBButton(init.components['b-button']);
trigger this.initAButton(init.components['a-button']);
.onButtonStateChangedObservable this.initThumbstick(init.components['xr-standard-thumbstick']);
.add((value) => { this.initGrip(init.components['xr-standard-squeeze']);
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);
}
});
}
}); });
} }
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) { public setBMenu(menu: Bmenu) {
this.bmenu = menu; this.bmenu = menu;
this.bmenu.setController(this.controller); 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;
}
}
} }

View File

@ -17,33 +17,36 @@ import {
import {Right} from "./right"; import {Right} from "./right";
import {Left} from "./left"; import {Left} from "./left";
import {Bmenu} from "../menus/bmenu"; import {Bmenu} from "../menus/bmenu";
import {Hud} from "../information/hud";
import {Controllers} from "./controllers";
export class Rigplatform { export class Rigplatform {
static LINEAR_VELOCITY = 4; static LINEAR_VELOCITY = 4;
static ANGULAR_VELOCITY = 3; static ANGULAR_VELOCITY = 3;
static x90 = Quaternion.RotationAxis(Vector3.Up(), 1.5708); static x90 = Quaternion.RotationAxis(Vector3.Up(), 1.5708);
public bMenu: Bmenu; public bMenu: Bmenu;
private scene: Scene;
public static instance: Rigplatform;
private static xr: WebXRDefaultExperience;
private yRotation: number = 0; private yRotation: number = 0;
public right: Right;
public left: Left;
public body: PhysicsBody; public body: PhysicsBody;
public rigMesh: Mesh; public rigMesh: Mesh;
private camera: Camera; private camera: Camera;
private scene: Scene;
private xr: WebXRDefaultExperience;
private turning: boolean = false; private turning: boolean = false;
constructor(scene: Scene, xr: WebXRDefaultExperience) { constructor(scene: Scene, xr: WebXRDefaultExperience) {
this.xr = xr;
this.bMenu = new Bmenu(scene, this.xr.baseExperience);
this.camera = scene.activeCamera;
this.scene = scene; this.scene = scene;
Rigplatform.xr = xr;
Rigplatform.instance = this;
this.rigMesh = MeshBuilder.CreateCylinder("platform", {diameter: 1.5, height: .01}, scene); this.bMenu = new Bmenu(scene, xr.baseExperience);
this.camera = scene.activeCamera;
for (const cam of this.scene.cameras) { 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.parent = this.rigMesh;
cam.position = new Vector3(0, 1.6, 0);
} }
const myMaterial = new StandardMaterial("myMaterial", scene); const myMaterial = new StandardMaterial("myMaterial", scene);
@ -55,31 +58,28 @@ export class Rigplatform {
new PhysicsAggregate( new PhysicsAggregate(
this.rigMesh, this.rigMesh,
PhysicsShapeType.CYLINDER, 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); scene);
rigAggregate.body.setMotionType(PhysicsMotionType.ANIMATED); rigAggregate.body.setMotionType(PhysicsMotionType.DYNAMIC);
rigAggregate.body.setGravityFactor(0); rigAggregate.body.setGravityFactor(.001);
this.#fixRotation(); this.#fixRotation();
this.body = rigAggregate.body; this.body = rigAggregate.body;
this.#setupKeyboard(); this.#setupKeyboard();
this.#initializeControllers(); this.#initializeControllers();
this.scene.onActiveCameraChanged.add((s) => { scene.onActiveCameraChanged.add((s) => {
this.camera = s.activeCamera; this.camera = s.activeCamera;
this.camera.parent = this.rigMesh; this.camera.parent = this.rigMesh;
console.log('camera changed');
}); });
} }
public forwardback(val: number) { public forwardback(val: number) {
const ray = this.camera.getForwardRay(); const ray = this.camera.getForwardRay();
this.body.setLinearVelocity(ray.direction.scale(val * -1)); this.body.setLinearVelocity(ray.direction.scale(val * -1));
} }
public leftright(val: number) { public leftright(val: number) {
const ray = this.camera.getForwardRay(); const ray = this.camera.getForwardRay();
const direction = ray.direction.applyRotationQuaternion(Rigplatform.x90).scale(val); const direction = ray.direction.applyRotationQuaternion(Rigplatform.x90).scale(val);
this.body.setLinearVelocity(direction); this.body.setLinearVelocity(direction);
@ -125,28 +125,46 @@ export class Rigplatform {
} }
#initializeControllers() { #initializeControllers() {
this.xr.input.onControllerAddedObservable.add((source, state) => { Rigplatform.xr.input.onControllerAddedObservable.add((source) => {
let controller; let controller;
switch (source.inputSource.handedness) { switch (source.inputSource.handedness) {
case "right": case "right":
controller = new Right(source); Right.instance = new Right(source);
this.right = controller; Right.instance.setBMenu(this.bMenu);
controller.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; break;
case "left": case "left":
controller = new Left(source); Left.instance = new Left(source);
this.left = controller;
break; 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) { if (controller) {
controller.setRig(this); controller.setRig(this);
} }
console.log(source);
console.log(state);
}); });
} }
//create a method to set the camera to the rig //create a method to set the camera to the rig
@ -181,7 +199,7 @@ export class Rigplatform {
this.updown(1 * Rigplatform.LINEAR_VELOCITY); this.updown(1 * Rigplatform.LINEAR_VELOCITY);
break; break;
case " ": case " ":
this.bMenu.toggle() this.bMenu.toggle(this.rigMesh)
} }
}); });
@ -197,6 +215,7 @@ export class Rigplatform {
#fixRotation() { #fixRotation() {
this.scene.registerBeforeRender(() => { this.scene.registerBeforeRender(() => {
const q = this.rigMesh.rotationQuaternion; const q = this.rigMesh.rotationQuaternion;
this.body.setAngularVelocity(Vector3.Zero());
const e = q.toEulerAngles(); const e = q.toEulerAngles();
e.y += this.yRotation; e.y += this.yRotation;
q.copyFrom(Quaternion.FromEulerAngles(0, e.y, 0)); q.copyFrom(Quaternion.FromEulerAngles(0, e.y, 0));

View File

@ -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;
}

View File

@ -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 {v4 as uuidv4} from 'uuid';
import {BmenuState} from "../menus/bmenu"; import {DiagramEntity, DiagramEvent, DiagramEventType} from "./diagramEntity";
import {PersistenceManager} from "./persistenceManager";
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;
}
export class DiagramManager { export class DiagramManager {
private persistenceManager: PersistenceManager = new PersistenceManager();
static onDiagramEventObservable = new Observable(); static onDiagramEventObservable = new Observable();
static leftController: Mesh; private scene: Scene;
private xr: WebXRExperienceHelper;
static currentMesh: AbstractMesh; 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.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()) { if (DiagramManager.onDiagramEventObservable.hasObservers()) {
} else { } else {
DiagramManager.onDiagramEventObservable.add(this.#onDiagramEvent, -1, true, this); 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) { #onDiagramEvent(event: DiagramEvent) {
console.log(event);
const entity = event.entity; const entity = event.entity;
let mesh; let mesh;
let material let material
if (entity) { if (entity) {
mesh = this.scene.getMeshByName(entity.id); mesh = this.scene.getMeshByName(entity.id);
material = this.scene.getMaterialByName("material-" + entity.id); if (mesh) {
if (!material) { material = mesh.material;
material = new StandardMaterial("material-" + event.entity.id, this.scene);
material.ambientColor = Color3.FromHexString(event.entity.color.replace("#", ""));
} }
} }
switch (event.type) { switch (event.type) {
case DiagramEventType.CLEAR:
DiagramManager.currentMesh.dispose();
DiagramManager.currentMesh = null;
break;
case DiagramEventType.DROPPED: case DiagramEventType.DROPPED:
break; break;
case DiagramEventType.DROP: case DiagramEventType.DROP:
if (DiagramManager.currentMesh) { 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.setParent(null);
DiagramManager.currentMesh = newMesh; DiagramManager.currentMesh = newMesh;
DiagramManager.onDiagramEventObservable.notifyObservers({ DiagramManager.onDiagramEventObservable.notifyObservers({
@ -81,44 +74,51 @@ export class DiagramManager {
} }
break; break;
case DiagramEventType.ADD: case DiagramEventType.ADD:
if (DiagramManager.currentMesh){ if (DiagramManager.currentMesh) {
DiagramManager.currentMesh.dispose(); DiagramManager.currentMesh.dispose();
} }
if (mesh) { if (mesh) {
return; return;
} else { } else {
mesh = this.#createMesh(entity); 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) { if (!mesh) {
return; return;
} }
} }
DiagramManager.currentMesh = mesh;
break;
case DiagramEventType.MODIFY: case DiagramEventType.MODIFY:
if (!mesh) { if (!mesh) {
} else { } else {
const rotation = entity.rotation; if (!material) {
const scale = entity.scale; material = new StandardMaterial("material-" + event.entity.id, this.scene);
const position = entity.position; material.diffuseColor = Color3.FromHexString(event.entity.color);
if (mesh) {
mesh.material = material;
}
}
mesh.material = material; mesh.material = material;
mesh.position = new Vector3(position.x, position.y, position.z); mesh.position = entity.position;
mesh.rotation = new Vector3(rotation.x, rotation.y, rotation.z); mesh.rotation = entity.rotation;
if (entity.parent) { if (entity.parent) {
mesh.parent = this.scene.getMeshByName(entity.parent); mesh.parent = this.scene.getMeshByName(entity.parent);
} else {
} }
} }
DiagramManager.currentMesh = mesh; DiagramManager.currentMesh = mesh;
break; break;
case DiagramEventType.REMOVE: case DiagramEventType.REMOVE:
break; break;
} }
} }
#createMesh(entity: DiagramEntity) { #createMesh(entity: DiagramEntity) {
@ -130,28 +130,41 @@ export class DiagramManager {
case "#box-template": case "#box-template":
mesh = MeshBuilder.CreateBox(entity.id, mesh = MeshBuilder.CreateBox(entity.id,
{ {
width: entity.scale.x, width: 1,
height: entity.scale.y, height: 1,
depth: entity.scale.z depth: 1
}, this.scene); }, this.scene);
break; break;
case "#sphere-template": case "#sphere-template":
mesh = MeshBuilder.CreateSphere(entity.id, {diameter: 1}, this.scene);
mesh = MeshBuilder.CreateSphere(entity.id, {diameter: entity.scale.x}, this.scene);
break break
case "#cylinder-template": case "#cylinder-template":
mesh = MeshBuilder.CreateCylinder(entity.id, { mesh = MeshBuilder.CreateCylinder(entity.id, {
diameter: entity.scale.x, diameter: 1,
height: entity.scale.y height: 1
}, this.scene); }, this.scene);
break; break;
default: default:
mesh = null; 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; return mesh;
} }
} }

View File

@ -0,0 +1,48 @@
import {AbstractMesh, Mesh, Observable, StandardMaterial, Vector3} from "@babylonjs/core";
import {DiagramEntity} from "./diagramEntity";
export class PersistenceManager {
public updateObserver: Observable<DiagramEntity> = new Observable<DiagramEntity>();
constructor() {
}
public add(mesh: AbstractMesh) {
const entity = <any>{};
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 = <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);
}
}

56
src/information/hud.ts Normal file
View File

@ -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)})`;
}
}

6
src/menus/MenuState.ts Normal file
View File

@ -0,0 +1,6 @@
export enum BmenuState {
NONE,
ADDING, // Adding a new entity
DROPPING, // Dropping an entity
}

View File

@ -1,43 +1,36 @@
import { import {
AbstractMesh, AbstractMesh,
Angle,
Color3, Color3,
Scene, Scene,
StandardMaterial, StandardMaterial,
TransformNode,
Vector3, Vector3,
WebXRExperienceHelper, WebXRExperienceHelper,
WebXRInputSource WebXRInputSource
} from "@babylonjs/core"; } from "@babylonjs/core";
import {GUI3DManager, HolographicButton, PlanePanel} from "@babylonjs/gui"; import {GUI3DManager, NearMenu, TouchHolographicButton} from "@babylonjs/gui";
import {DiagramEntity, DiagramEvent, DiagramEventType, DiagramManager} from "../diagram/diagramManager"; 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 { export class Bmenu {
private readonly scene;
private state: BmenuState = BmenuState.NONE; private state: BmenuState = BmenuState.NONE;
private manager: GUI3DManager;
private scene: Scene;
private xr;
private manager;
private panel;
private rightController: AbstractMesh; private rightController: AbstractMesh;
private xr: WebXRExperienceHelper;
constructor(scene: Scene, xr: WebXRExperienceHelper) { constructor(scene: Scene, xr: WebXRExperienceHelper) {
this.scene = scene; this.scene = scene;
this.manager = new GUI3DManager(scene);
this.xr = xr; this.xr = xr;
DiagramManager.onDiagramEventObservable.add((event: DiagramEvent) => { DiagramManager.onDiagramEventObservable.add((event: DiagramEvent) => {
if (event.type === DiagramEventType.DROPPED) { if (event.type === DiagramEventType.DROPPED) {
this.state = BmenuState.ADDING; this.state = BmenuState.ADDING;
} }
}); });
} }
setController(controller: WebXRInputSource) { setController(controller: WebXRInputSource) {
@ -45,7 +38,7 @@ export class Bmenu {
} }
makeButton(name: string, id: string) { makeButton(name: string, id: string) {
const button = new HolographicButton(name); const button = new TouchHolographicButton(name);
button.text = name; button.text = name;
button.name = id; button.name = id;
button.onPointerClickObservable.add(this.#clickhandler, -1, false, this); button.onPointerClickObservable.add(this.#clickhandler, -1, false, this);
@ -59,76 +52,94 @@ export class Bmenu {
public setState(state: BmenuState) { public setState(state: BmenuState) {
this.state = state; this.state = state;
} }
toggle(mesh: AbstractMesh) {
toggle() { console.log(mesh.name);
if (this.panel) { if (this.manager) {
this.panel.dispose(); this.manager.dispose();
this.panel = null; this.manager = null;
} else { } else {
const anchor = new TransformNode("bMenuAnchor"); this.manager = new GUI3DManager(this.scene);
anchor.rotation.y = Angle.FromDegrees(180).radians(); const panel = new NearMenu();
const cam = this.xr.camera.getFrontPosition(1);
cam.y = cam.y - .5;
anchor.position = cam;
const panel = new PlanePanel();
panel.margin = .06;
this.manager.addControl(panel); this.manager.addControl(panel);
panel.linkToTransformNode(anchor); const follower = panel.defaultBehavior.followBehavior;
panel.addControl(this.makeButton("Add Box", "addBox")); follower.maxViewHorizontalDegrees = 45;
panel.addControl(this.makeButton("Add Sphere", "addSphere")); follower.useFixedVerticalOffset = true;
panel.addControl(this.makeButton("Add Cylinder", "addCylinder")); follower.fixedVerticalOffset = 1;
panel.addControl(this.makeButton("Done Adding", "doneAdding")); follower.defaultDistance = 2;
for (const control of panel.children) { follower.maximumDistance = 3;
control.scaling = new Vector3(.1, .1, .1); follower.minimumDistance = 1;
}
this.panel = panel;
}
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) { #clickhandler(_info, state) {
console.log(state.currentTarget.name); console.log(state.currentTarget.name);
const id = this?.rightController?.id || null;
let entity: DiagramEntity = { let entity: DiagramEntity = {
template: null, template: null,
position: new Vector3(0, -.040, .13), position: new Vector3(-.01, -.1, .14),
rotation: new Vector3(), rotation: new Vector3(76.04, 0, 0),
scale: new Vector3(.1, .1, .1), scale: new Vector3(.1, .1, .1),
color: "#CEE", color: "#CC0000",
text: "test", text: "test",
last_seen: new Date(), last_seen: new Date(),
parent: this.rightController.id parent: id
}; };
switch (state.currentTarget.name) { switch (state.currentTarget.name) {
case "addBox": case "addBox":
entity.template = "#box-template"; entity.template = "#box-template";
this.state = BmenuState.ADDING;
break; break;
case "addSphere": case "addSphere":
entity.template = "#sphere-template"; entity.template = "#sphere-template";
this.state = BmenuState.ADDING;
break; break;
case "addCylinder": case "addCylinder":
entity.template = "#cylinder-template"; entity.template = "#cylinder-template";
this.state = BmenuState.ADDING;
break; break;
case "doneAdding": case "doneAdding":
this.state = BmenuState.NONE; this.state = BmenuState.NONE;
break; break;
default: default:
console.log("Unknown button"); console.log("Unknown button");
return; return;
} }
const event: DiagramEvent = { if (this.state === BmenuState.ADDING) {
type: DiagramEventType.ADD, const event: DiagramEvent = {
entity: entity 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() { #createDefaultMaterial() {
const myMaterial = new StandardMaterial("myMaterial", this.scene); const myMaterial = new StandardMaterial("myMaterial", this.scene);
myMaterial.diffuseColor = Color3.FromHexString("#CEE"); myMaterial.diffuseColor = Color3.FromHexString("#CEE");
return myMaterial; return myMaterial;
} }