246 lines
8.9 KiB
TypeScript
246 lines
8.9 KiB
TypeScript
import {
|
|
AbstractMesh,
|
|
Color3,
|
|
InstancedMesh,
|
|
Mesh,
|
|
MeshBuilder,
|
|
Scene,
|
|
StandardMaterial,
|
|
TransformNode,
|
|
Vector3,
|
|
WebXRExperienceHelper
|
|
} from "@babylonjs/core";
|
|
|
|
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",
|
|
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;
|
|
private readonly xr: WebXRExperienceHelper;
|
|
public readonly node: TransformNode;
|
|
private readonly diagramManager: DiagramManager;
|
|
private readonly manager: GUI3DManager;
|
|
private readonly gridsize = 5;
|
|
private readonly addPanel: StackPanel3D;
|
|
private readonly controllers: Controllers;
|
|
private xObserver;
|
|
|
|
constructor(scene: Scene, xr: WebXRExperienceHelper, diagramManager: DiagramManager, controllers: Controllers) {
|
|
this.scene = scene;
|
|
this.controllers = controllers;
|
|
this.diagramManager = diagramManager;
|
|
this.diagramManager.onDiagramEventObservable.add((evt) => {
|
|
if (evt?.entity?.color && evt.type == DiagramEventType.CHANGECOLOR) {
|
|
this.updateToolbox(evt.entity.color);
|
|
}
|
|
}, -1, true, this);
|
|
this.addPanel = new StackPanel3D();
|
|
this.manager = new GUI3DManager(scene);
|
|
this.manager.addControl(this.addPanel);
|
|
this.node = new TransformNode("toolbox", this.scene);
|
|
const handle = MeshBuilder.CreateCapsule("handle", {
|
|
radius: .05,
|
|
orientation: Vector3.Right(),
|
|
height: .4
|
|
}, this.scene);
|
|
handle.id = "handle";
|
|
const handleMaterial = new StandardMaterial("handle-material", this.scene);
|
|
handleMaterial.diffuseColor = Color3.FromHexString("#EEEEFF");
|
|
handleMaterial.alpha = .5;
|
|
handle.material = handleMaterial;
|
|
handle.position = Vector3.Zero();
|
|
|
|
this.node.parent = handle;
|
|
this.xr = xr;
|
|
if (!this.scene.activeCamera) {
|
|
return;
|
|
} else {
|
|
this.buildToolbox();
|
|
}
|
|
|
|
Toolbox.instance = this;
|
|
if (!this.xObserver) {
|
|
this.xObserver = this.controllers.controllerObserver.add((evt) => {
|
|
if (evt.type == 'x-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, 0, 0));
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
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 {
|
|
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);
|
|
}
|
|
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;
|
|
mesh.metadata = {tool: 'color'};
|
|
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[];
|
|
} |