Refactored physics to reduce duplicate code.

This commit is contained in:
Michael Mainguy 2023-08-01 13:53:55 -05:00
parent 89623b5007
commit 8412b6f378
7 changed files with 290 additions and 977 deletions

891
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,7 @@
{
"name": "immersive",
"private": true,
"version": "0.0.0",
"version": "0.0.1",
"type": "module",
"scripts": {
"dev": "vite",
@ -16,19 +16,12 @@
"@babylonjs/gui": "^6.14.0",
"@babylonjs/havok": "1.1.1",
"@babylonjs/inspector": "^6.14.0",
"express": "^4.18.2",
"@auth0/auth0-spa-js": "^2.0.8",
"ring-client-api": "^11.8.0",
"@maptiler/client": "^1.5.0",
"axios": "^1.4.0",
"ring-client-api": "11.7.7",
"dexie": "^3.2.4",
"dexie-observable": "^4.0.1-beta.13",
"google-static-maps-tile": "1.0.0",
"query-string": "^8.1.0",
"vite-express": "^0.9.1",
"loglevel": "^1.8.1",
"round": "^2.0.1",
"express-http-proxy": "^1.6.3",
"earcut": "^2.2.4",
"uuid": "^9.0.0"
},

View File

@ -13,7 +13,6 @@ import {MeshConverter} from "../diagram/meshConverter";
import {DiagramManager} from "../diagram/diagramManager";
import {DiagramEvent, DiagramEventType} from "../diagram/diagramEntity";
import log from "loglevel";
import {AppConfig} from "../util/appConfig";
import {Controllers} from "./controllers";
@ -135,10 +134,8 @@ export class Base {
if ("toolbox" != mesh?.parent?.parent?.id) {
if (mesh.physicsBody) {
const transformNode = this.setupTransformNode(mesh);
mesh.physicsBody.setMotionType(PhysicsMotionType.ANIMATED);
//mesh.setParent(transformNode);
this.grabbedMeshParentId = transformNode.id;
} else {
mesh.setParent(this.controller.motionController.rootMesh);
@ -194,29 +191,17 @@ export class Base {
log.getLogger("Base").warn("Base", "Have not implemented snapping to parent yet");
//@note: this is not implemented yet
} else {
//mesh.setParent(null);
this.applyTransform(mesh)
mesh?.physicsBody?.setMotionType(PhysicsMotionType.DYNAMIC);
mesh.setParent(null);
}
} else {
const parent = this.scene.getTransformNodeById(this.grabbedMeshParentId);
if (parent) {
this.applyTransform(parent);
this.grabbedMeshParentId = null;
parent.dispose();
}
}
}
private applyTransform(mesh: TransformNode) {
const config = AppConfig.config;
const snappedRotation = config.snapRotateVal(mesh.absoluteRotationQuaternion.toEulerAngles().clone());
const snappedPosition = config.snapGridVal(mesh.absolutePosition.clone());
mesh.rotation = snappedRotation;
mesh.position = snappedPosition;
}
private drop() {
const mesh = this.grabbedMesh;
if (!mesh) {
@ -225,6 +210,7 @@ export class Base {
if (this.handleGrabbed(mesh)) {
return;
}
this.reparent(mesh);
this.previousParentId = null;
this.previousScaling = null;

View File

@ -8,7 +8,9 @@ import {
MeshBuilder,
PhysicsAggregate,
PhysicsBody,
PhysicsMotionType,
PhysicsShapeType,
Quaternion,
Scene,
StandardMaterial
} from "@babylonjs/core";
@ -79,7 +81,12 @@ export class MeshConverter {
}
if (entity.rotation) {
mesh.rotation = entity.rotation;
if (mesh.rotationQuaternion) {
mesh.rotationQuaternion = Quaternion.FromEulerAngles(entity.rotation.x, entity.rotation.y, entity.rotation.z);
} else {
mesh.rotation = entity.rotation;
}
}
if (entity.parent) {
mesh.parent = scene.getNodeById(entity.parent);
@ -119,7 +126,7 @@ export class MeshConverter {
return null;
}
if (mesh.physicsBody) {
return mesh.physicsBody;
mesh.physicsBody.dispose();
}
let shapeType = PhysicsShapeType.BOX;
switch (mesh.metadata.template) {
@ -136,10 +143,11 @@ export class MeshConverter {
}
const aggregate = new PhysicsAggregate(mesh,
shapeType, {mass: 20, restitution: .2, friction: .9}, scene);
aggregate.body.setLinearDamping(.9);
aggregate.body.setAngularDamping(.5);
const body = aggregate.body;
body.setMotionType(PhysicsMotionType.ANIMATED);
body.setLinearDamping(.9);
body.setAngularDamping(.5);
body.setGravityFactor(0);
return aggregate.body;
}

View File

@ -1,46 +1,47 @@
import {AbstractMesh, Angle, MeshBuilder, Scene, WebXRExperienceHelper} from "@babylonjs/core";
import {AdvancedDynamicTexture, InputText} from "@babylonjs/gui";
import {Right} from "../controllers/right";
import {Left} from "../controllers/left";
import {Observable, WebXRSessionManager} from "@babylonjs/core";
import log from "loglevel";
export type TextEvent = {
text: string;
}
export class InputTextView {
private mesh: AbstractMesh;
private readonly scene: Scene;
private xr: WebXRExperienceHelper;
private inputPlane: AbstractMesh;
private inputText: InputText;
constructor(scene: Scene, xr: WebXRExperienceHelper, mesh: AbstractMesh ) {
this.scene = scene;
this.xr = xr;
this.mesh = mesh;
public readonly onTextObservable: Observable<TextEvent> = new Observable<TextEvent>();
private text: string;
private xrSession: WebXRSessionManager;
constructor(xrSession: WebXRSessionManager, text: string) {
this.xrSession = xrSession;
this.text = text;
}
public async show(text: string) {
this.inputPlane = MeshBuilder.CreatePlane("myPlane", {width: 1, height: .125}, this.scene);
const pos = this.mesh.absolutePosition;
pos.y += .2;
this.inputPlane.position= pos;
this.inputPlane.rotation.y = Angle.FromDegrees(180).radians();
const textDisplayTexture = AdvancedDynamicTexture.CreateForMesh(this.inputPlane, 1024, 128);
this.inputPlane.material.backFaceCulling = false;
this.inputText = this.createInputText();
this.inputText.text = text;
textDisplayTexture.addControl(this.inputText);
}
private createInputText(): InputText {
const inputText = new InputText("input");
inputText.color= "white";
inputText.background = "black";
inputText.height= "128px";
inputText.width= "1024px";
inputText.maxWidth= "1024px";
inputText.margin="0px";
inputText.fontSize= "48px";
return inputText;
}
public async dispose() {
this.inputPlane.dispose(false, true);
this.inputPlane = null;
this.inputText = null;
}
public async updateText(text: string) {
this.inputText.text = text;
public show() {
const textInput = document.createElement("input");
textInput.type = "text";
document.body.appendChild(textInput);
textInput.value = this.text;
if (this.xrSession.inXRSession) {
Right.instance.disable();
Left.instance.disable();
}
textInput.focus();
if (navigator.userAgent.indexOf('Macintosh') > -1) {
textInput.addEventListener('input', (event) => {
log.debug(event);
});
} else {
textInput.addEventListener('blur', () => {
log.getLogger('bmenu').debug("blur");
this.onTextObservable.notifyObservers({text: textInput.value});
Right.instance.enable();
Left.instance.enable();
textInput.blur();
textInput.remove();
});
}
}
}

View File

@ -1,46 +1,57 @@
import {Angle, Color3, MeshBuilder, Scene, StandardMaterial, Texture} from "@babylonjs/core";
import axios from "axios";
import {
Angle,
Color3,
MeshBuilder,
Scene,
StandardMaterial,
Texture,
Vector3,
WebXRSessionManager
} from "@babylonjs/core";
import log from "loglevel";
export class Cameras {
private readonly scene: Scene;
private token: string;
private cameras;
private cameratextures = new Array<Texture>();
private readonly logger = log.getLogger('bmenu');
constructor(scene: Scene, token: string) {
private xrSession: WebXRSessionManager;
private startPosition = new Vector3(0, 0, 0);
constructor(scene: Scene, xrSession: WebXRSessionManager) {
this.scene = scene;
this.token = token;
this.xrSession = xrSession;
}
public async getCameras() {
this.cameras = await axios.get('https://local.immersiveidea.com/api/cameras');
public createCameras(position: Vector3) {
this.startPosition = position;
this.getCameras();
}
public createCameras() {
this.createCamera(12333524, 0);
this.createCamera(115860395, 1);
this.createCamera(115855810, 2);
this.createCamera(99677736, 3);
this.createCamera(48497021, 4);
this.createCamera(55870327, 5);
private getCameras() {
}
public createCamera(id, index) {
private async createCamera() {
const width = 1.6;
const height = .9
const plane = MeshBuilder.CreatePlane("plane", {width: width, height: height}, this.scene);
const materialPlane = new StandardMaterial("texturePlane", this.scene);
const imageText = new Texture("https://local.immersiveidea.com/api/cameras?id=" + id, this.scene);
//const photo = []
//await cam.getSnapshot();
//const textureBlob = new Blob([photo], {type: 'image/jpeg'});
//const textureUrl = URL.createObjectURL(textureBlob);
const imageText = new Texture("", this.scene);
materialPlane.diffuseTexture = new Texture("https://local.immersiveidea.com/api/cameras?id=" + id, this.scene);
materialPlane.diffuseTexture = imageText;
materialPlane.specularColor = new Color3(0, 0, 0);
materialPlane.backFaceCulling = false;
plane.material = materialPlane;
plane.rotation.y = Angle.FromDegrees(180).radians();
plane.position.y = height / 2 + .2;
plane.position.z = -3;
plane.position.x = (width * 3) - (index * width);
this.cameratextures.push(imageText);
plane.position.x = (width * 3) + this.startPosition.x;
this.startPosition.x += 3;
}
}

View File

@ -14,9 +14,8 @@ import {DiagramEvent, DiagramEventType} from "../diagram/diagramEntity";
import {MeshConverter} from "../diagram/meshConverter";
import log from "loglevel";
import {InputTextView} from "../information/inputTextView";
import {Right} from "../controllers/right";
import {Left} from "../controllers/left";
import {DiaSounds} from "../util/diaSounds";
import {Cameras} from "../integration/ring/cameras";
export class EditMenu {
private state: BmenuState = BmenuState.NONE;
@ -45,115 +44,43 @@ export class EditMenu {
case PointerEventTypes.POINTERPICK:
if (pointerInfo.pickInfo?.pickedMesh?.metadata?.template &&
pointerInfo.pickInfo?.pickedMesh?.parent?.parent?.id != "toolbox") {
this.cleanup()
.then(() => {
log.getLogger("bmenu").debug("cleaned up");
})
.catch((e) => {
log.getLogger("bmenu").error(e);
});
this.handleEventStateAction(pointerInfo).then(() => {
log.getLogger("bmenu").debug("handled");
}).catch((e) => {
log.getLogger("bmenu").error(e);
});
break;
}
}
});
}
private async handleEventStateAction(pointerInfo: PointerInfo) {
switch (this.state) {
case BmenuState.REMOVING:
log.debug("removing " + pointerInfo.pickInfo.pickedMesh.id);
const event: DiagramEvent = {
type: DiagramEventType.REMOVE,
entity:
MeshConverter.toDiagramEntity(pointerInfo.pickInfo.pickedMesh)
}
this.diagramManager.onDiagramEventObservable.notifyObservers(event);
break;
case BmenuState.MODIFYING:
if (pointerInfo.pickInfo.pickedMesh.metadata?.template &&
pointerInfo.pickInfo.pickedMesh.parent?.parent?.id != "toolbox") {
if (this.gizmoManager.gizmos.boundingBoxGizmo.attachedMesh?.id == pointerInfo.pickInfo?.pickedMesh?.id) {
this.gizmoManager.gizmos.boundingBoxGizmo.attachedMesh = null;
} else {
const mesh = pointerInfo.pickInfo.pickedMesh;
this.gizmoManager.attachToMesh(mesh);
toggle() {
if (this.manager) {
DiaSounds.instance.exit.play();
this.manager.dispose();
this.manager = null;
this.gizmoManager.gizmos.boundingBoxGizmo.onScaleBoxDragObservable.add(() => {
this.diagramManager.onDiagramEventObservable.notifyObservers({
type: DiagramEventType.MODIFY,
entity: MeshConverter.toDiagramEntity(mesh),
}
)
log.debug(mesh.scaling);
});
}
}
break;
case BmenuState.LABELING:
const mesh = pointerInfo.pickInfo.pickedMesh;
log.debug("labeling " + mesh.id);
const textInput = document.createElement("input");
textInput.type = "text";
document.body.appendChild(textInput);
if (mesh?.metadata?.text) {
textInput.value = mesh.metadata.text;
} else {
textInput.value = "";
}
if (this.xr.sessionManager.inXRSession) {
Right.instance.disable();
Left.instance.disable();
}
textInput.focus();
} else {
DiaSounds.instance.enter.play();
this.manager = new GUI3DManager(this.scene);
const panel = new StackPanel3D();
this.manager.addControl(panel);
panel.addControl(this.makeButton("Modify", "modify"));
panel.addControl(this.makeButton("Remove", "remove"));
panel.addControl(this.makeButton("Add Label", "label"));
panel.addControl(this.makeButton("Add Ring Cameras", "addRingCameras"));
this.manager.controlScaling = .5;
const offset = new Vector3(0, -.2, 3);
offset.applyRotationQuaternionInPlace(this.scene.activeCamera.absoluteRotation);
panel.node.position =
this.scene.activeCamera.globalPosition.add(offset);
panel.node.lookAt(this.scene.activeCamera.globalPosition);
panel.node.rotation.y = panel.node.rotation.y + Math.PI;
if (navigator.userAgent.indexOf('Macintosh') > -1) {
textInput.addEventListener('input', (event) => {
log.debug(event);
});
const textView = new InputTextView(this.scene, this.xr, mesh)
await textView.show(textInput.value);
textInput.addEventListener('keydown', (event) => {
if (event.key == "Enter") {
log.getLogger('bmenu').debug("enter");
MeshConverter.updateTextNode(mesh, textInput.value);
this.persist(mesh, textInput.value);
this.cleanup();
} else {
textView.updateText(textInput.value);
MeshConverter.updateTextNode(mesh, textInput.value);
}
});
this.textView = textView;
} else {
textInput.addEventListener('blur', () => {
log.getLogger('bmenu').debug("blur");
MeshConverter.updateTextNode(mesh, textInput.value);
this.persist(mesh, textInput.value);
this.cleanup();
Right.instance.enable();
Left.instance.enable();
});
}
this.textInput = textInput;
break;
}
}
private async cleanup() {
if (this.textInput) {
this.textInput.blur();
this.textInput.remove();
}
this.textInput = null;
this.textView && await this.textView.dispose();
this.textView = null;
}
private persist(mesh: AbstractMesh, text: string) {
if (mesh.metadata) {
mesh.metadata.text = text;
@ -178,29 +105,54 @@ export class EditMenu {
return button;
}
toggle() {
//console.log(mesh.name);
if (this.manager) {
DiaSounds.instance.exit.play();
this.manager.dispose();
this.manager = null;
} else {
DiaSounds.instance.enter.play();
this.manager = new GUI3DManager(this.scene);
const panel = new StackPanel3D();
this.manager.addControl(panel);
panel.addControl(this.makeButton("Modify", "modify"));
panel.addControl(this.makeButton("Remove", "remove"));
panel.addControl(this.makeButton("Add Label", "label"));
this.manager.controlScaling = .5;
const offset = new Vector3(0, -.2, 3);
offset.applyRotationQuaternionInPlace(this.scene.activeCamera.absoluteRotation);
panel.node.position =
this.scene.activeCamera.globalPosition.add(offset);
panel.node.lookAt(this.scene.activeCamera.globalPosition);
panel.node.rotation.y = panel.node.rotation.y + Math.PI;
private async handleEventStateAction(pointerInfo: PointerInfo) {
const mesh = pointerInfo.pickInfo.pickedMesh;
if (!mesh) {
log.warn("no mesh");
return;
}
switch (this.state) {
case BmenuState.REMOVING:
log.debug("removing " + mesh?.id);
const event: DiagramEvent = {
type: DiagramEventType.REMOVE,
entity:
MeshConverter.toDiagramEntity(pointerInfo.pickInfo.pickedMesh)
}
this.diagramManager.onDiagramEventObservable.notifyObservers(event);
break;
case BmenuState.MODIFYING:
if (mesh.metadata?.template &&
mesh.parent?.parent?.id != "toolbox") {
if (this.gizmoManager.gizmos.boundingBoxGizmo.attachedMesh?.id == mesh.id) {
this.gizmoManager.gizmos.boundingBoxGizmo.attachedMesh = null;
} else {
this.gizmoManager.attachToMesh(mesh);
this.gizmoManager.gizmos.boundingBoxGizmo.onScaleBoxDragObservable.add(() => {
this.diagramManager.onDiagramEventObservable.notifyObservers({
type: DiagramEventType.MODIFY,
entity: MeshConverter.toDiagramEntity(mesh),
}
)
log.debug(mesh.scaling);
});
}
}
break;
case BmenuState.LABELING:
log.debug("labeling " + mesh.id);
let text = "";
if (mesh?.metadata?.text) {
text = mesh.metadata.text;
}
const textInput = new InputTextView(this.xr.sessionManager, text);
textInput.show();
textInput.onTextObservable.addOnce((value) => {
this.persist(mesh, value.text);
MeshConverter.updateTextNode(mesh, value.text);
});
break;
}
}
@ -215,6 +167,9 @@ export class EditMenu {
case "label":
this.state = BmenuState.LABELING;
break;
case "addRingCameras":
const cameras = new Cameras(this.scene, this.xr.sessionManager);
break;
default:
log.error("Unknown button");
return;