diff --git a/package-lock.json b/package-lock.json index cfdb94c..df53abc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,6 +21,7 @@ "google-static-maps-tile": "1.0.0", "query-string": "^8.1.0", "ring-client-api": "^11.8.0", + "uuid": "^9.0.0", "vite-express": "^0.9.1" }, "devDependencies": { diff --git a/package.json b/package.json index 3cecb4e..c3ef175 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,8 @@ "google-static-maps-tile": "1.0.0", "query-string": "^8.1.0", "vite-express": "^0.9.1", - "express-http-proxy": "^1.6.3" + "express-http-proxy": "^1.6.3", + "uuid": "^9.0.0" }, "devDependencies": { "typescript": "^5.0.2", diff --git a/public/outdoor_field.jpeg b/public/outdoor_field.jpeg new file mode 100644 index 0000000..d86b095 Binary files /dev/null and b/public/outdoor_field.jpeg differ diff --git a/src/app.ts b/src/app.ts index a723f30..6591ad0 100644 --- a/src/app.ts +++ b/src/app.ts @@ -8,6 +8,7 @@ import { HemisphericLight, MeshBuilder, PBRMetallicRoughnessMaterial, + PhotoDome, PhysicsAggregate, PhysicsShapeType, Scene, @@ -21,8 +22,8 @@ import {Left} from "./controllers/left"; import {Bmenu} from "./menus/bmenu"; import HavokPhysics from "@babylonjs/havok"; import {Rigplatform} from "./controllers/rigplatform"; -import {Cameras} from "./integration/ring/cameras"; -import {Mapt} from "./util/mapt"; + +import {DiagramManager} from "./diagram/diagramManager"; class App { @@ -43,6 +44,7 @@ class App { const engine = new Engine(canvas, true); const scene = new Scene(engine); + const diagramManager = new DiagramManager(scene); const havokInstance = await HavokPhysics(); const havokPlugin = new HavokPlugin(true, havokInstance); scene.enablePhysics(new Vector3(0, -9.8, 0), havokPlugin); @@ -51,65 +53,21 @@ class App { camera.attachControl(canvas, true); new HemisphericLight("light1", new Vector3(1, 1, 0), scene); - const rig = new Rigplatform(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', {}, scene); -*/ + const xr = await WebXRDefaultExperience.CreateAsync(scene, { floorMeshes: [this.createGround(scene)], optionalFeatures: true }); - xr.baseExperience.camera.parent = rig.rigMesh; - const b = new Bmenu(scene, rig, xr.baseExperience); - //const box = MeshBuilder.CreateBox("box", {size: 1}, scene); - //box.position.z = -4; - - - /*box.actionManager.registerAction( - new InterpolateValueAction( - ActionManager.OnPointerOverTrigger,box, 'visibility', 0.2, 500 - ) - ); - box.actionManager.registerAction( - new InterpolateValueAction( - ActionManager.OnPointerOutTrigger,box, 'visibility', 1, 200 - ) - );*/ - - //const edit = new ObjectEditor(scene, box); - //const edit = new ObjectEditor(scene, box); - const ring = new Cameras(scene, this.token); - ring.getCameras().then(() => ring.createCameras()); - - const stickVector = Vector3.Zero(); - xr.input.onControllerAddedObservable.add((source, state) => { - let controller; - switch (source.inputSource.handedness) { - case "right": - controller = new Right(source); - rig.right = controller; - controller.setBMenu(b); - break; - case "left": - controller = new Left(source); - rig.left = controller; - break; - - } - xr.baseExperience.camera.position = new Vector3(0, 1.6, 0); - if (controller) { - controller.setStickVector(stickVector); - controller.setCamera(xr.baseExperience.camera); - controller.setRig(rig.body); - } - - console.log(source); - console.log(state); - }); + const rig = new Rigplatform(scene, xr); + //const ring = new Cameras(scene, this.token); + //ring.getCameras().then(() => ring.createCameras()); xr.teleportation.detach(); // hide/show the Inspector @@ -123,12 +81,7 @@ class App { } } }); - const map = new Mapt(scene); - map.buildMapImage(); - //map.createMapTiles(26.443397,-82.111512); - - // run the main render loop engine.runRenderLoop(() => { scene.render(); }); @@ -142,6 +95,7 @@ class App { groundMaterial.baseTexture = gText; groundMaterial.metallic = 0; groundMaterial.roughness = 1; + const ground = MeshBuilder.CreateGround("ground", {width: 100, height: 100, subdivisions: 1}, scene); ground.material = groundMaterial; @@ -149,5 +103,6 @@ class App { return ground; } } + const app = new App(); diff --git a/src/controllers/base.ts b/src/controllers/base.ts index 0ecf5cc..7338ed2 100644 --- a/src/controllers/base.ts +++ b/src/controllers/base.ts @@ -1,10 +1,10 @@ import {PhysicsBody, Vector3, WebXRCamera, WebXRInputSource} from "@babylonjs/core"; +import {Rigplatform} from "./rigplatform"; export class Base { + static stickVector = Vector3.Zero(); protected controller: WebXRInputSource; - protected stickVector: Vector3; - protected body: PhysicsBody; - protected camera: WebXRCamera; + protected rig: Rigplatform; protected speedFactor = 4; constructor(controller: @@ -12,24 +12,17 @@ export class Base { this.controller = controller; this.controller.onMotionControllerInitObservable.add((init) => { if (init.components['xr-standard-trigger']) { - init.components['xr-standard-trigger'].onButtonStateChangedObservable.add((value) => { - if (value.value == 1) { - console.log(value); - } + init.components['xr-standard-trigger'] + .onButtonStateChangedObservable + .add((value) => { + if (value.value == 1) { + console.log(value); + } }); } }); } - - setRig(body: PhysicsBody) { - this.body = body; - } - - setCamera(camera: WebXRCamera) { - this.camera = camera; - } - - setStickVector(vector: Vector3) { - this.stickVector = vector; + setRig(rig: Rigplatform) { + this.rig = rig; } } \ No newline at end of file diff --git a/src/controllers/left.ts b/src/controllers/left.ts index dc8fcf4..c1af316 100644 --- a/src/controllers/left.ts +++ b/src/controllers/left.ts @@ -1,8 +1,8 @@ -import {Quaternion, Vector3, WebXRInputSource} from "@babylonjs/core"; +import {Vector3, WebXRInputSource} from "@babylonjs/core"; import {Base} from "./base"; export class Left extends Base { - private x90 = Quaternion.RotationAxis(Vector3.Up(), 1.5708); + constructor(controller: WebXRInputSource) { @@ -11,26 +11,23 @@ export class Left extends Base { if (init.components['xr-standard-thumbstick']) { init.components['xr-standard-thumbstick'] .onAxisValueChangedObservable.add((value) => { - const ray = this.camera.getForwardRay(); + if (Math.abs(value.x) > .1) { - const direction = ray.direction.applyRotationQuaternion(this.x90).scale(value.x*this.speedFactor); - this.body.setLinearVelocity(direction); - this.stickVector.x = 1; + this.rig.leftright(value.x*this.speedFactor); + Base.stickVector.x = 1; } else { - this.stickVector.x = 0; + Base.stickVector.x = 0; } if (Math.abs(value.y) > .1) { - let direction = Vector3.Zero(); - this.body.getLinearVelocityToRef(direction); - direction.y = (value.y*-1*this.speedFactor); - this.body.setLinearVelocity(direction); - this.stickVector.y = 1; + this.rig.updown(value.y*this.speedFactor); + Base.stickVector.y = 1; } else { - this.stickVector.y = 0; + Base.stickVector.y = 0; } - if (this.stickVector.equals(Vector3.Zero())) { - this.body.setLinearVelocity(Vector3.Zero()); + if (Base.stickVector.equals(Vector3.Zero())) { + this.rig.updown(0); + this.rig.leftright(0) } else { } diff --git a/src/controllers/right.ts b/src/controllers/right.ts index 90abf5f..6f7acf8 100644 --- a/src/controllers/right.ts +++ b/src/controllers/right.ts @@ -1,15 +1,37 @@ import {Base} from "./base"; -import {Mesh, Vector3, WebXRInputSource} from "@babylonjs/core"; -import {Bmenu} from "../menus/bmenu"; +import {Vector3, WebXRInputSource} from "@babylonjs/core"; +import {Bmenu, BmenuState} from "../menus/bmenu"; +import {DiagramEvent, DiagramEventType, DiagramManager} from "../diagram/diagramManager"; export class Right extends Base { private bmenu: Bmenu; - private addMesh: Mesh; + private down: boolean = false; + constructor(controller: WebXRInputSource) { super(controller); - this.controller.onMotionControllerInitObservable.add((init)=> { + const trigger = init.components['xr-standard-trigger']; + if (trigger) { + trigger + .onButtonStateChangedObservable + .add((value) => { + if (value.value > .4 && !this.down) { + this.down = true; + if (this.bmenu.getState() == BmenuState.ADDING) { + this.bmenu.setState(BmenuState.DROPPING); + const event: DiagramEvent = { + type: DiagramEventType.DROP, + entity: null + } + DiagramManager.onDiagramEventObservable.notifyObservers(event); + } + } + if (value.value < .05) { + this.down = false; + } + }); + } if (init.components['b-button']) { init.components['b-button'].onButtonStateChangedObservable.add((value)=>{ if (value.pressed) { @@ -21,29 +43,29 @@ export class Right extends Base { if (init.components['xr-standard-thumbstick']) { init.components['xr-standard-thumbstick'] .onAxisValueChangedObservable.add((value) => { - const ray = this.camera.getForwardRay(); - if (Math.abs(value.x) > .1) { - this.body.setAngularVelocity(Vector3.Up().scale(value.x)); + this.rig.turn(value.x); } else { - this.body.setAngularVelocity(Vector3.Zero()); + this.rig.turn(0); } + if (Math.abs(value.y) > .1) { - this.body.setLinearVelocity(ray.direction.scale(value.y*-1*this.speedFactor)); - this.stickVector.z = 1; + this.rig.forwardback(value.y*this.speedFactor); + Base.stickVector.z = 1; } else { - this.stickVector.z = 0; + Base.stickVector.z = 0; } - if (this.stickVector.equals(Vector3.Zero())) { - this.body.setLinearVelocity(Vector3.Zero()); + if (Base.stickVector.equals(Vector3.Zero())) { + this.rig.forwardback(0); } }); } - }); } + public setBMenu(menu: Bmenu) { this.bmenu = menu; + this.bmenu.setController(this.controller); } } \ No newline at end of file diff --git a/src/controllers/rigplatform.ts b/src/controllers/rigplatform.ts index 06388a3..fe3184b 100644 --- a/src/controllers/rigplatform.ts +++ b/src/controllers/rigplatform.ts @@ -1,40 +1,191 @@ import { + Camera, Color3, Mesh, MeshBuilder, - PhysicsAggregate, PhysicsBody, PhysicsMotionType, - PhysicsShapeType, Quaternion, + PhysicsAggregate, + PhysicsBody, + PhysicsMotionType, + PhysicsShapeType, + Quaternion, Scene, StandardMaterial, - Vector3 + Vector3, + WebXRDefaultExperience } from "@babylonjs/core"; import {Right} from "./right"; import {Left} from "./left"; +import {Bmenu} from "../menus/bmenu"; export class Rigplatform { + static LINEAR_VELOCITY = 4; + static ANGULAR_VELOCITY = 3; + public bMenu: Bmenu; + static x90 = Quaternion.RotationAxis(Vector3.Up(), 1.5708); + private camera: Camera; private scene: Scene; + private xr: WebXRDefaultExperience; public right: Right; public left: Left; public body: PhysicsBody; public rigMesh: Mesh; - constructor(scene: Scene) { + 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; + this.rigMesh = MeshBuilder.CreateCylinder("platform", {diameter: 1.5, height: .01}, scene); + + for (const cam of this.scene.cameras) { + cam.parent = this.rigMesh; + } + const myMaterial = new StandardMaterial("myMaterial", scene); myMaterial.diffuseColor = Color3.Blue(); this.rigMesh.material = myMaterial; this.rigMesh.setAbsolutePosition(new Vector3(0, .1, -3)); - this.rigMesh.visibility=0; + this.rigMesh.visibility = 1; const rigAggregate = 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: .1, restitution: .1}, scene); rigAggregate.body.setMotionType(PhysicsMotionType.ANIMATED); rigAggregate.body.setGravityFactor(0); this.#fixRotation(); this.body = rigAggregate.body; + this.#setupKeyboard(); + this.#initializeControllers(); + this.scene.onActiveCameraChanged.add((s) => { + this.camera = s.activeCamera; + this.camera.parent = this.rigMesh; + console.log('camera changed'); + }); + } + + #initializeControllers() { + this.xr.input.onControllerAddedObservable.add((source, state) => { + let controller; + switch (source.inputSource.handedness) { + case "right": + controller = new Right(source); + this.right = controller; + controller.setBMenu(this.bMenu); + break; + case "left": + controller = new Left(source); + this.left = controller; + break; + + } + this.xr.baseExperience.camera.position = new Vector3(0, 1.6, 0); + if (controller) { + controller.setRig(this); + } + + console.log(source); + console.log(state); + }); + } + + 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); + console.log(val); + } + + public stop() { + this.body.setLinearVelocity(Vector3.Zero()); + this.body.setAngularVelocity(Vector3.Zero()); + } + + public updown(val: number) { + let direction = Vector3.Zero(); + this.body.getLinearVelocityToRef(direction); + direction.y = (val * -1); + this.body.setLinearVelocity(direction); + + } + + public turn(val: number) { + const snap = true; + if (snap) { + if (!this.turning) { + this.turning = true; + const q = this.rigMesh.rotation.y += Math.abs(val) * 22.5; + + } else { + if (val < .1) { + this.turning = false; + } + } + } else { + if (Math.abs(val) > .1) { + this.body.setAngularVelocity(Vector3.Up().scale(val)); + } else { + this.body.setAngularVelocity(Vector3.Zero()); + } + } + + } + + //create a method to set the camera to the rig + + #setupKeyboard() { + ///simplify this with a map + + window.addEventListener("keydown", (ev) => { + switch (ev.key) { + case "w": + this.forwardback(1 * Rigplatform.LINEAR_VELOCITY); + break; + case "s": + this.forwardback(-1 * Rigplatform.LINEAR_VELOCITY); + break; + case "a": + this.leftright(1 * Rigplatform.LINEAR_VELOCITY); + break; + case "d": + this.leftright(-1 * Rigplatform.LINEAR_VELOCITY); + break; + case "q": + this.turn(-1 * Rigplatform.ANGULAR_VELOCITY); + break; + case "e": + this.turn(1 * Rigplatform.ANGULAR_VELOCITY); + break; + case "W": + this.updown(-1 * Rigplatform.LINEAR_VELOCITY); + break; + case "S": + this.updown(1 * Rigplatform.LINEAR_VELOCITY); + break; + case " ": + this.bMenu.toggle() + } + + }); + window.addEventListener("keyup", (ev) => { + const keys = "wsadqeWS"; + + if (keys.indexOf(ev.key) > -1) { + this.stop(); + } + }); } #fixRotation() { diff --git a/src/diagram/diagramEntity.ts b/src/diagram/diagramEntity.ts new file mode 100644 index 0000000..139597f --- /dev/null +++ b/src/diagram/diagramEntity.ts @@ -0,0 +1,2 @@ + + diff --git a/src/diagram/diagramManager.ts b/src/diagram/diagramManager.ts new file mode 100644 index 0000000..64aaff1 --- /dev/null +++ b/src/diagram/diagramManager.ts @@ -0,0 +1,140 @@ +import {AbstractMesh, Color3, Mesh, MeshBuilder, Observable, Scene, StandardMaterial, Vector3} 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; +} +export class DiagramManager { + static onDiagramEventObservable = new Observable(); + static leftController: Mesh; + static currentMesh: AbstractMesh; + static rightController: Mesh; + static state: BmenuState; + private scene: Scene; + + constructor(scene: Scene) { + this.scene = scene; + if (DiagramManager.onDiagramEventObservable.hasObservers()) { + + } else { + DiagramManager.onDiagramEventObservable.add(this.#onDiagramEvent, -1, true, this); + } + } + #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("#","")); + } + } + + + switch (event.type) { + case DiagramEventType.DROPPED: + break; + case DiagramEventType.DROP: + if (DiagramManager.currentMesh) { + const newMesh = DiagramManager.currentMesh.clone(DiagramManager.currentMesh.name = "id" + uuidv4(), DiagramManager.currentMesh.parent); + DiagramManager.currentMesh.setParent(null); + DiagramManager.currentMesh = newMesh; + DiagramManager.onDiagramEventObservable.notifyObservers({type: DiagramEventType.DROPPED, entity: entity}); + } + break; + case DiagramEventType.ADD: + if (mesh) { + return; + } else { + mesh = this.#createMesh(entity); + if (!mesh) { + return; + } + } + + case DiagramEventType.MODIFY: + if (!mesh) { + + } else { + const rotation = entity.rotation; + const scale = entity.scale; + const position = entity.position; + + mesh.material = material; + mesh.position = new Vector3(position.x, position.y, position.z); + mesh.rotation = new Vector3(rotation.x, rotation.y, rotation.z); + + if (entity.parent) { + mesh.parent = this.scene.getMeshByName(entity.parent); + } + } + DiagramManager.currentMesh = mesh; + break; + case DiagramEventType.REMOVE: + break; + + + } + + } + #createMesh(entity: DiagramEntity) { + if (!entity.id) { + entity.id = "id" + uuidv4(); + } + let mesh: Mesh; + switch (entity.template) { + case "#box-template": + mesh = MeshBuilder.CreateBox( entity.id, + {width: entity.scale.x, + height: entity.scale.y, + depth: entity.scale.z}, this.scene); + break; + + case "#sphere-template": + + mesh= MeshBuilder.CreateSphere(entity.id, {diameter: entity.scale.x}, this.scene); + break + case "#cylinder-template": + mesh= MeshBuilder.CreateCylinder(entity.id, {diameter: entity.scale.x, height: entity.scale.y}, this.scene); + break; + default: + mesh = null; + } + return mesh; + } + +} + + diff --git a/src/menus/bmenu.ts b/src/menus/bmenu.ts index b0d6960..0c8244b 100644 --- a/src/menus/bmenu.ts +++ b/src/menus/bmenu.ts @@ -1,28 +1,44 @@ -import {Angle, FadeInOutBehavior, Scene, TransformNode, Vector3, WebXRExperienceHelper} from "@babylonjs/core"; import { - Container3D, - CylinderPanel, - GUI3DManager, - HandMenu, - HolographicButton, HolographicSlate, - NearMenu, PlanePanel, - SpherePanel -} from "@babylonjs/gui"; -import {Rigplatform} from "../controllers/rigplatform"; + AbstractMesh, + Angle, + Color3, Mesh, + MeshBuilder, + Scene, SceneSerializer, + StandardMaterial, + TransformNode, Vector3, + WebXRExperienceHelper, WebXRInputSource +} from "@babylonjs/core"; +import {GUI3DManager, HolographicButton, PlanePanel} from "@babylonjs/gui"; +import {DiagramEntity, DiagramEvent, DiagramEventType, DiagramManager} from "../diagram/diagramManager"; +export enum BmenuState { + NONE, + ADDING, // Adding a new entity + DROPPING, // Dropping an entity + +} export class Bmenu { private scene; - private rig; + private state: BmenuState = BmenuState.NONE; + private xr; private manager; private panel; - constructor(scene: Scene, rig: Rigplatform, xr: WebXRExperienceHelper) { + private rightController: AbstractMesh; + + constructor(scene: Scene, xr: WebXRExperienceHelper) { this.scene = scene; - this.rig = rig; 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) { + this.rightController = controller.grip; } - makeButton(name: string, id: string) { const button = new HolographicButton(name); button.text = name; @@ -30,20 +46,69 @@ export class Bmenu { button.onPointerClickObservable.add(this.#clickhandler, -1, false, this); return button; } + #clickhandler(_info, state) { console.log(state.currentTarget.name); + + let entity: DiagramEntity = { + template: null, + position: new Vector3(0,-.040,.13), + rotation: new Vector3(), + scale: new Vector3(.1, .1, .1), + color: "#CEE", + text: "test", + last_seen: new Date(), + parent: this.rightController.id + + }; + + switch (state.currentTarget.name) { + case "addBox": + entity.template = "#box-template"; + break; + case "addSphere": + entity.template = "#sphere-template"; + break; + case "addCylinder": + entity.template = "#cylinder-template"; + break; + default: + console.log("Unknown button"); + return; + } + const event: DiagramEvent = { + type: DiagramEventType.ADD, + entity: entity + } + this.state = BmenuState.ADDING; + DiagramManager.onDiagramEventObservable.notifyObservers(event); } + public getState() { + return this.state; + } + public setState(state: BmenuState) { + this.state = state; + } + + #createDefaultMaterial() { + + const myMaterial = new StandardMaterial("myMaterial", this.scene); + myMaterial.diffuseColor = Color3.FromHexString("#CEE"); + return myMaterial; + } + toggle() { if (this.panel) { this.panel.dispose(); this.panel = null; + this.setState(BmenuState.NONE); } else { const anchor = new TransformNode("bMenuAnchor"); anchor.rotation.y = Angle.FromDegrees(180).radians(); const cam = this.xr.camera.getFrontPosition(2); anchor.position = cam; const panel = new PlanePanel(); - panel.margin=.6; + panel.margin = .6; //panel.scaling.y=.5; //panel.orientation = Container3D.FACEFORWARDREVERSED_ORIENTATION;