ui/machineLabels: use troika for label rendering

This commit is contained in:
Johannes Kirschbauer
2025-08-31 10:42:59 +02:00
parent c5178ac16a
commit e42a07423e
4 changed files with 35 additions and 38 deletions

View File

@@ -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));
}
};

View File

@@ -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) {

View File

@@ -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);

View File

@@ -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());
};