233 lines
8.7 KiB
TypeScript
233 lines
8.7 KiB
TypeScript
import {
|
|
AbstractMesh,
|
|
ActionManager,
|
|
Color3,
|
|
ExecuteCodeAction,
|
|
InstancedMesh,
|
|
Mesh,
|
|
Observable,
|
|
PhysicsAggregate,
|
|
PhysicsBody,
|
|
PhysicsMotionType,
|
|
PhysicsShapeType,
|
|
PlaySoundAction,
|
|
Scene,
|
|
WebXRExperienceHelper
|
|
} from "@babylonjs/core";
|
|
import {DiagramEntity, DiagramEvent, DiagramEventType} from "./diagramEntity";
|
|
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 {TextLabel} from "./textLabel";
|
|
|
|
export class DiagramManager {
|
|
public readonly onDiagramEventObservable: Observable<DiagramEvent> = new Observable();
|
|
private readonly logger = log.getLogger('DiagramManager');
|
|
private persistenceManager: IPersistenceManager = null;
|
|
private readonly scene: Scene;
|
|
private xr: WebXRExperienceHelper;
|
|
|
|
|
|
public setPersistenceManager(persistenceManager: IPersistenceManager) {
|
|
this.persistenceManager = persistenceManager;
|
|
this.persistenceManager.updateObserver.add(this.onRemoteEvent, -1, true, this);
|
|
}
|
|
|
|
private getPersistenceManager(): IPersistenceManager {
|
|
if (!this.persistenceManager) {
|
|
this.logger.warn("persistenceManager not set");
|
|
return null;
|
|
}
|
|
return this.persistenceManager;
|
|
}
|
|
|
|
private readonly actionManager: ActionManager;
|
|
private config: AppConfig;
|
|
|
|
constructor(scene: Scene, xr: WebXRExperienceHelper) {
|
|
this.scene = scene;
|
|
this.xr = xr;
|
|
this.actionManager = new ActionManager(this.scene);
|
|
this.actionManager.registerAction(
|
|
new PlaySoundAction(ActionManager.OnPointerOverTrigger, DiaSounds.instance.tick));
|
|
this.actionManager.registerAction(
|
|
new ExecuteCodeAction(ActionManager.OnPointerOverTrigger, (evt) => {
|
|
Controllers.controllerObserver.notifyObservers({
|
|
type: 'pulse',
|
|
gripId: evt?.additionalData?.pickResult?.gripTransform?.id
|
|
})
|
|
this.logger.debug(evt);
|
|
})
|
|
);
|
|
if (this.onDiagramEventObservable.hasObservers()) {
|
|
this.logger.warn("onDiagramEventObservable already has Observers, you should be careful");
|
|
}
|
|
this.onDiagramEventObservable.add(this.onDiagramEvent, -1, true, this);
|
|
this.logger.debug("DiagramManager constructed");
|
|
}
|
|
|
|
public createCopy(mesh: AbstractMesh, copy: boolean = false): AbstractMesh {
|
|
let newMesh;
|
|
if (!mesh.isAnInstance) {
|
|
newMesh = new InstancedMesh("new", (mesh as Mesh));
|
|
} else {
|
|
newMesh = new InstancedMesh("new", (mesh as InstancedMesh).sourceMesh);
|
|
}
|
|
newMesh.actionManager = this.actionManager;
|
|
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 {
|
|
newMesh.scaling = AppConfig.config.createSnapVal;
|
|
}
|
|
newMesh.material = mesh.material;
|
|
newMesh.metadata = mesh.metadata;
|
|
DiagramShapePhysics.applyPhysics(newMesh, this.scene);
|
|
return newMesh;
|
|
}
|
|
|
|
private onRemoteEvent(event: DiagramEntity) {
|
|
this.logger.debug(event);
|
|
const toolMesh = this.scene.getMeshById("tool-" + event.template + "-" + event.color);
|
|
if (!toolMesh) {
|
|
log.debug('no mesh found for ' + event.template + "-" + event.color, 'adding it');
|
|
this.onDiagramEventObservable.notifyObservers({
|
|
type: DiagramEventType.CHANGECOLOR,
|
|
entity: event
|
|
});
|
|
}
|
|
const mesh = MeshConverter.fromDiagramEntity(event, this.scene);
|
|
mesh.actionManager = this.actionManager;
|
|
if (event.parent) {
|
|
mesh.parent = this.scene.getMeshById(event.parent);
|
|
}
|
|
DiagramShapePhysics.applyPhysics(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);
|
|
}
|
|
switch (event.type) {
|
|
case DiagramEventType.CLEAR:
|
|
break;
|
|
case DiagramEventType.DROPPED:
|
|
break;
|
|
case DiagramEventType.DROP:
|
|
this.getPersistenceManager()?.modify(mesh);
|
|
TextLabel.updateTextNode(mesh, entity.text);
|
|
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) {
|
|
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();
|
|
DiaSounds.instance.exit.play();
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
class DiagramShapePhysics {
|
|
private static logger: log.Logger = log.getLogger('DiagramShapePhysics');
|
|
|
|
public static applyPhysics(mesh: AbstractMesh, scene: Scene, motionType?: PhysicsMotionType): 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);
|
|
const body = aggregate.body;
|
|
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, state) => {
|
|
if (event.distance > .001 && !DiaSounds.instance.low.isPlaying) {
|
|
this.logger.debug(event, state);
|
|
DiaSounds.instance.low.play();
|
|
}
|
|
}, -1, false, this);
|
|
//body.setMotionType(PhysicsMotionType.ANIMATED);
|
|
body.setLinearDamping(.95);
|
|
body.setAngularDamping(.99);
|
|
body.setGravityFactor(0);
|
|
return aggregate.body;
|
|
}
|
|
} |