Fixed grid and rotation snapping to be more sensible.

This commit is contained in:
Michael Mainguy 2024-04-20 17:22:14 -05:00
parent 4b06cb2679
commit 81c61fc6f8
7 changed files with 93 additions and 81 deletions

78
package-lock.json generated
View File

@ -8,14 +8,14 @@
"name": "immersive",
"version": "0.0.1",
"dependencies": {
"@babylonjs/core": "^7.1.0",
"@babylonjs/gui": "^7.1.0",
"@babylonjs/havok": "1.3.1",
"@babylonjs/inspector": "^7.1.0",
"@babylonjs/loaders": "^7.1.0",
"@babylonjs/materials": "^7.1.0",
"@babylonjs/procedural-textures": "^7.1.0",
"@babylonjs/serializers": "^7.1.0",
"@babylonjs/core": "^7.3.1",
"@babylonjs/gui": "^7.3.1",
"@babylonjs/havok": "1.3.4",
"@babylonjs/inspector": "^7.3.1",
"@babylonjs/loaders": "^7.3.1",
"@babylonjs/materials": "^7.3.1",
"@babylonjs/procedural-textures": "^7.3.1",
"@babylonjs/serializers": "^7.3.1",
"@picovoice/cobra-web": "^2.0.3",
"@picovoice/eagle-web": "^1.0.0",
"@picovoice/web-voice-processor": "^4.0.9",
@ -26,7 +26,7 @@
"@types/react": "^18.2.72",
"@types/react-dom": "^18.2.22",
"axios": "^1.6.8",
"babylon-html": "^0.0.2",
"babylon-html": "0.0.3",
"dom-to-image-more": "^3.3.0",
"earcut": "^2.2.4",
"events": "^3.3.0",
@ -55,8 +55,7 @@
}
},
"../babylon-html": {
"version": "0.0.2",
"extraneous": true,
"version": "0.0.3",
"license": "MIT",
"dependencies": {
"@babylonjs/core": "^7.1.0",
@ -70,14 +69,14 @@
}
},
"node_modules/@babylonjs/core": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/@babylonjs/core/-/core-7.1.0.tgz",
"integrity": "sha512-nz1CmflajMPnjdnwYrj72s4D/Q8IkOW4DBbwMfUNLFSCondfFXS3sZBgHeCAHpimR5n4e27YwvJ66MkQJsSraQ=="
"version": "7.3.1",
"resolved": "https://registry.npmjs.org/@babylonjs/core/-/core-7.3.1.tgz",
"integrity": "sha512-jahsEq0gFEIt/DzQvbIOtfikuAIplEtVT1tOURzJa3b+HMnSQBXj0p9w556HTRVCUj5bghP3Py8VHYMfwDaZRg=="
},
"node_modules/@babylonjs/gui": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/@babylonjs/gui/-/gui-7.1.0.tgz",
"integrity": "sha512-tY6MGtigjZwv+9rqtGTsyBZL9sHSPjgVq+JEWHUsACH0ffNkKsQxtKSDc5bryzNQpo1rzTMHqEeU+DnqNSVe1Q==",
"version": "7.3.1",
"resolved": "https://registry.npmjs.org/@babylonjs/gui/-/gui-7.3.1.tgz",
"integrity": "sha512-ElIkJV832nYAEi4hbFD/pqNtj4rnIVfFIBsIp+D+ANxj+BulXsXX9119st8iLoEow0d2tEocXe7Wksx7TYkZCQ==",
"peerDependencies": {
"@babylonjs/core": "^7.0.0"
}
@ -95,17 +94,17 @@
}
},
"node_modules/@babylonjs/havok": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/@babylonjs/havok/-/havok-1.3.1.tgz",
"integrity": "sha512-ctaAQ2RN7hzE2vukGiA27//08YE4RNqH4RN26fCd8q0q7Qn+pXg4P61ZgakWYox/YS4VqHrB3ovZUDtPt2Scxg==",
"version": "1.3.4",
"resolved": "https://registry.npmjs.org/@babylonjs/havok/-/havok-1.3.4.tgz",
"integrity": "sha512-mCz+w7vlSjPHLfGkCvt2oTRY2qFWXem6x/B6bolNW8lgsOn1SebeZ/lx6Q6nobNbOtWqs5jt4ESEb+ctzutvMw==",
"dependencies": {
"@types/emscripten": "^1.39.6"
}
},
"node_modules/@babylonjs/inspector": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/@babylonjs/inspector/-/inspector-7.1.0.tgz",
"integrity": "sha512-Hs9pk1LstJMnD4xXpHFlQVqXPtH/hhUcNSPaZlz8iesFrHJEXF4Mlao+ozwbl2WzDbMzmmex5nc1lw2HEBdhww==",
"version": "7.3.1",
"resolved": "https://registry.npmjs.org/@babylonjs/inspector/-/inspector-7.3.1.tgz",
"integrity": "sha512-RjifJ9Na69Xxf56kHdYuGzVXK1I8yl3/hhpF5TIUcL7N8BPmAhM+rp+94YIBRlz/uceC2tXm0FJvfvb6Ydu4zQ==",
"dependencies": {
"@fortawesome/fontawesome-svg-core": "^6.1.0",
"@fortawesome/free-regular-svg-icons": "^6.0.0",
@ -123,34 +122,34 @@
}
},
"node_modules/@babylonjs/loaders": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/@babylonjs/loaders/-/loaders-7.1.0.tgz",
"integrity": "sha512-6gLbFiUsiqsQDRWLl4TL5pMKiemSAB/E61SHpp8xkBalxZUiKroSbZt3Irxc7CHIq8SE5CYfPUPYjs6BmpTgqw==",
"version": "7.3.1",
"resolved": "https://registry.npmjs.org/@babylonjs/loaders/-/loaders-7.3.1.tgz",
"integrity": "sha512-SWI2r9NyRWTPYQ5MWJ2rCH3KN++8sCz44BSS5xayVj0IB4m+LuxnuchH4cdFAnbul56gXmL7mkZLG1sR154GHg==",
"peerDependencies": {
"@babylonjs/core": "^7.0.0",
"babylonjs-gltf2interface": "^7.0.0"
}
},
"node_modules/@babylonjs/materials": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/@babylonjs/materials/-/materials-7.1.0.tgz",
"integrity": "sha512-tHzpV6xUqpAgF9rPudDXetk1tIPbhggrybfIpvoWwbKlffIPQw4URsA+IBG04U5owWsicdhvTLQWP1WEwfbMQg==",
"version": "7.3.1",
"resolved": "https://registry.npmjs.org/@babylonjs/materials/-/materials-7.3.1.tgz",
"integrity": "sha512-0jACK6543/KDyOG5DNY10DM73JUHaB7n5au/pRVKV371pAbU224h1l5J+d6gRZCY3mhtE2MEi1N0PKQF8VRI8w==",
"peerDependencies": {
"@babylonjs/core": "^7.0.0"
}
},
"node_modules/@babylonjs/procedural-textures": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/@babylonjs/procedural-textures/-/procedural-textures-7.1.0.tgz",
"integrity": "sha512-ajjaXEQJpWNQQzWZdUJJzN4NBtI96lxbrhmBUpPdfQwAhX2LRG0tbKWKXd66QHh82f/FG+daIA2BCL7M4f2MMw==",
"version": "7.3.1",
"resolved": "https://registry.npmjs.org/@babylonjs/procedural-textures/-/procedural-textures-7.3.1.tgz",
"integrity": "sha512-TFwj3RoCGe/HldI7QM85QGW3eZZcl1RfwNCt8CATM6d9Juj9mWtwKVd7A0A6b0uXRsx97YY59d5zw6kcy52DmA==",
"peerDependencies": {
"@babylonjs/core": "^7.0.0"
}
},
"node_modules/@babylonjs/serializers": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/@babylonjs/serializers/-/serializers-7.1.0.tgz",
"integrity": "sha512-ToYutpqvqFVic+mhFkza6rLeE31sXXpixhdpu2bE+MRP13FoxNKfPD4np/DGBP7zUWmXIi2yRm6sZ7cGs8jp1g==",
"version": "7.3.1",
"resolved": "https://registry.npmjs.org/@babylonjs/serializers/-/serializers-7.3.1.tgz",
"integrity": "sha512-xU6SBD1JdoRfjZPS14NqszFAE2p+RzW3KocESVXPYmU5L/C/7Z6YYMUCT2r8hmQ4ANqrwa7Fp+qcGzbeFBFl5Q==",
"peerDependencies": {
"@babylonjs/core": "^7.0.0",
"babylonjs-gltf2interface": "^7.0.0"
@ -1108,13 +1107,8 @@
}
},
"node_modules/babylon-html": {
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/babylon-html/-/babylon-html-0.0.2.tgz",
"integrity": "sha512-bcdLgMmnjvLrysZq5VFzc71TkDT0b6coCv3ZoFzTxJDhXtZF96FFaRMba9rxMHp1TM3rX9u6yW8N/sJodA/qVg==",
"dependencies": {
"@babylonjs/core": "^7.1.0",
"dom-to-image-more": "^3.3.0"
}
"resolved": "../babylon-html",
"link": true
},
"node_modules/babylonjs-gltf2interface": {
"version": "7.1.0",

View File

@ -16,14 +16,14 @@
"havok": "cp ./node_modules/@babylonjs/havok/lib/esm/HavokPhysics.wasm ./node_modules/.vite/deps"
},
"dependencies": {
"@babylonjs/core": "^7.1.0",
"@babylonjs/gui": "^7.1.0",
"@babylonjs/havok": "1.3.1",
"@babylonjs/inspector": "^7.1.0",
"@babylonjs/loaders": "^7.1.0",
"@babylonjs/materials": "^7.1.0",
"@babylonjs/procedural-textures": "^7.1.0",
"@babylonjs/serializers": "^7.1.0",
"@babylonjs/core": "^7.3.1",
"@babylonjs/gui": "^7.3.1",
"@babylonjs/havok": "1.3.4",
"@babylonjs/inspector": "^7.3.1",
"@babylonjs/loaders": "^7.3.1",
"@babylonjs/materials": "^7.3.1",
"@babylonjs/procedural-textures": "^7.3.1",
"@babylonjs/serializers": "^7.3.1",
"@picovoice/cobra-web": "^2.0.3",
"@picovoice/eagle-web": "^1.0.0",
"@picovoice/web-voice-processor": "^4.0.9",
@ -34,7 +34,7 @@
"@types/react": "^18.2.72",
"@types/react-dom": "^18.2.22",
"axios": "^1.6.8",
"babylon-html": "^0.0.2",
"babylon-html": "0.0.3",
"dom-to-image-more": "^3.3.0",
"earcut": "^2.2.4",
"events": "^3.3.0",

View File

@ -3,6 +3,7 @@ import {
Mesh,
PhysicsMotionType,
Scene,
TransformNode,
Vector3,
WebXRControllerComponent,
WebXRDefaultExperience,
@ -31,7 +32,7 @@ import {DefaultScene} from "../defaultScene";
const CLICK_TIME = 300;
export class Base {
static stickVector = Vector3.Zero();
protected controller: WebXRInputSource;
protected xrInputSource: WebXRInputSource;
protected speedFactor = 4;
protected readonly scene: Scene;
protected grabbedMesh: AbstractMesh = null;
@ -47,25 +48,34 @@ export class Base {
private lastPosition: Vector3 = null;
protected controllers: Controllers;
private clickMenu: ClickMenu;
private pickPoint: Vector3 = new Vector3();
constructor(controller: WebXRInputSource,
xr: WebXRDefaultExperience,
diagramManager: DiagramManager) {
this.logger = log.getLogger('Base');
this.logger.setLevel(this.logger.levels.DEBUG);
this.controller = controller;
this.xrInputSource = controller;
this.controllers = diagramManager.controllers;
this.scene = DefaultScene.Scene;
this.xr = xr;
this.scene.onPointerObservable.add((pointerInfo) => {
if (pointerInfo.pickInfo.pickedMesh?.metadata?.template) {
const mesh = pointerInfo.pickInfo.pickedMesh;
const pos = mesh.absolutePosition;
this.pickPoint.copyFrom(pointerInfo.pickInfo.pickedPoint);
}
});
this.diagramManager = diagramManager;
this.scene.onBeforeRenderObservable.add(beforeRenderObserver, -1, false, this);
this.controller.onMotionControllerInitObservable.add(motionControllerObserver, -1, false, this);
this.xrInputSource.onMotionControllerInitObservable.add(motionControllerObserver, -1, false, this);
this.controllers.controllerObserver.add((event) => {
this.logger.debug(event);
switch (event.type) {
case ControllerEventType.PULSE:
if (event.gripId == this?.controller?.grip?.id) {
this.controller?.motionController?.pulse(.25, 30)
if (event.gripId == this?.xrInputSource?.grip?.id) {
this.xrInputSource?.motionController?.pulse(.25, 30)
.then(() => {
this.logger.debug("pulse done");
});
@ -83,14 +93,14 @@ export class Base {
public disable() {
this.scene.preventDefaultOnPointerDown = true;
this.controller.motionController.rootMesh.setEnabled(false)
this.controller.pointer.setEnabled(false);
this.xrInputSource.motionController.rootMesh.setEnabled(false)
this.xrInputSource.pointer.setEnabled(false);
}
public enable() {
this.scene.preventDefaultOnPointerDown = false;
this.controller.motionController.rootMesh.setEnabled(true);
this.controller.pointer.setEnabled(true)
this.xrInputSource.motionController.rootMesh.setEnabled(true);
this.xrInputSource.pointer.setEnabled(true)
}
protected initClicker(trigger: WebXRControllerComponent) {
@ -120,7 +130,7 @@ export class Base {
}
private grab(cloneEntity: boolean = false) {
let mesh = this.xr.pointerSelection.getMeshUnderPointer(this.controller.uniqueId);
let mesh = this.xr.pointerSelection.getMeshUnderPointer(this.xrInputSource.uniqueId);
if (!mesh) {
return;
@ -129,7 +139,7 @@ export class Base {
displayDebug(mesh);
if (!isDiagramEntity(mesh)) {
if (handleWasGrabbed(mesh)) {
mesh && mesh.setParent(this.controller.motionController.rootMesh);
mesh && mesh.setParent(this.xrInputSource.motionController.rootMesh);
this.grabbedMesh = mesh;
} else {
if (mesh?.parent?.parent?.metadata?.grabbable) {
@ -156,16 +166,16 @@ export class Base {
if ((!mesh.metadata?.grabClone || player) && !cloneEntity) {
if (mesh.physicsBody) {
const transformNode = setupTransformNode(mesh, this.controller.motionController.rootMesh);
const transformNode = setupTransformNode(mesh, this.xrInputSource.motionController.rootMesh);
mesh.physicsBody.setMotionType(PhysicsMotionType.ANIMATED);
this.grabbedMeshParentId = transformNode.id;
} else {
mesh.setParent(this.controller.motionController.rootMesh);
mesh.setParent(this.xrInputSource.motionController.rootMesh);
}
this.grabbedMesh = mesh;
} else {
this.logger.debug("cloning " + mesh?.id);
const clone = grabAndClone(this.diagramManager, mesh, this.controller.motionController.rootMesh);
const clone = grabAndClone(this.diagramManager, mesh, this.xrInputSource.motionController.rootMesh);
clone.newMesh.metadata.grabClone = false;
clone.newMesh.metadata.tool = false;
this.grabbedMeshParentId = clone.transformNode.id;
@ -196,8 +206,14 @@ export class Base {
reparent(mesh, this.previousParentId, this.grabbedMeshParentId);
this.grabbedMeshParentId = null;
if (!mesh.physicsBody) {
mesh.position = snapGridVal(mesh.position, this.diagramManager._config.current.gridSnap);
mesh.rotation = snapRotateVal(mesh.rotation, this.diagramManager._config.current.rotateSnap);
const transform = new TransformNode('temp', this.scene);
transform.rotation = mesh.rotation;
transform.position = this.pickPoint;
mesh.setParent(transform);
transform.position = snapGridVal(transform.position, this.diagramManager._config.current.gridSnap).clone();
transform.rotation = snapRotateVal(transform.rotation, this.diagramManager._config.current.rotateSnap);
mesh.setParent(null);
}
this.previousParentId = null;
this.previousScaling = null;
@ -224,7 +240,7 @@ export class Base {
}
private click() {
let mesh = this.xr.pointerSelection.getMeshUnderPointer(this.controller.uniqueId);
let mesh = this.xr.pointerSelection.getMeshUnderPointer(this.xrInputSource.uniqueId);
if (pointable(mesh)) {
this.logger.debug("click on " + mesh.id);
@ -234,7 +250,7 @@ export class Base {
this.clickMenu = null;
}
} else {
this.clickMenu = new ClickMenu(mesh, this.diagramManager, this.controller.grip);
this.clickMenu = new ClickMenu(mesh, this.diagramManager, this.xrInputSource.grip);
}
} else {

View File

@ -19,7 +19,7 @@ export class Left extends Base {
WebXRInputSource, xr: WebXRDefaultExperience, diagramManager: DiagramManager) {
super(controller, xr, diagramManager);
const scene = DefaultScene.Scene;
this.controller.onMotionControllerInitObservable.add((init) => {
this.xrInputSource.onMotionControllerInitObservable.add((init) => {
if (init.components['xr-standard-thumbstick']) {
init.components['xr-standard-thumbstick']
.onAxisValueChangedObservable.add((value) => {
@ -66,7 +66,7 @@ export class Left extends Base {
this.controllers.controllerObserver.notifyObservers({
type: ControllerEventType.TRIGGER,
value: button.value,
controller: this.controller
controller: this.xrInputSource
});
}, -1, false, this);
}

View File

@ -44,7 +44,7 @@ export class Right extends Base {
super(controller, xr, diagramManager);
const scene = DefaultScene.Scene;
this.controller.onMotionControllerInitObservable.add((init) => {
this.xrInputSource.onMotionControllerInitObservable.add((init) => {
this.initTrigger(init.components['xr-standard-trigger']);
if (init.components['a-button']) {
const transform = new TransformNode('a-button', scene);
@ -70,7 +70,7 @@ export class Right extends Base {
this.controllers.controllerObserver.notifyObservers({
type: ControllerEventType.TRIGGER,
value: button.value,
controller: this.controller
controller: this.xrInputSource
});
}, -1, false, this);
}

View File

@ -55,7 +55,6 @@ export class DiagramConnection {
private toAnchor: TransformNode;
private fromAnchor: TransformNode;
private transformNode: TransformNode;
private points: Vector3[] = [];
private _mesh: AbstractMesh;
@ -129,7 +128,6 @@ export class DiagramConnection {
private buildConnection() {
this.logger.debug(`buildConnection from ${this._from} to ${this._to}`);
this._mesh = MeshBuilder.CreateCylinder(this.id + "_connection", {diameter: .025, height: 1}, this.scene);
this.transformNode = new TransformNode(this.id + "_transform", this.scene);
this.transformNode.metadata = {exportable: true};
this._mesh.setParent(this.transformNode);
@ -154,7 +152,6 @@ export class DiagramConnection {
}
private beforeRender = () => {
if (this.tick % 5 == 0) {
this.recalculate();
this.setPoints();

View File

@ -1,5 +1,5 @@
import {AbstractMesh, InstancedMesh, Mesh, MeshBuilder, Scene, TransformNode, Vector3} from "@babylonjs/core";
import {buildStandardMaterial} from "../materials/functions/buildStandardMaterial";
import {AbstractMesh, InstancedMesh, Mesh, Scene, TransformNode, Vector3} from "@babylonjs/core";
import {HtmlMeshBuilder} from "babylon-html";
export class Handle {
public mesh: AbstractMesh;
@ -86,13 +86,18 @@ function getHandleMesh(name: string, scene: Scene): InstancedMesh {
instance.setParent(scene.getMeshByName("platform"));
return instance;
}
const handle = MeshBuilder.CreateCapsule("base-handle-mesh", {
/*const handle = MeshBuilder.CreateCapsule("base-handle-mesh", {
radius: .04,
orientation: Vector3.Right(),
height: .3
}, scene);*/
const handle = HtmlMeshBuilder.CreatePlaneSync("base-handle-mesh", {
html:
`<div style="width: 100%; height: 100%; border-radius: 32px; background-color: #111122; color: #eeeeee"><center>Handle</center></div>
`, width: .5, height: .1, image: {width: 256, height: 51}
}, scene);
handle.setEnabled(false);
handle.material = buildStandardMaterial('base-handle-material', scene, "#CCCCDD");
//handle.material = buildStandardMaterial('base-handle-material', scene, "#CCCCDD");
handle.id = "base-handle-mesh";
const instance = new InstancedMesh(name, (handle as Mesh));
instance.setParent(scene.getMeshById("platform"));