Refactored into pure functions for key diagram manager functionality.
This commit is contained in:
parent
be8306141f
commit
c945423954
@ -46,10 +46,11 @@ export class App {
|
||||
const scene = new Scene(engine);
|
||||
|
||||
const persistenceManager = new IndexdbPersistenceManager("diagram");
|
||||
|
||||
const controllers = new Controllers();
|
||||
const toolbox = new Toolbox(scene, controllers);
|
||||
|
||||
const diagramManager = new DiagramManager(scene, controllers, toolbox);
|
||||
|
||||
diagramManager.setPersistenceManager(persistenceManager);
|
||||
const config = new AppConfig(persistenceManager);
|
||||
const environment = new CustomEnvironment(scene, "default", config);
|
||||
|
||||
28
src/diagram/diagramEntityActionManager.ts
Normal file
28
src/diagram/diagramEntityActionManager.ts
Normal file
@ -0,0 +1,28 @@
|
||||
import {ActionManager, ExecuteCodeAction, PlaySoundAction, Scene} from "@babylonjs/core";
|
||||
import {DiaSounds} from "../util/diaSounds";
|
||||
import {Controllers} from "../controllers/controllers";
|
||||
import log from "loglevel";
|
||||
|
||||
export class DiagramEntityActionManager {
|
||||
_actionManager: ActionManager;
|
||||
private readonly logger = log.getLogger('DiagramEntityActionManager');
|
||||
|
||||
constructor(scene: Scene, sounds: DiaSounds, controllers: Controllers) {
|
||||
this._actionManager = new ActionManager(scene);
|
||||
this._actionManager.registerAction(
|
||||
new PlaySoundAction(ActionManager.OnPointerOverTrigger, sounds.tick));
|
||||
this._actionManager.registerAction(
|
||||
new ExecuteCodeAction(ActionManager.OnPointerOverTrigger, (evt) => {
|
||||
controllers.controllerObserver.notifyObservers({
|
||||
type: 'pulse',
|
||||
gripId: evt?.additionalData?.pickResult?.gripTransform?.id
|
||||
})
|
||||
this.logger.debug(evt);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
public get manager() {
|
||||
return this._actionManager;
|
||||
}
|
||||
}
|
||||
95
src/diagram/diagramEventHandler.ts
Normal file
95
src/diagram/diagramEventHandler.ts
Normal file
@ -0,0 +1,95 @@
|
||||
import {DiagramEvent, DiagramEventType} from "./diagramEntity";
|
||||
import log from "loglevel";
|
||||
import {MeshConverter} from "./meshConverter";
|
||||
import {applyPhysics} from "./functions/diagramShapePhysics";
|
||||
import {ActionManager, Color3, PhysicsMotionType, Scene} from "@babylonjs/core";
|
||||
import {TextLabel} from "./textLabel";
|
||||
import {Toolbox} from "../toolbox/toolbox";
|
||||
import {DiaSounds} from "../util/diaSounds";
|
||||
import {IPersistenceManager} from "../integration/iPersistenceManager";
|
||||
|
||||
|
||||
export function diagramEventHandler(event: DiagramEvent,
|
||||
scene: Scene,
|
||||
toolbox: Toolbox,
|
||||
physicsEnabled: boolean,
|
||||
actionManager: ActionManager,
|
||||
sounds: DiaSounds,
|
||||
persistenceManager: IPersistenceManager) {
|
||||
const entity = event.entity;
|
||||
let mesh;
|
||||
if (entity) {
|
||||
mesh = scene.getMeshById(entity.id);
|
||||
}
|
||||
if (!mesh && event?.entity?.template) {
|
||||
const toolMesh = scene.getMeshById("tool-" + event.entity.template + "-" + event.entity.color);
|
||||
if (!toolMesh && event.type != DiagramEventType.CHANGECOLOR) {
|
||||
log.debug('no mesh found for ' + event.entity.template + "-" + event.entity.color, 'adding it');
|
||||
toolbox.updateToolbox(event.entity.color);
|
||||
}
|
||||
mesh = MeshConverter.fromDiagramEntity(event.entity, scene);
|
||||
if (mesh) {
|
||||
mesh.actionManager = actionManager;
|
||||
if (physicsEnabled) {
|
||||
applyPhysics(sounds, mesh, scene, PhysicsMotionType.DYNAMIC);
|
||||
}
|
||||
}
|
||||
}
|
||||
switch (event.type) {
|
||||
case DiagramEventType.CLEAR:
|
||||
break;
|
||||
case DiagramEventType.DROPPED:
|
||||
break;
|
||||
case DiagramEventType.DROP:
|
||||
if (mesh.metadata.template.indexOf('#') > -1) {
|
||||
persistenceManager.modify(mesh);
|
||||
TextLabel.updateTextNode(mesh, entity.text);
|
||||
}
|
||||
|
||||
break;
|
||||
case DiagramEventType.ADD:
|
||||
persistenceManager.add(mesh);
|
||||
if (!mesh.actionManager) {
|
||||
mesh.actionManager = actionManager;
|
||||
}
|
||||
if (physicsEnabled) {
|
||||
applyPhysics(sounds, mesh, scene);
|
||||
}
|
||||
|
||||
break;
|
||||
case DiagramEventType.MODIFY:
|
||||
persistenceManager.modify(mesh);
|
||||
if (physicsEnabled) {
|
||||
applyPhysics(sounds, mesh, scene);
|
||||
}
|
||||
|
||||
break;
|
||||
case DiagramEventType.CHANGECOLOR:
|
||||
if (!event.oldColor) {
|
||||
if (!event.newColor) {
|
||||
persistenceManager.changeColor(null, Color3.FromHexString(event.entity.color));
|
||||
this.logger.info("Received color change event, sending entity color as new color");
|
||||
} else {
|
||||
this.logger.info("Received color change event, no old color, sending new color");
|
||||
persistenceManager.changeColor(null, event.newColor);
|
||||
}
|
||||
} else {
|
||||
if (event.newColor) {
|
||||
this.logger.info("changing color from " + event.oldColor + " to " + event.newColor);
|
||||
persistenceManager.changeColor(event.oldColor, event.newColor);
|
||||
} else {
|
||||
this.logger.error("changing color from " + event.oldColor + ", but no new color found");
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
case DiagramEventType.REMOVE:
|
||||
if (mesh) {
|
||||
persistenceManager.remove(mesh)
|
||||
mesh?.physicsBody?.dispose();
|
||||
mesh.dispose();
|
||||
sounds.exit.play();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -1,16 +1,4 @@
|
||||
import {
|
||||
AbstractMesh,
|
||||
ActionManager,
|
||||
Color3,
|
||||
ExecuteCodeAction,
|
||||
InstancedMesh,
|
||||
Mesh,
|
||||
Observable,
|
||||
PhysicsMotionType,
|
||||
PlaySoundAction,
|
||||
Scene,
|
||||
Vector3
|
||||
} from "@babylonjs/core";
|
||||
import {AbstractMesh, Color3, InstancedMesh, Mesh, Observable, PhysicsMotionType, Scene} from "@babylonjs/core";
|
||||
import {DiagramEntity, DiagramEvent, DiagramEventType} from "./diagramEntity";
|
||||
import {IPersistenceManager} from "../integration/iPersistenceManager";
|
||||
import {MeshConverter} from "./meshConverter";
|
||||
@ -18,9 +6,13 @@ import log from "loglevel";
|
||||
import {Controllers} from "../controllers/controllers";
|
||||
import {DiaSounds} from "../util/diaSounds";
|
||||
import {AppConfig} from "../util/appConfig";
|
||||
import {TextLabel} from "./textLabel";
|
||||
import {Toolbox} from "../toolbox/toolbox";
|
||||
import {DiagramShapePhysics} from "./diagramShapePhysics";
|
||||
import {PresentationManager} from "./presentationManager";
|
||||
import {DiagramEntityActionManager} from "./diagramEntityActionManager";
|
||||
import {diagramEventHandler} from "./diagramEventHandler";
|
||||
import {deepCopy} from "../util/deepCopy";
|
||||
import {applyPhysics} from "./functions/diagramShapePhysics";
|
||||
import {applyScaling} from "./functions/applyScaling";
|
||||
|
||||
|
||||
export class DiagramManager {
|
||||
@ -29,25 +21,20 @@ export class DiagramManager {
|
||||
private persistenceManager: IPersistenceManager = null;
|
||||
private readonly toolbox: Toolbox;
|
||||
private readonly scene: Scene;
|
||||
private sounds: DiaSounds;
|
||||
private readonly sounds: DiaSounds;
|
||||
private readonly controllers: Controllers;
|
||||
private readonly diagramEntityActionManager: DiagramEntityActionManager
|
||||
private presentationManager: PresentationManager;
|
||||
private _config: AppConfig;
|
||||
|
||||
constructor(scene: Scene, controllers: Controllers, toolbox: Toolbox) {
|
||||
this.sounds = new DiaSounds(scene);
|
||||
this.scene = scene;
|
||||
this.toolbox = toolbox;
|
||||
this.controllers = controllers;
|
||||
this.actionManager = new ActionManager(this.scene);
|
||||
this.actionManager.registerAction(
|
||||
new PlaySoundAction(ActionManager.OnPointerOverTrigger, this.sounds.tick));
|
||||
this.actionManager.registerAction(
|
||||
new ExecuteCodeAction(ActionManager.OnPointerOverTrigger, (evt) => {
|
||||
this.controllers.controllerObserver.notifyObservers({
|
||||
type: 'pulse',
|
||||
gripId: evt?.additionalData?.pickResult?.gripTransform?.id
|
||||
})
|
||||
this.logger.debug(evt);
|
||||
})
|
||||
);
|
||||
this.presentationManager = new PresentationManager(this.scene);
|
||||
this.diagramEntityActionManager = new DiagramEntityActionManager(this.scene, this.sounds, this.controllers);
|
||||
|
||||
if (this.onDiagramEventObservable.hasObservers()) {
|
||||
this.logger.warn("onDiagramEventObservable already has Observers, you should be careful");
|
||||
}
|
||||
@ -56,6 +43,7 @@ export class DiagramManager {
|
||||
}, -1, true, this, false);
|
||||
this.onDiagramEventObservable.add(this.onDiagramEvent, -1, true, this);
|
||||
this.logger.debug("DiagramManager constructed");
|
||||
|
||||
scene.onMeshRemovedObservable.add((mesh) => {
|
||||
if (mesh?.metadata?.template) {
|
||||
if (mesh.metadata.template != '#connection-template') {
|
||||
@ -73,29 +61,14 @@ export class DiagramManager {
|
||||
});
|
||||
}
|
||||
|
||||
private _config: AppConfig;
|
||||
|
||||
private getPersistenceManager(): IPersistenceManager {
|
||||
if (!this.persistenceManager) {
|
||||
this.logger.warn("persistenceManager not set");
|
||||
return null;
|
||||
}
|
||||
return this.persistenceManager;
|
||||
}
|
||||
|
||||
private readonly actionManager: ActionManager;
|
||||
private controllers: Controllers;
|
||||
|
||||
public get config(): AppConfig {
|
||||
return this._config;
|
||||
}
|
||||
|
||||
public setPersistenceManager(persistenceManager: IPersistenceManager) {
|
||||
this.persistenceManager = persistenceManager;
|
||||
this._config = new AppConfig(persistenceManager);
|
||||
this.persistenceManager.updateObserver.add(this.onRemoteEvent, -1, true, this);
|
||||
}
|
||||
|
||||
public createCopy(mesh: AbstractMesh, copy: boolean = false): AbstractMesh {
|
||||
let newMesh;
|
||||
if (!mesh.isAnInstance) {
|
||||
@ -103,152 +76,43 @@ export class DiagramManager {
|
||||
} else {
|
||||
newMesh = new InstancedMesh("new", (mesh as InstancedMesh).sourceMesh);
|
||||
}
|
||||
newMesh.actionManager = this.actionManager;
|
||||
newMesh.actionManager = this.diagramEntityActionManager.manager;
|
||||
newMesh.position = mesh.absolutePosition.clone();
|
||||
if (mesh.absoluteRotationQuaternion) {
|
||||
newMesh.rotation = mesh.absoluteRotationQuaternion.toEulerAngles().clone();
|
||||
} else {
|
||||
this.logger.error("no rotation quaternion");
|
||||
}
|
||||
if (copy) {
|
||||
newMesh.scaling = mesh.scaling.clone();
|
||||
} else {
|
||||
if (this.config.current?.createSnap) {
|
||||
newMesh.scaling.x = this.config.current?.createSnap;
|
||||
newMesh.scaling.y = this.config.current?.createSnap;
|
||||
newMesh.scaling.z = this.config.current?.createSnap;
|
||||
} else {
|
||||
newMesh.scaling = Vector3.One();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
applyScaling(mesh, newMesh, copy, this.config.current?.createSnap);
|
||||
newMesh.material = mesh.material;
|
||||
|
||||
newMesh.metadata = this.deepCopy(mesh.metadata);
|
||||
newMesh.metadata = deepCopy(mesh.metadata);
|
||||
if (this.config.current?.physicsEnabled) {
|
||||
DiagramShapePhysics.applyPhysics(this.sounds, newMesh, this.scene);
|
||||
applyPhysics(this.sounds, newMesh, this.scene);
|
||||
}
|
||||
|
||||
this.persistenceManager.add(newMesh);
|
||||
return newMesh;
|
||||
}
|
||||
|
||||
private deepCopy<T, U = T extends Array<infer V> ? V : never>(source: T): T {
|
||||
if (Array.isArray(source)) {
|
||||
return source.map(item => (this.deepCopy(item))) as T & U[]
|
||||
}
|
||||
if (source instanceof Date) {
|
||||
return new Date(source.getTime()) as T & Date
|
||||
}
|
||||
if (source && typeof source === 'object') {
|
||||
return (Object.getOwnPropertyNames(source) as (keyof T)[]).reduce<T>((o, prop) => {
|
||||
Object.defineProperty(o, prop, Object.getOwnPropertyDescriptor(source, prop)!)
|
||||
o[prop] = this.deepCopy(source[prop])
|
||||
return o
|
||||
}, Object.create(Object.getPrototypeOf(source)))
|
||||
}
|
||||
return source
|
||||
}
|
||||
|
||||
private onRemoteEvent(event: DiagramEntity) {
|
||||
this.logger.debug(event);
|
||||
const toolMesh = this.scene.getMeshById("tool-" + event.template + "-" + event.color);
|
||||
if (!toolMesh && (event.template != '#connection-template')) {
|
||||
log.debug('no mesh found for ' + event.template + "-" + event.color, 'adding it');
|
||||
//this.getPersistenceManager()?.changeColor(null, Color3.FromHexString(event.color));
|
||||
this.toolbox.updateToolbox(event.color);
|
||||
}
|
||||
const mesh = MeshConverter.fromDiagramEntity(event, this.scene);
|
||||
mesh.actionManager = this.actionManager;
|
||||
mesh.actionManager = this.diagramEntityActionManager.manager;
|
||||
if (event.parent) {
|
||||
mesh.parent = this.scene.getMeshById(event.parent);
|
||||
}
|
||||
if (this.config.current?.physicsEnabled) {
|
||||
DiagramShapePhysics.applyPhysics(this.sounds, mesh, this.scene, PhysicsMotionType.DYNAMIC);
|
||||
applyPhysics(this.sounds, mesh, this.scene, PhysicsMotionType.DYNAMIC);
|
||||
}
|
||||
}
|
||||
|
||||
private onDiagramEvent(event: DiagramEvent) {
|
||||
this.logger.debug(event.type);
|
||||
const entity = event.entity;
|
||||
let mesh;
|
||||
if (entity) {
|
||||
mesh = this.scene.getMeshById(entity.id);
|
||||
}
|
||||
if (!mesh && event?.entity?.template) {
|
||||
const toolMesh = this.scene.getMeshById("tool-" + event.entity.template + "-" + event.entity.color);
|
||||
if (!toolMesh && event.type != DiagramEventType.CHANGECOLOR) {
|
||||
log.debug('no mesh found for ' + event.entity.template + "-" + event.entity.color, 'adding it');
|
||||
this.toolbox.updateToolbox(event.entity.color);
|
||||
}
|
||||
mesh = MeshConverter.fromDiagramEntity(event.entity, this.scene);
|
||||
if (mesh) {
|
||||
mesh.actionManager = this.actionManager;
|
||||
if (this.config.current.physicsEnabled) {
|
||||
DiagramShapePhysics.applyPhysics(this.sounds, mesh, this.scene, PhysicsMotionType.DYNAMIC);
|
||||
}
|
||||
}
|
||||
}
|
||||
switch (event.type) {
|
||||
case DiagramEventType.CLEAR:
|
||||
break;
|
||||
case DiagramEventType.DROPPED:
|
||||
break;
|
||||
case DiagramEventType.DROP:
|
||||
if (mesh.metadata.template.indexOf('#') > -1) {
|
||||
this.getPersistenceManager()?.modify(mesh);
|
||||
TextLabel.updateTextNode(mesh, entity.text);
|
||||
}
|
||||
|
||||
break;
|
||||
case DiagramEventType.ADD:
|
||||
this.getPersistenceManager()?.add(mesh);
|
||||
if (!mesh.actionManager) {
|
||||
mesh.actionManager = this.actionManager;
|
||||
}
|
||||
if (this.config.current.physicsEnabled) {
|
||||
DiagramShapePhysics
|
||||
.applyPhysics(this.sounds, mesh, this.scene);
|
||||
}
|
||||
|
||||
break;
|
||||
case DiagramEventType.MODIFY:
|
||||
this.getPersistenceManager()?.modify(mesh);
|
||||
if (this.config.current.physicsEnabled) {
|
||||
DiagramShapePhysics
|
||||
.applyPhysics(this.sounds, mesh, this.scene);
|
||||
}
|
||||
|
||||
break;
|
||||
case DiagramEventType.CHANGECOLOR:
|
||||
if (!event.oldColor) {
|
||||
if (!event.newColor) {
|
||||
this.getPersistenceManager()?.changeColor(null, Color3.FromHexString(event.entity.color));
|
||||
this.logger.info("Recieved color change event, sending entity color as new color");
|
||||
} else {
|
||||
this.logger.info("Recieved color change event, no old color, sending new color");
|
||||
this.getPersistenceManager()?.changeColor(null, event.newColor);
|
||||
}
|
||||
} else {
|
||||
if (event.newColor) {
|
||||
this.logger.info("changing color from " + event.oldColor + " to " + event.newColor);
|
||||
this.getPersistenceManager()?.changeColor(event.oldColor, event.newColor);
|
||||
} else {
|
||||
this.logger.error("changing color from " + event.oldColor + ", but no new color found");
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
case DiagramEventType.REMOVE:
|
||||
if (mesh) {
|
||||
this.getPersistenceManager()?.remove(mesh)
|
||||
mesh?.physicsBody?.dispose();
|
||||
mesh.dispose();
|
||||
this.sounds.exit.play();
|
||||
|
||||
}
|
||||
break;
|
||||
}
|
||||
diagramEventHandler(
|
||||
event, this.scene, this.toolbox, this.config.current.physicsEnabled,
|
||||
this.diagramEntityActionManager.manager, this.sounds, this.persistenceManager);
|
||||
}
|
||||
}
|
||||
@ -1,74 +0,0 @@
|
||||
import log from "loglevel";
|
||||
import {DiaSounds} from "../util/diaSounds";
|
||||
import {AbstractMesh, PhysicsAggregate, PhysicsMotionType, PhysicsShapeType, Scene} from "@babylonjs/core";
|
||||
|
||||
export class DiagramShapePhysics {
|
||||
private static logger: log.Logger = log.getLogger('DiagramShapePhysics');
|
||||
|
||||
public static applyPhysics(sounds: DiaSounds, mesh: AbstractMesh, scene: Scene, motionType?: PhysicsMotionType) {
|
||||
if (!mesh?.metadata?.template) {
|
||||
this.logger.error("applyPhysics: mesh.metadata.template is null", mesh);
|
||||
return;
|
||||
}
|
||||
if (mesh.metadata.template == '#connection-template') {
|
||||
return;
|
||||
}
|
||||
if (!scene) {
|
||||
this.logger.error("applyPhysics: mesh or scene is null");
|
||||
return;
|
||||
}
|
||||
|
||||
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);
|
||||
const body = aggregate.body;
|
||||
body.setLinearDamping(1.95);
|
||||
body.setAngularDamping(1.99);
|
||||
|
||||
if (motionType) {
|
||||
body
|
||||
.setMotionType(motionType);
|
||||
} else {
|
||||
if (mesh.parent) {
|
||||
body
|
||||
.setMotionType(PhysicsMotionType.ANIMATED);
|
||||
} else {
|
||||
body
|
||||
.setMotionType(PhysicsMotionType.DYNAMIC);
|
||||
}
|
||||
}
|
||||
body.setCollisionCallbackEnabled(true);
|
||||
body.getCollisionObservable().add((event) => {
|
||||
|
||||
if (event.impulse < 10 && event.impulse > 1) {
|
||||
const sound = sounds.bounce;
|
||||
sound.setVolume(event.impulse / 10);
|
||||
sound.attachToMesh(mesh);
|
||||
sound.play();
|
||||
}
|
||||
}, -1, false, this);
|
||||
//body.setMotionType(PhysicsMotionType.ANIMATED);
|
||||
body.setLinearDamping(.95);
|
||||
body.setAngularDamping(.99);
|
||||
body.setGravityFactor(0);
|
||||
|
||||
}
|
||||
}
|
||||
16
src/diagram/functions/applyScaling.ts
Normal file
16
src/diagram/functions/applyScaling.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import {AbstractMesh, Vector3} from "@babylonjs/core";
|
||||
|
||||
export function applyScaling(oldMesh: AbstractMesh,
|
||||
newMesh: AbstractMesh,
|
||||
copy: boolean,
|
||||
snap: number) {
|
||||
if (copy) {
|
||||
newMesh.scaling = oldMesh.scaling.clone();
|
||||
} else {
|
||||
if (snap) {
|
||||
newMesh.scaling.set(snap, snap, snap);
|
||||
} else {
|
||||
newMesh.scaling = Vector3.One();
|
||||
}
|
||||
}
|
||||
}
|
||||
70
src/diagram/functions/diagramShapePhysics.ts
Normal file
70
src/diagram/functions/diagramShapePhysics.ts
Normal file
@ -0,0 +1,70 @@
|
||||
import {DiaSounds} from "../../util/diaSounds";
|
||||
import {AbstractMesh, PhysicsAggregate, PhysicsMotionType, PhysicsShapeType, Scene} from "@babylonjs/core";
|
||||
import log from "loglevel";
|
||||
|
||||
export function applyPhysics(sounds: DiaSounds, mesh: AbstractMesh, scene: Scene, motionType?: PhysicsMotionType) {
|
||||
const logger = log.getLogger('DiagramShapePhysics');
|
||||
if (!mesh?.metadata?.template) {
|
||||
logger.error("applyPhysics: mesh.metadata.template is null", mesh);
|
||||
return;
|
||||
}
|
||||
if (mesh.metadata.template == '#connection-template') {
|
||||
return;
|
||||
}
|
||||
if (!scene) {
|
||||
logger.error("applyPhysics: mesh or scene is null");
|
||||
return;
|
||||
}
|
||||
|
||||
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);
|
||||
const body = aggregate.body;
|
||||
body.setLinearDamping(1.95);
|
||||
body.setAngularDamping(1.99);
|
||||
|
||||
if (motionType) {
|
||||
body
|
||||
.setMotionType(motionType);
|
||||
} else {
|
||||
if (mesh.parent) {
|
||||
body
|
||||
.setMotionType(PhysicsMotionType.ANIMATED);
|
||||
} else {
|
||||
body
|
||||
.setMotionType(PhysicsMotionType.DYNAMIC);
|
||||
}
|
||||
}
|
||||
body.setCollisionCallbackEnabled(true);
|
||||
body.getCollisionObservable().add((event) => {
|
||||
|
||||
if (event.impulse < 10 && event.impulse > 1) {
|
||||
const sound = sounds.bounce;
|
||||
sound.setVolume(event.impulse / 10);
|
||||
sound.attachToMesh(mesh);
|
||||
sound.play();
|
||||
}
|
||||
}, -1, false, this);
|
||||
//body.setMotionType(PhysicsMotionType.ANIMATED);
|
||||
body.setLinearDamping(.95);
|
||||
body.setAngularDamping(.99);
|
||||
body.setGravityFactor(0);
|
||||
}
|
||||
0
src/diagram/functions/fromDiagramEntity.ts
Normal file
0
src/diagram/functions/fromDiagramEntity.ts
Normal file
0
src/diagram/functions/toDiagramEntity.ts
Normal file
0
src/diagram/functions/toDiagramEntity.ts
Normal file
53
src/diagram/presentationManager.ts
Normal file
53
src/diagram/presentationManager.ts
Normal file
@ -0,0 +1,53 @@
|
||||
import {PresentationStep} from "./presentationStep";
|
||||
import log, {Logger} from "loglevel";
|
||||
import {Scene} from "@babylonjs/core";
|
||||
|
||||
export class PresentationManager {
|
||||
_currentStep: PresentationStep = null;
|
||||
private scene: Scene;
|
||||
private logger: Logger = log.getLogger("PresentationManager");
|
||||
|
||||
constructor(scene: Scene) {
|
||||
this.scene = scene;
|
||||
}
|
||||
|
||||
_steps: PresentationStep[] = [];
|
||||
|
||||
public get steps(): PresentationStep[] {
|
||||
return this._steps;
|
||||
}
|
||||
|
||||
public addStep(): PresentationStep {
|
||||
const step = new PresentationStep();
|
||||
this._currentStep = step;
|
||||
if (this._steps.length > 0) {
|
||||
this._steps[this._steps.length - 1].next = step;
|
||||
} else {
|
||||
this.scene.getActiveMeshes().forEach((mesh) => {
|
||||
if (mesh.metadata?.template) {
|
||||
step.entities.push({
|
||||
entity: mesh,
|
||||
endPosition: mesh.position.clone(),
|
||||
endRotation: mesh.rotation.clone(),
|
||||
endScaling: mesh.scaling.clone()
|
||||
})
|
||||
step.duration = 1;
|
||||
}
|
||||
});
|
||||
}
|
||||
this._steps.push(step);
|
||||
return step;
|
||||
}
|
||||
|
||||
public play() {
|
||||
this._currentStep.play();
|
||||
if (this._currentStep.next) {
|
||||
this._currentStep = this._currentStep.next;
|
||||
}
|
||||
}
|
||||
|
||||
public reset() {
|
||||
this._currentStep = this._steps[0];
|
||||
this._steps[0].play();
|
||||
}
|
||||
}
|
||||
45
src/diagram/presentationStep.ts
Normal file
45
src/diagram/presentationStep.ts
Normal file
@ -0,0 +1,45 @@
|
||||
import {AbstractMesh, Animation, Vector3} from "@babylonjs/core";
|
||||
|
||||
export type EntityTransform = {
|
||||
entity: AbstractMesh,
|
||||
endPosition?: Vector3,
|
||||
endRotation?: Vector3,
|
||||
endScaling?: Vector3
|
||||
}
|
||||
|
||||
export class PresentationStep {
|
||||
public id: string;
|
||||
public name: string;
|
||||
public duration: number = 2;
|
||||
public entities: Array<EntityTransform> = [];
|
||||
public next: PresentationStep;
|
||||
private readonly fps: number = 30;
|
||||
|
||||
private get endFrame(): number {
|
||||
return this.fps * this.duration;
|
||||
}
|
||||
|
||||
public play() {
|
||||
this.entities.forEach((entityTransform) => {
|
||||
if (entityTransform.endPosition) {
|
||||
|
||||
const transform = new Animation("transform", "position",
|
||||
this.fps, Animation.ANIMATIONTYPE_VECTOR3, Animation.ANIMATIONLOOPMODE_CONSTANT);
|
||||
const keyframes = [
|
||||
{frame: 0, value: entityTransform.entity.position},
|
||||
{frame: this.endFrame, value: entityTransform.endPosition}
|
||||
]
|
||||
transform.setKeys(keyframes);
|
||||
entityTransform.entity.animations.push(transform);
|
||||
entityTransform.entity.getScene().beginAnimation(entityTransform.entity, 0, this.endFrame, false);
|
||||
}
|
||||
if (entityTransform.endRotation) {
|
||||
//entityTransform.entity.rotation = entityTransform.endRotation;
|
||||
}
|
||||
if (entityTransform.endScaling) {
|
||||
//entityTransform.entity.scaling = entityTransform.endScaling;
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
@ -1,7 +1,7 @@
|
||||
import {Scene, WebXRExperienceHelper} from "@babylonjs/core";
|
||||
import {Controllers} from "../controllers/controllers";
|
||||
|
||||
export class BaseMenu {
|
||||
export class AbstractMenu {
|
||||
protected scene: Scene;
|
||||
protected xr: WebXRExperienceHelper;
|
||||
protected controllers: Controllers;
|
||||
@ -12,4 +12,8 @@ export class BaseMenu {
|
||||
this.controllers = controllers;
|
||||
}
|
||||
|
||||
public toggle() {
|
||||
throw new Error("AbstractMenu.toggle() not implemented");
|
||||
}
|
||||
|
||||
}
|
||||
@ -5,9 +5,9 @@ import log from "loglevel";
|
||||
import {AppConfig} from "../util/appConfig";
|
||||
import {Controllers} from "../controllers/controllers";
|
||||
import {DiaSounds} from "../util/diaSounds";
|
||||
import {BaseMenu} from "./baseMenu";
|
||||
import {AbstractMenu} from "./abstractMenu";
|
||||
|
||||
export class ConfigMenu extends BaseMenu {
|
||||
export class ConfigMenu extends AbstractMenu {
|
||||
private sounds: DiaSounds;
|
||||
private configPlane: AbstractMesh = null;
|
||||
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
import {AbstractMesh, MeshBuilder, Scene, WebXRExperienceHelper} from "@babylonjs/core";
|
||||
import {Controllers} from "../controllers/controllers";
|
||||
import {BaseMenu} from "./baseMenu";
|
||||
import {AbstractMenu} from "./abstractMenu";
|
||||
import {AdvancedDynamicTexture, Grid, TextBlock} from "@babylonjs/gui";
|
||||
import {CameraHelper} from "../util/cameraHelper";
|
||||
|
||||
export class IntegrationMenu extends BaseMenu {
|
||||
export class IntegrationMenu extends AbstractMenu {
|
||||
private plane: AbstractMesh = null;
|
||||
|
||||
constructor(scene: Scene, xr: WebXRExperienceHelper, controllers: Controllers) {
|
||||
|
||||
@ -16,17 +16,16 @@ import {AdvancedDynamicTexture, Button3D, ColorPicker, GUI3DManager, StackPanel3
|
||||
import {Controllers} from "../controllers/controllers";
|
||||
|
||||
export enum ToolType {
|
||||
BOX ="#box-template",
|
||||
Sphere="#sphere-template",
|
||||
Cylinder="#cylinder-template",
|
||||
Cone ="#cone-template",
|
||||
PLANE ="#plane-template",
|
||||
OBJECT ="#object-template",
|
||||
BOX = "#box-template",
|
||||
SPHERE = "#sphere-template",
|
||||
CYLINDER = "#cylinder-template",
|
||||
CONE = "#cone-template",
|
||||
PLANE = "#plane-template",
|
||||
OBJECT = "#object-template",
|
||||
}
|
||||
|
||||
export class Toolbox {
|
||||
private index = 0;
|
||||
public static instance: Toolbox;
|
||||
private readonly scene: Scene;
|
||||
public readonly node: TransformNode;
|
||||
private readonly manager: GUI3DManager;
|
||||
@ -60,7 +59,6 @@ export class Toolbox {
|
||||
|
||||
this.buildToolbox();
|
||||
|
||||
Toolbox.instance = this;
|
||||
if (!this.xObserver) {
|
||||
this.xObserver = this.controllers.controllerObserver.add((evt) => {
|
||||
if (evt.type == 'x-button') {
|
||||
@ -75,72 +73,68 @@ export class Toolbox {
|
||||
}
|
||||
|
||||
public buildTool(tool: ToolType, parent: AbstractMesh) {
|
||||
let newItem: Mesh;
|
||||
const id = this.toolId(tool, (parent.material as StandardMaterial).diffuseColor);
|
||||
const material = parent.material;
|
||||
const toolname = "tool-" + id;
|
||||
switch (tool) {
|
||||
case ToolType.BOX:
|
||||
newItem = MeshBuilder.CreateBox(toolname, {width: 1, height: 1, depth: 1}, this.scene);
|
||||
break;
|
||||
case ToolType.Sphere:
|
||||
newItem = MeshBuilder.CreateSphere(toolname, {diameter: 1}, this.scene);
|
||||
break;
|
||||
case ToolType.Cylinder:
|
||||
newItem = MeshBuilder.CreateCylinder(toolname, {height: 1, diameter: 1}, this.scene);
|
||||
break;
|
||||
case ToolType.Cone:
|
||||
newItem = MeshBuilder.CreateCylinder(toolname, {diameterTop: 0, height: 1, diameterBottom: 1}, this.scene);
|
||||
break;
|
||||
case ToolType.PLANE:
|
||||
newItem = MeshBuilder.CreatePlane(toolname, {width: 1, height: 1}, this.scene);
|
||||
break;
|
||||
case ToolType.OBJECT:
|
||||
break;
|
||||
}
|
||||
if (newItem) {
|
||||
newItem.material = material;
|
||||
newItem.id = toolname
|
||||
if (tool === ToolType.PLANE) {
|
||||
newItem.material.backFaceCulling = false;
|
||||
}
|
||||
|
||||
newItem.scaling = new Vector3(Toolbox.WIDGET_SIZE,
|
||||
Toolbox.WIDGET_SIZE,
|
||||
Toolbox.WIDGET_SIZE);
|
||||
newItem.parent = parent;
|
||||
if (!newItem.material) {
|
||||
newItem.material = parent.material;
|
||||
}
|
||||
|
||||
if (newItem.metadata) {
|
||||
newItem.metadata.template = tool;
|
||||
} else {
|
||||
newItem.metadata = {template: tool};
|
||||
}
|
||||
const instance = new InstancedMesh("instance-" + id, newItem);
|
||||
if (instance.metadata) {
|
||||
instance.metadata.template = tool;
|
||||
} else {
|
||||
instance.metadata = {template: tool};
|
||||
}
|
||||
instance.parent = parent;
|
||||
newItem.setEnabled(false);
|
||||
newItem.onEnabledStateChangedObservable.add(() => {
|
||||
instance.setEnabled(false);
|
||||
});
|
||||
return instance;
|
||||
} else {
|
||||
const newItem = this.buildMesh(tool, `tool-${id}`);
|
||||
if (!newItem) {
|
||||
return null;
|
||||
}
|
||||
newItem.material = parent.material;
|
||||
if (tool === ToolType.PLANE) {
|
||||
newItem.material.backFaceCulling = false;
|
||||
}
|
||||
newItem.scaling = new Vector3(Toolbox.WIDGET_SIZE,
|
||||
Toolbox.WIDGET_SIZE,
|
||||
Toolbox.WIDGET_SIZE);
|
||||
newItem.parent = parent;
|
||||
newItem.metadata = {template: tool};
|
||||
const instance = new InstancedMesh("instance-" + id, newItem);
|
||||
instance.metadata = {template: tool};
|
||||
instance.parent = parent;
|
||||
newItem.setEnabled(false);
|
||||
newItem.onEnabledStateChangedObservable.add(() => {
|
||||
instance.setEnabled(false);
|
||||
});
|
||||
return instance;
|
||||
|
||||
}
|
||||
|
||||
private buildMesh(type: ToolType, toolname: string): Mesh {
|
||||
switch (type) {
|
||||
case ToolType.BOX:
|
||||
return MeshBuilder.CreateBox(toolname, {width: 1, height: 1, depth: 1}, this.scene);
|
||||
break;
|
||||
case ToolType.SPHERE:
|
||||
return MeshBuilder.CreateSphere(toolname, {diameter: 1}, this.scene);
|
||||
break;
|
||||
case ToolType.CYLINDER:
|
||||
return MeshBuilder.CreateCylinder(toolname, {height: 1, diameter: 1}, this.scene);
|
||||
break;
|
||||
case ToolType.CONE:
|
||||
return MeshBuilder.CreateCylinder(toolname, {
|
||||
diameterTop: 0,
|
||||
height: 1,
|
||||
diameterBottom: 1
|
||||
}, this.scene);
|
||||
break;
|
||||
case ToolType.PLANE:
|
||||
return MeshBuilder.CreatePlane(toolname, {width: 1, height: 1}, this.scene);
|
||||
break;
|
||||
case ToolType.OBJECT:
|
||||
return null;
|
||||
break;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private toolId(tool: ToolType, color: Color3) {
|
||||
return tool + "-" + color.toHexString();
|
||||
}
|
||||
|
||||
private calculatePosition(i: number) {
|
||||
return (i/this.gridsize)-.5-(1/this.gridsize/2);
|
||||
return (i / this.gridsize) - .5 - (1 / this.gridsize / 2);
|
||||
}
|
||||
|
||||
private static WIDGET_SIZE = .1;
|
||||
|
||||
private buildToolbox() {
|
||||
@ -165,6 +159,7 @@ export class Toolbox {
|
||||
this.node.parent.setEnabled(false);
|
||||
|
||||
}
|
||||
|
||||
public updateToolbox(color: string) {
|
||||
if (this.scene.getMeshById("toolbox-color-" + color)) {
|
||||
return;
|
||||
@ -179,9 +174,13 @@ export class Toolbox {
|
||||
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);
|
||||
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.position.z = this.index++ / 4;
|
||||
mesh.parent = this.node;
|
||||
mesh.metadata = {tool: 'color'};
|
||||
let i = 0;
|
||||
|
||||
16
src/util/deepCopy.ts
Normal file
16
src/util/deepCopy.ts
Normal file
@ -0,0 +1,16 @@
|
||||
export function deepCopy<T, U = T extends Array<infer V> ? V : never>(source: T): T {
|
||||
if (Array.isArray(source)) {
|
||||
return source.map(item => (deepCopy(item))) as T & U[]
|
||||
}
|
||||
if (source instanceof Date) {
|
||||
return new Date(source.getTime()) as T & Date
|
||||
}
|
||||
if (source && typeof source === 'object') {
|
||||
return (Object.getOwnPropertyNames(source) as (keyof T)[]).reduce<T>((o, prop) => {
|
||||
Object.defineProperty(o, prop, Object.getOwnPropertyDescriptor(source, prop)!)
|
||||
o[prop] = deepCopy(source[prop])
|
||||
return o
|
||||
}, Object.create(Object.getPrototypeOf(source)))
|
||||
}
|
||||
return source
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user