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

32
package-lock.json generated
View File

@ -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",

View File

@ -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",

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

View File

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

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 {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 (!Controllers.movable) {
this.moveRig(value);
} else {
this.moveMovable(value);
}
});
}
});
}
private moveMovable(value: { x: number, y: number }) {
if (Math.abs(value.x) > .1) {
this.rig.leftright(value.x * this.speedFactor);
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) {
this.rig.updown(value.y * this.speedFactor);
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())) {
this.rig.updown(0);
this.rig.leftright(0)
Controllers.controllerObserver.notifyObservers({type: 'leftright', value: 0});
Controllers.controllerObserver.notifyObservers({type: 'updown', value: 0});
} else {
}
});
}
});
}
}

View File

@ -1,17 +1,30 @@
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;
constructor(controller:
WebXRInputSource) {
super(controller);
this.controller.onMotionControllerInitObservable.add((init) => {
const trigger = init.components['xr-standard-trigger'];
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
@ -32,40 +45,116 @@ export class Right extends Base {
}
});
}
if (init.components['b-button']) {
init.components['b-button'].onButtonStateChangedObservable.add((value) => {
}
private initAButton(abutton: WebXRControllerComponent) {
if (abutton) {
abutton.onButtonStateChangedObservable.add((value) => {
if (value.pressed) {
this.bmenu.toggle();
if (DiagramManager.currentMesh) {
if (Controllers.movable) {
Controllers.movable = null;
} else {
Controllers.movable = DiagramManager.currentMesh;
}
}
}
});
}
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);
}
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) {
this.rig.forwardback(value.y * this.speedFactor);
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())) {
this.rig.forwardback(0);
Controllers.controllerObserver.notifyObservers({type: 'forwardback', value: 0});
}
});
}
});
}
constructor(controller:
WebXRInputSource) {
super(controller);
Right.instance = this;
this.controller.onMotionControllerInitObservable.add((init) => {
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;
}
}
}

View File

@ -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 "left":
controller = new Left(source);
this.left = controller;
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;
}
this.xr.baseExperience.camera.position = new Vector3(0, 1.6, 0);
});
break;
case "left":
Left.instance = new Left(source);
break;
}
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));

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 {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({
@ -84,41 +77,48 @@ export class DiagramManager {
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.position = new Vector3(position.x, position.y, position.z);
mesh.rotation = new Vector3(rotation.x, rotation.y, rotation.z);
}
}
mesh.material = material;
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;
}
}

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 {
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,72 +52,90 @@ 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;
}
if (this.state === BmenuState.ADDING) {
const event: DiagramEvent = {
type: DiagramEventType.ADD,
entity: entity
}
this.state = BmenuState.ADDING;
DiagramManager.onDiagramEventObservable.notifyObservers(event);
} else {
const event: DiagramEvent = {
type: DiagramEventType.CLEAR
}
DiagramManager.onDiagramEventObservable.notifyObservers(event);
}
}
#createDefaultMaterial() {