ui/machineLabels: use troika for label rendering
This commit is contained in:
@@ -12,6 +12,7 @@ import {
|
||||
useContext,
|
||||
} from "solid-js";
|
||||
import {
|
||||
buildClanPath,
|
||||
buildMachinePath,
|
||||
maybeUseMachineName,
|
||||
useClanURI,
|
||||
@@ -197,6 +198,8 @@ const ClanSceneController = (props: RouteSectionProps) => {
|
||||
const selected = ids.values().next().value;
|
||||
if (selected) {
|
||||
navigate(buildMachinePath(ctx.clanURI, selected));
|
||||
} else {
|
||||
navigate(buildClanPath(ctx.clanURI));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -3,9 +3,9 @@ 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";
|
||||
import { TextGeometry } from "three/examples/jsm/geometries/TextGeometry.js";
|
||||
import { FontLoader, FontData } from "three/examples/jsm/Addons";
|
||||
import font from "../../.fonts/CommitMono_Regular.json";
|
||||
// @ts-expect-error: No types for troika-three-text
|
||||
import { Text } from "troika-three-text";
|
||||
import ttf from "../../.fonts/CommitMonoV143-VF.ttf";
|
||||
|
||||
// Constants
|
||||
const BASE_SIZE = 0.9;
|
||||
@@ -68,7 +68,6 @@ export class MachineRepr {
|
||||
this.baseMesh.name = "base";
|
||||
|
||||
const label = this.createLabel(id);
|
||||
// this.cubeMesh.add(label);
|
||||
|
||||
const shadowPlaneMaterial = new THREE.MeshStandardMaterial({
|
||||
color: BASE_COLOR, // any color you like
|
||||
@@ -168,34 +167,27 @@ export class MachineRepr {
|
||||
}
|
||||
|
||||
private createLabel(id: string) {
|
||||
const loader = new FontLoader();
|
||||
const final = loader.parse(font as unknown as FontData);
|
||||
const text = new Text();
|
||||
text.text = id;
|
||||
text.font = ttf;
|
||||
// text.font = ".fonts/CommitMonoV143-VF.woff2"; // <-- normal web font, not JSON
|
||||
text.fontSize = 0.15; // relative to your cube size
|
||||
text.color = 0x000000; // any THREE.Color
|
||||
text.anchorX = "center"; // horizontal centering
|
||||
text.anchorY = "bottom"; // baseline aligns to cube top
|
||||
text.position.set(0, CUBE_SIZE + 0.05, 0);
|
||||
|
||||
const geometry = new TextGeometry(id, {
|
||||
font: final,
|
||||
size: 0.1,
|
||||
depth: 0.01,
|
||||
curveSegments: 12,
|
||||
bevelEnabled: false,
|
||||
bevelThickness: 0.01,
|
||||
bevelSize: 0.01,
|
||||
bevelOffset: 0,
|
||||
bevelSegments: 5,
|
||||
// If you want it to always face camera:
|
||||
text.userData.isLabel = true;
|
||||
text.outlineWidth = 0.005;
|
||||
text.outlineColor = 0x333333;
|
||||
text.quaternion.copy(this.camera.quaternion);
|
||||
|
||||
// Re-render on text changes
|
||||
text.sync(() => {
|
||||
renderLoop.requestRender();
|
||||
});
|
||||
|
||||
const textMaterial = new THREE.MeshPhongMaterial({ color: 0x000000 });
|
||||
const textMesh = new THREE.Mesh(geometry, textMaterial);
|
||||
|
||||
geometry.computeBoundingBox();
|
||||
if (geometry.boundingBox) {
|
||||
const xMid =
|
||||
-0.5 * (geometry.boundingBox.max.x - geometry.boundingBox.min.x);
|
||||
geometry.translate(xMid, 0, 0); // shift so it's centered on X
|
||||
}
|
||||
textMesh.position.set(0, CUBE_SIZE + 0.15, 0); // above the cube
|
||||
textMesh.quaternion.copy(this.camera.quaternion);
|
||||
textMesh.userData.isLabel = true;
|
||||
return textMesh;
|
||||
return text;
|
||||
}
|
||||
|
||||
dispose(scene: THREE.Scene) {
|
||||
|
||||
@@ -98,6 +98,11 @@ class RenderLoop {
|
||||
if (obj.userData.isLabel) {
|
||||
(obj as THREE.Mesh).quaternion.copy(this.camera.quaternion);
|
||||
}
|
||||
// if (obj.userData.isLabel) {
|
||||
// const camPos = new THREE.Vector3();
|
||||
// this.camera.getWorldPosition(camPos);
|
||||
// obj.lookAt(new THREE.Vector3(camPos.x, obj.position.y, camPos.z));
|
||||
// }
|
||||
});
|
||||
|
||||
this.labelRenderer.render(this.scene, this.camera);
|
||||
|
||||
@@ -25,6 +25,7 @@ import { MachineManager } from "./MachineManager";
|
||||
import cx from "classnames";
|
||||
import { Portal } from "solid-js/web";
|
||||
import { Menu } from "../components/ContextMenu/ContextMenu";
|
||||
import { clearHighlight, setHighlightGroups } from "./highlightStore";
|
||||
|
||||
function intersectMachines(
|
||||
event: MouseEvent,
|
||||
@@ -177,12 +178,6 @@ export function CubeScene(props: {
|
||||
return base;
|
||||
}
|
||||
|
||||
function toggleSelection(id: string) {
|
||||
const next = new Set<string>();
|
||||
next.add(id);
|
||||
props.onSelect(next);
|
||||
}
|
||||
|
||||
const initialCameraPosition = { x: 20, y: 20, z: 20 };
|
||||
const initialSphericalCameraPosition = new THREE.Spherical();
|
||||
initialSphericalCameraPosition.setFromVector3(
|
||||
@@ -469,6 +464,7 @@ export function CubeScene(props: {
|
||||
|
||||
props.setMachinePos(currId, pos);
|
||||
setWorldMode("select");
|
||||
clearHighlight("move");
|
||||
}
|
||||
|
||||
const rect = renderer.domElement.getBoundingClientRect();
|
||||
@@ -486,13 +482,13 @@ export function CubeScene(props: {
|
||||
console.log("Clicked on cube:", intersects);
|
||||
const id = intersects[0].object.userData.id;
|
||||
|
||||
if (worldMode() === "select") toggleSelection(id);
|
||||
if (worldMode() === "select") props.onSelect(new Set<string>([id]));
|
||||
|
||||
emitMachineClick(id); // notify subscribers
|
||||
} else {
|
||||
emitMachineClick(null);
|
||||
|
||||
props.onSelect(new Set<string>()); // Clear selection if clicked outside cubes
|
||||
if (worldMode() === "select") props.onSelect(new Set<string>());
|
||||
}
|
||||
};
|
||||
|
||||
@@ -646,6 +642,7 @@ export function CubeScene(props: {
|
||||
};
|
||||
const handleMenuSelect = (mode: "move") => {
|
||||
setWorldMode(mode);
|
||||
setHighlightGroups({ move: new Set(menuIntersection()) });
|
||||
console.log("Menu selected, new World mode", worldMode());
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user