ui: add machineRepr to handle machine visual representation
This commit is contained in:
141
pkgs/clan-app/ui/src/scene/MachineRepr.ts
Normal file
141
pkgs/clan-app/ui/src/scene/MachineRepr.ts
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
import * as THREE from "three";
|
||||||
|
import { ObjectRegistry } from "./ObjectRegistry";
|
||||||
|
import { CSS2DObject } from "three/examples/jsm/renderers/CSS2DRenderer.js";
|
||||||
|
import { Accessor, createEffect, createRoot, on } from "solid-js";
|
||||||
|
import { renderLoop } from "./RenderLoop";
|
||||||
|
|
||||||
|
// Constants
|
||||||
|
const BASE_SIZE = 0.9;
|
||||||
|
const CUBE_SIZE = BASE_SIZE / 1.5;
|
||||||
|
const CUBE_HEIGHT = CUBE_SIZE;
|
||||||
|
const BASE_HEIGHT = 0.05;
|
||||||
|
const CUBE_COLOR = 0xd7e0e1;
|
||||||
|
const CUBE_EMISSIVE = 0x303030;
|
||||||
|
|
||||||
|
const CUBE_SELECTED_COLOR = 0x4b6767;
|
||||||
|
|
||||||
|
const BASE_COLOR = 0xecfdff;
|
||||||
|
const BASE_EMISSIVE = 0x0c0c0c;
|
||||||
|
const BASE_SELECTED_COLOR = 0x69b0e3;
|
||||||
|
const BASE_SELECTED_EMISSIVE = 0x666666; // Emissive color for selected bases
|
||||||
|
|
||||||
|
export class MachineRepr {
|
||||||
|
public id: string;
|
||||||
|
public group: THREE.Group;
|
||||||
|
|
||||||
|
private cubeMesh: THREE.Mesh;
|
||||||
|
private baseMesh: THREE.Mesh;
|
||||||
|
private geometry: THREE.BoxGeometry;
|
||||||
|
private material: THREE.MeshPhongMaterial;
|
||||||
|
|
||||||
|
private disposeRoot: () => void;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
scene: THREE.Scene,
|
||||||
|
registry: ObjectRegistry,
|
||||||
|
position: THREE.Vector2,
|
||||||
|
id: string,
|
||||||
|
selectedSignal: Accessor<Set<string>>,
|
||||||
|
) {
|
||||||
|
this.id = id;
|
||||||
|
this.geometry = new THREE.BoxGeometry(CUBE_SIZE, CUBE_SIZE, CUBE_SIZE);
|
||||||
|
this.material = new THREE.MeshPhongMaterial({
|
||||||
|
color: CUBE_COLOR,
|
||||||
|
emissive: CUBE_EMISSIVE,
|
||||||
|
shininess: 100,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.cubeMesh = new THREE.Mesh(this.geometry, this.material);
|
||||||
|
this.cubeMesh.castShadow = true;
|
||||||
|
this.cubeMesh.receiveShadow = true;
|
||||||
|
this.cubeMesh.userData = { id };
|
||||||
|
this.cubeMesh.name = "cube";
|
||||||
|
this.cubeMesh.position.set(0, CUBE_HEIGHT / 2, 0);
|
||||||
|
|
||||||
|
this.baseMesh = this.createCubeBase(
|
||||||
|
BASE_COLOR,
|
||||||
|
BASE_EMISSIVE,
|
||||||
|
new THREE.BoxGeometry(BASE_SIZE, BASE_HEIGHT, BASE_SIZE),
|
||||||
|
);
|
||||||
|
this.baseMesh.name = "base";
|
||||||
|
|
||||||
|
const label = this.createLabel(id);
|
||||||
|
this.cubeMesh.add(label);
|
||||||
|
|
||||||
|
this.group = new THREE.Group();
|
||||||
|
this.group.add(this.cubeMesh);
|
||||||
|
this.group.add(this.baseMesh);
|
||||||
|
|
||||||
|
this.group.position.set(position.x, 0, position.y);
|
||||||
|
this.group.userData.id = id;
|
||||||
|
|
||||||
|
this.disposeRoot = createRoot((disposeEffects) => {
|
||||||
|
createEffect(
|
||||||
|
on(selectedSignal, (selectedIds) => {
|
||||||
|
const isSelected = selectedIds.has(this.id);
|
||||||
|
// Update cube
|
||||||
|
(this.cubeMesh.material as THREE.MeshPhongMaterial).color.set(
|
||||||
|
isSelected ? CUBE_SELECTED_COLOR : CUBE_COLOR,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Update base
|
||||||
|
(this.baseMesh.material as THREE.MeshPhongMaterial).color.set(
|
||||||
|
isSelected ? BASE_SELECTED_COLOR : BASE_COLOR,
|
||||||
|
);
|
||||||
|
(this.baseMesh.material as THREE.MeshPhongMaterial).emissive.set(
|
||||||
|
isSelected ? BASE_SELECTED_EMISSIVE : BASE_EMISSIVE,
|
||||||
|
);
|
||||||
|
|
||||||
|
renderLoop.requestRender();
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
return disposeEffects;
|
||||||
|
});
|
||||||
|
|
||||||
|
scene.add(this.group);
|
||||||
|
|
||||||
|
registry.add({
|
||||||
|
object: this.group,
|
||||||
|
id,
|
||||||
|
type: "machine",
|
||||||
|
dispose: () => this.dispose(scene),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private createCubeBase(
|
||||||
|
color: THREE.ColorRepresentation,
|
||||||
|
emissive: THREE.ColorRepresentation,
|
||||||
|
geometry: THREE.BoxGeometry,
|
||||||
|
) {
|
||||||
|
const baseMaterial = new THREE.MeshPhongMaterial({
|
||||||
|
color,
|
||||||
|
emissive,
|
||||||
|
transparent: true,
|
||||||
|
opacity: 1,
|
||||||
|
});
|
||||||
|
const base = new THREE.Mesh(geometry, baseMaterial);
|
||||||
|
base.position.set(0, BASE_HEIGHT / 2, 0);
|
||||||
|
base.receiveShadow = true;
|
||||||
|
return base;
|
||||||
|
}
|
||||||
|
|
||||||
|
private createLabel(id: string) {
|
||||||
|
const div = document.createElement("div");
|
||||||
|
div.className = "machine-label";
|
||||||
|
div.textContent = id;
|
||||||
|
const label = new CSS2DObject(div);
|
||||||
|
label.position.set(0, CUBE_SIZE + 0.1, 0);
|
||||||
|
return label;
|
||||||
|
}
|
||||||
|
|
||||||
|
dispose(scene: THREE.Scene) {
|
||||||
|
this.disposeRoot?.(); // Stop SolidJS effects
|
||||||
|
|
||||||
|
scene.remove(this.group);
|
||||||
|
|
||||||
|
this.geometry.dispose();
|
||||||
|
this.material.dispose();
|
||||||
|
(this.baseMesh.material as THREE.Material).dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user