Updated P2P code, moved some classes to increase modularity.

This commit is contained in:
Michael Mainguy 2023-08-03 11:36:30 -05:00
parent f44d84b7be
commit 77f5a2543f
13 changed files with 266 additions and 193 deletions

View File

@ -24,6 +24,7 @@ import log from "loglevel";
import {AppConfig} from "./util/appConfig";
import {DiaSounds} from "./util/diaSounds";
import {PeerjsNetworkConnection} from "./integration/peerjsNetworkConnection";
import {InputTextView} from "./information/inputTextView";
export class App {
//preTasks = [havokModule];
@ -60,6 +61,26 @@ export class App {
const engine = new Engine(canvas, true);
const scene = new Scene(engine);
const query = Object.fromEntries(new URLSearchParams(window.location.search));
this.logger.debug('Query', query);
if (query.shareCode) {
scene.onReadyObservable.addOnce(() => {
this.logger.debug('Scene ready');
const identityView = new InputTextView({scene: scene, text: ""});
identityView.onTextObservable.add((text) => {
if (text?.text?.trim() != "") {
this.logger.debug('Identity', text.text);
const network = new PeerjsNetworkConnection(query.shareCode, text.text);
if (query.host) {
network.connect(query.host);
}
}
});
identityView.show();
});
}
this.scene = scene;
const sounds = new DiaSounds(scene);
sounds.enter.autoplay = true;
@ -114,9 +135,8 @@ export class App {
const diagramManager = new DiagramManager(this.scene, xr.baseExperience);
this.rig = new Rigplatform(this.scene, xr, diagramManager);
const toolbox = new Toolbox(scene, xr.baseExperience, diagramManager);
//const network = new PeerjsNetworkConnection();
import ('./diagram/indexdbPersistenceManager').then((module) => {
import ('./integration/indexdbPersistenceManager').then((module) => {
const persistenceManager = new module.IndexdbPersistenceManager("diagram");
diagramManager.setPersistenceManager(persistenceManager);
AppConfig.config.setPersistenceManager(persistenceManager);
@ -206,8 +226,13 @@ export class App {
engine.runRenderLoop(() => {
scene.render();
});
//const data = window.location.search.replace('?', '')
// .split('&')
// .map((x) => x.split('='));
//const network = new PeerjsNetworkConnection();
//network.connect(data[0][1]);
this.logger.info('Render loop started');
}

View File

@ -14,8 +14,6 @@ import {DiagramManager} from "../diagram/diagramManager";
import {DiagramEvent, DiagramEventType} from "../diagram/diagramEntity";
import log from "loglevel";
import {Controllers} from "./controllers";
import {DiagramShapePhysics} from "../diagram/diagramShapePhysics";
export class Base {
static stickVector = Vector3.Zero();
@ -151,9 +149,6 @@ export class Base {
}
transformNode.setParent(this.controller.motionController.rootMesh);
this.grabbedMeshParentId = transformNode.id;
DiagramShapePhysics
.applyPhysics(newMesh, this.scene)
.setMotionType(PhysicsMotionType.ANIMATED);
//newMesh && newMesh.setParent(this.controller.motionController.rootMesh);

View File

@ -10,8 +10,6 @@ export enum DiagramEventType {
CLEAR,
CHANGECOLOR,
COPY
}

View File

@ -6,19 +6,21 @@ import {
InstancedMesh,
Mesh,
Observable,
PhysicsAggregate,
PhysicsBody,
PhysicsMotionType,
PhysicsShapeType,
PlaySoundAction,
Scene,
WebXRExperienceHelper
} from "@babylonjs/core";
import {DiagramEntity, DiagramEvent, DiagramEventType} from "./diagramEntity";
import {IPersistenceManager} from "./iPersistenceManager";
import {IPersistenceManager} from "../integration/iPersistenceManager";
import {MeshConverter} from "./meshConverter";
import log from "loglevel";
import {Controllers} from "../controllers/controllers";
import {DiaSounds} from "../util/diaSounds";
import {AppConfig} from "../util/appConfig";
import {DiagramShapePhysics} from "./diagramShapePhysics";
import {TextLabel} from "./textLabel";
export class DiagramManager {
@ -41,8 +43,9 @@ export class DiagramManager {
}
return this.persistenceManager;
}
private readonly actionManager: ActionManager;
private readonly actionManager: ActionManager;
private config: AppConfig;
constructor(scene: Scene, xr: WebXRExperienceHelper) {
this.scene = scene;
this.xr = xr;
@ -86,6 +89,7 @@ export class DiagramManager {
}
newMesh.material = mesh.material;
newMesh.metadata = mesh.metadata;
DiagramShapePhysics.applyPhysics(newMesh, this.scene);
return newMesh;
}
@ -127,9 +131,14 @@ export class DiagramManager {
break;
case DiagramEventType.ADD:
this.getPersistenceManager()?.add(mesh);
DiagramShapePhysics
.applyPhysics(mesh, this.scene);
break;
case DiagramEventType.MODIFY:
this.getPersistenceManager()?.modify(mesh);
DiagramShapePhysics
.applyPhysics(mesh, this.scene);
break;
case DiagramEventType.CHANGECOLOR:
if (!event.oldColor) {
@ -160,4 +169,61 @@ export class DiagramManager {
break;
}
}
}
class DiagramShapePhysics {
private static logger: log.Logger = log.getLogger('DiagramShapePhysics');
public static applyPhysics(mesh: AbstractMesh, scene: Scene): PhysicsBody {
if (!mesh?.metadata?.template) {
this.logger.error("applyPhysics: mesh.metadata.template is null", mesh);
return null;
}
if (!scene) {
this.logger.error("applyPhysics: mesh or scene is null");
return null;
}
if (mesh.physicsBody) {
mesh.physicsBody.dispose();
}
let shapeType = PhysicsShapeType.BOX;
switch (mesh.metadata.template) {
case "#sphere-template":
shapeType = PhysicsShapeType.SPHERE;
break;
case "#cylinder-template":
shapeType = PhysicsShapeType.CYLINDER;
break;
case "#cone-template":
shapeType = PhysicsShapeType.CONVEX_HULL;
break;
}
let mass = mesh.scaling.x * mesh.scaling.y * mesh.scaling.z * 10;
const aggregate = new PhysicsAggregate(mesh,
shapeType, {mass: mass, restitution: .02, friction: .9}, scene);
if (mesh.parent) {
aggregate.body
.setMotionType(PhysicsMotionType.ANIMATED);
} else {
aggregate.body
.setMotionType(PhysicsMotionType.DYNAMIC);
}
aggregate.body.setCollisionCallbackEnabled(true);
aggregate.body.getCollisionObservable().add((event, state) => {
if (event.distance > .001 && !DiaSounds.instance.low.isPlaying) {
this.logger.debug(event, state);
DiaSounds.instance.low.play();
}
}, -1, false, this);
const body = aggregate.body;
body.setMotionType(PhysicsMotionType.ANIMATED);
body.setLinearDamping(.95);
body.setAngularDamping(.99);
body.setGravityFactor(0);
return aggregate.body;
}
}

View File

@ -1,52 +0,0 @@
import {AbstractMesh, PhysicsAggregate, PhysicsBody, PhysicsMotionType, PhysicsShapeType, Scene} from "@babylonjs/core";
import {DiaSounds} from "../util/diaSounds";
import log from "loglevel";
export class DiagramShapePhysics {
private static logger: log.Logger = log.getLogger('DiagramShapePhysics');
public static applyPhysics(mesh: AbstractMesh, scene: Scene): PhysicsBody {
if (!mesh?.metadata?.template) {
this.logger.error("applyPhysics: mesh.metadata.template is null", mesh);
return null;
}
if (!scene) {
this.logger.error("applyPhysics: mesh or scene is null");
return null;
}
if (mesh.physicsBody) {
mesh.physicsBody.dispose();
}
let shapeType = PhysicsShapeType.BOX;
switch (mesh.metadata.template) {
case "#sphere-template":
shapeType = PhysicsShapeType.SPHERE;
break;
case "#cylinder-template":
shapeType = PhysicsShapeType.CYLINDER;
break;
case "#cone-template":
shapeType = PhysicsShapeType.CONVEX_HULL;
break;
}
let mass = mesh.scaling.x * mesh.scaling.y * mesh.scaling.z * 10;
const aggregate = new PhysicsAggregate(mesh,
shapeType, {mass: mass, restitution: .02, friction: .9}, scene);
aggregate.body.setCollisionCallbackEnabled(true);
aggregate.body.getCollisionObservable().add((event, state) => {
if (event.distance > .001 && !DiaSounds.instance.low.isPlaying) {
this.logger.debug(event, state);
DiaSounds.instance.low.play();
}
}, -1, false, this);
const body = aggregate.body;
body.setMotionType(PhysicsMotionType.ANIMATED);
body.setLinearDamping(.95);
body.setAngularDamping(.99);
body.setGravityFactor(0);
return aggregate.body;
}
}

View File

@ -1,23 +1,65 @@
import {Right} from "../controllers/right";
import {Left} from "../controllers/left";
import {Observable, WebXRSessionManager} from "@babylonjs/core";
import {Observable, Scene, WebXRSessionManager} from "@babylonjs/core";
import log from "loglevel";
import {AdvancedDynamicTexture, InputText} from "@babylonjs/gui";
export type TextEvent = {
text: string;
}
export type InputTextViewOptions = {
scene?: Scene;
xrSession?: WebXRSessionManager;
text?: string;
}
export class InputTextView {
public readonly onTextObservable: Observable<TextEvent> = new Observable<TextEvent>();
private text: string;
private xrSession: WebXRSessionManager;
private readonly scene: Scene;
private readonly xrSession: WebXRSessionManager;
constructor(xrSession: WebXRSessionManager, text: string) {
this.xrSession = xrSession;
this.text = text;
constructor(options: InputTextViewOptions) {
if (options.text) {
this.text = options.text;
}
if (options.xrSession) {
this.xrSession = options.xrSession;
}
if (options.scene) {
this.scene = options.scene;
}
}
public show() {
if (this?.xrSession?.inXRSession) {
this.showXr();
} else {
this.showWeb();
}
}
public showWeb() {
const textInput = new InputText('identity', this.text);
textInput.width = 0.2;
textInput.height = "40px";
textInput.color = "white";
textInput.background = "black";
textInput.focusedBackground = "black";
const advancedTexture = AdvancedDynamicTexture.CreateFullscreenUI("myUI");
advancedTexture.addControl(textInput);
textInput.onKeyboardEventProcessedObservable.add((evt) => {
if (evt.key === 'Enter') {
this.onTextObservable.notifyObservers({text: textInput.text});
textInput.dispose();
advancedTexture.dispose();
}
});
}
private showXr() {
const textInput = document.createElement("input");
textInput.type = "text";
document.body.appendChild(textInput);

View File

@ -1,5 +1,5 @@
import {AbstractMesh, Color3, Observable} from "@babylonjs/core";
import {DiagramEntity} from "./diagramEntity";
import {DiagramEntity} from "../diagram/diagramEntity";
import {AppConfigType} from "../util/appConfigType";
export enum DiagramListingEventType {

View File

@ -1,8 +1,8 @@
import {DiagramListing, DiagramListingEvent, DiagramListingEventType, IPersistenceManager} from "./iPersistenceManager";
import {AbstractMesh, Observable, Vector3} from "@babylonjs/core";
import {DiagramEntity} from "./diagramEntity";
import {DiagramEntity} from "../diagram/diagramEntity";
import Dexie from "dexie";
import {MeshConverter} from "./meshConverter";
import {MeshConverter} from "../diagram/meshConverter";
import log from "loglevel";
import {AppConfigType} from "../util/appConfigType";

View File

@ -5,40 +5,33 @@ export class PeerjsNetworkConnection {
private logger: log.Logger = log.getLogger('PeerjsNetworkConnection');
private dataChannel: P2PDataChannel<any>;
constructor() {
constructor(dataChannel: string, identity: string) {
const config = {
debug: false,
dataChannel: 'default',
dataChannel: dataChannel,
connectionTimeout: 5000,
pingInterval: 4000,
pingTimeout: 8000
}
const data = window.location.search.replace('?', '')
.split('&')
.map((x) => x.split('='));
this.dataChannel = new P2PDataChannel(data[0][1], config);
this.dataChannel = new P2PDataChannel(identity, config);
this.dataChannel.onConnected((peerId) => {
this.logger.debug(peerId, ' connected');
});
this.dataChannel.onMessage((message) => {
if (message.sender !== this.dataChannel.localPeerId) {
this.logger.debug(message.payload, ' received from ', message.sender);
}
});
this.connect();
}
private async connect() {
public connect(host: string) {
try {
const data = window.location.search.replace('?', '')
.split('&')
.map((x) => x.split('='));
const connection = await this.dataChannel.connect(data[1][1]).then(() => {
console.log('Connected');
this.dataChannel.connect(host).then((peerId) => {
this.logger.debug('Broadcasting Join', peerId);
this.dataChannel.broadcast({payload: 'Joined'});
});
this.dataChannel.broadcast({payload: 'Hello World'});
} catch (err) {
this.logger.error(err);
}

View File

@ -17,14 +17,11 @@ import {InputTextView} from "../information/inputTextView";
import {DiaSounds} from "../util/diaSounds";
import {CameraHelper} from "../util/cameraHelper";
import {TextLabel} from "../diagram/textLabel";
import {DiagramShapePhysics} from "../diagram/diagramShapePhysics";
export class EditMenu {
private state: EditMenuState = EditMenuState.NONE;
private manager: GUI3DManager;
private readonly scene: Scene;
private textView: InputTextView;
private textInput: HTMLElement;
private readonly logger: log.Logger = log.getLogger('EditMenu');
private gizmoManager: GizmoManager;
private readonly xr: WebXRExperienceHelper;
@ -158,7 +155,6 @@ export class EditMenu {
if (mesh) {
const newMesh = this.diagramManager.createCopy(mesh, true);
newMesh.setParent(null);
DiagramShapePhysics.applyPhysics(newMesh, this.scene);
}
this.logger.warn('copying not implemented', mesh);
//@todo implement
@ -170,7 +166,7 @@ export class EditMenu {
if (mesh?.metadata?.text) {
text = mesh.metadata.text;
}
const textInput = new InputTextView(this.xr.sessionManager, text);
const textInput = new InputTextView({xrSession: this.xr.sessionManager, text: text});
textInput.show();
textInput.onTextObservable.addOnce((value) => {
this.persist(mesh, value.text);

View File

@ -1,6 +1,5 @@
import {
AbstractMesh,
Angle,
Color3,
InstancedMesh,
Mesh,
@ -16,6 +15,7 @@ import {CameraHelper} from "../util/cameraHelper";
import {AdvancedDynamicTexture, Button3D, ColorPicker, GUI3DManager, StackPanel3D, TextBlock} from "@babylonjs/gui";
import {DiagramManager} from "../diagram/diagramManager";
import {DiagramEventType} from "../diagram/diagramEntity";
import {Controllers} from "../controllers/controllers";
export enum ToolType {
BOX ="#box-template",
@ -60,8 +60,7 @@ export class Toolbox {
handleMaterial.diffuseColor = Color3.FromHexString("#EEEEFF");
handleMaterial.alpha = .5;
handle.material = handleMaterial;
handle.position = CameraHelper.getFrontPosition(2, this.scene);
handle.position.y = 1.6;
handle.position = Vector3.Zero();
this.node.parent = handle;
this.xr = xr;
@ -72,97 +71,15 @@ export class Toolbox {
}
Toolbox.instance = this;
Controllers.controllerObserver.add((evt) => {
if (evt.type == 'y-button') {
if (evt.value == 1) {
this.node.parent.setEnabled(!this.node.parent.isEnabled(false));
CameraHelper.setMenuPosition(this.node.parent as Mesh, this.scene,
new Vector3(0, -.5, 0));
}
}
private buildToolbox() {
this.node.position.y = .1;
this.node.scaling = new Vector3(0.6, 0.6, 0.6);
const color = "#7777FF";
this.buildColor(Color3.FromHexString(color));
const addButton = new Button3D("add-button");
const text = new TextBlock("add-button-text", "Add Color");
text.color = "white";
text.fontSize = "48px";
text.text = "Add Color";
addButton.content = text;
this.addPanel.node.parent = this.node;
this.addPanel.addControl(addButton);
this.addPanel.node.rotation =
new Vector3(
Angle.FromDegrees(0).radians(),
Angle.FromDegrees(180).radians(),
Angle.FromDegrees(0).radians());
this.addPanel.node.scaling = new Vector3(.1, .1,.1);
this.addPanel.position = new Vector3(0, 0, .5);
addButton.onPointerClickObservable.add(() => {
this.buildColor(Color3.Random());
});
}
private calculatePosition(i: number) {
return (i/this.gridsize)-.5-(1/this.gridsize/2);
}
private static WIDGET_SIZE = .1;
private buildColor(color: Color3) {
const width = 1;
const depth = .2;
const material = new StandardMaterial("material-" + color.toHexString(), this.scene);
material.diffuseColor = color;
const mesh = MeshBuilder.CreateBox("toolbox-color-" + color.toHexString(), {width: width, height: .01, depth: depth}, this.scene);
mesh.material = material;
mesh.position.z = this.index++/4;
mesh.parent = this.node;
let i = 0;
for (const tool of enumKeys(ToolType)) {
const newItem = this.buildTool(ToolType[tool], mesh);
if (newItem) {
newItem.position = new Vector3(this.calculatePosition(++i), .1, 0);
}
}
const colorPickerPlane = MeshBuilder
.CreatePlane("myPlane",
{
width: Toolbox.WIDGET_SIZE,
height: Toolbox.WIDGET_SIZE
}, this.scene);
colorPickerPlane.parent = mesh;
colorPickerPlane.position = new Vector3(this.calculatePosition(++i), .1, 0);
const colorPickerTexture = AdvancedDynamicTexture.CreateForMesh(colorPickerPlane, 1024, 1024);
const colorPicker = new ColorPicker("color-picker");
colorPicker.scaleY = 5;
colorPicker.scaleX = 5;
colorPicker.value = color;
colorPicker.onValueChangedObservable.add((value) => {
const oldColor = material.diffuseColor.clone();
const newColor = value.clone();
material.diffuseColor = newColor;
const newColorHex = newColor.toHexString();
material.id = "material-" + newColorHex;
material.name = "material-" + newColorHex;
mesh.id = "toolbox-color-" + newColorHex;
mesh.name = "toolbox-color-" + newColorHex;
this.diagramManager.onDiagramEventObservable.notifyObservers(
{
type: DiagramEventType.CHANGECOLOR,
oldColor: oldColor,
newColor: newColor
}
);
});
colorPickerTexture.addControl(colorPicker);
this.addPanel.position.z += .25;
this.node.position.z -= .125;
}
public updateToolbox(color: string) {
if (this.scene.getMeshById("toolbox-color-" + color)) {
return;
} else {
this.buildColor(Color3.FromHexString(color));
}
}
public buildTool(tool: ToolType, parent: AbstractMesh) {
@ -215,13 +132,105 @@ export class Toolbox {
instance.metadata = {template: tool};
}
instance.parent = parent;
newItem.setEnabled(false)
newItem.setEnabled(false);
newItem.onEnabledStateChangedObservable.add(() => {
instance.setEnabled(false);
});
return instance;
} else {
return null;
}
}
private calculatePosition(i: number) {
return (i/this.gridsize)-.5-(1/this.gridsize/2);
}
private static WIDGET_SIZE = .1;
private buildToolbox() {
this.node.position.y = .1;
this.node.scaling = new Vector3(0.6, 0.6, 0.6);
const color = "#7777FF";
this.buildColor(Color3.FromHexString(color));
const addButton = new Button3D("add-button");
const text = new TextBlock("add-button-text", "Add Color");
text.color = "white";
text.fontSize = "48px";
text.text = "Add Color";
addButton.content = text;
this.addPanel.node.parent = this.node.parent;
this.addPanel.addControl(addButton);
this.addPanel.node.scaling = new Vector3(.1, .1, .1);
this.addPanel.position = new Vector3(-.25, 0, 0);
addButton.onPointerClickObservable.add(() => {
this.buildColor(Color3.Random());
});
this.node.parent.setEnabled(false);
}
public updateToolbox(color: string) {
if (this.scene.getMeshById("toolbox-color-" + color)) {
return;
} else {
this.buildColor(Color3.FromHexString(color));
}
}
private buildColor(color: Color3) {
const width = 1;
const depth = .2;
const material = new StandardMaterial("material-" + color.toHexString(), this.scene);
material.diffuseColor = color;
const mesh = MeshBuilder.CreateBox("toolbox-color-" + color.toHexString(), {width: width, height: .01, depth: depth}, this.scene);
mesh.material = material;
mesh.position.z = this.index++/4;
mesh.parent = this.node;
let i = 0;
for (const tool of enumKeys(ToolType)) {
const newItem = this.buildTool(ToolType[tool], mesh);
if (newItem) {
newItem.position = new Vector3(this.calculatePosition(++i), .1, 0);
}
}
const colorPickerPlane = MeshBuilder
.CreatePlane("colorPickerPlane",
{
width: Toolbox.WIDGET_SIZE,
height: Toolbox.WIDGET_SIZE
}, this.scene);
const colorPickerTexture = AdvancedDynamicTexture.CreateForMesh(colorPickerPlane, 1024, 1024);
colorPickerPlane.parent = mesh;
colorPickerPlane.position = new Vector3(this.calculatePosition(++i), .1, 0);
const colorPicker = new ColorPicker("color-picker");
colorPicker.scaleY = 5;
colorPicker.scaleX = 5;
colorPicker.value = color;
colorPicker.onValueChangedObservable.add((value) => {
const oldColor = material.diffuseColor.clone();
const newColor = value.clone();
material.diffuseColor = newColor;
const newColorHex = newColor.toHexString();
material.id = "material-" + newColorHex;
material.name = "material-" + newColorHex;
mesh.id = "toolbox-color-" + newColorHex;
mesh.name = "toolbox-color-" + newColorHex;
this.diagramManager.onDiagramEventObservable.notifyObservers(
{
type: DiagramEventType.CHANGECOLOR,
oldColor: oldColor,
newColor: newColor
}
);
});
colorPickerTexture.addControl(colorPicker);
}
}
function enumKeys<O extends object, K extends keyof O = keyof O>(obj: O): K[] {
return Object.keys(obj).filter(k => Number.isNaN(+k)) as K[];
}

View File

@ -1,7 +1,7 @@
import {Angle, Vector3} from "@babylonjs/core";
import round from "round";
import log from "loglevel";
import {IPersistenceManager} from "../diagram/persistenceManager";
import {IPersistenceManager} from "../integration/iPersistenceManager";
import {AppConfigType} from "./appConfigType";
export type SnapValue = {

View File

@ -7,9 +7,10 @@ export class CameraHelper {
return scene.activeCamera.globalPosition.add(offset);
}
public static setMenuPosition(node: TransformNode, scene: Scene) {
public static setMenuPosition(node: TransformNode, scene: Scene, offset: Vector3 = Vector3.Zero()) {
node.position =
CameraHelper.getFrontPosition(2, scene);
node.position.addInPlace(offset);
node.lookAt(scene.activeCamera.globalPosition);
node.rotation.y = node.rotation.y + Math.PI;
node.position.y += .2;