ui/scene: init move machine

This commit is contained in:
Johannes Kirschbauer
2025-08-29 18:42:28 +02:00
parent 25fdabee29
commit ba027c2239

View File

@@ -5,6 +5,7 @@ import {
onMount,
on,
JSX,
Show,
} from "solid-js";
import "./cubes.css";
@@ -22,6 +23,28 @@ import { renderLoop } from "./RenderLoop";
import { ObjectRegistry } from "./ObjectRegistry";
import { MachineManager } from "./MachineManager";
import cx from "classnames";
import { Portal } from "solid-js/web";
import { Menu } from "../components/ContextMenu/ContextMenu";
function intersectMachines(
event: MouseEvent,
renderer: THREE.WebGLRenderer,
camera: THREE.Camera,
machineManager: MachineManager,
raycaster: THREE.Raycaster,
): string[] {
const rect = renderer.domElement.getBoundingClientRect();
const mouse = new THREE.Vector2(
((event.clientX - rect.left) / rect.width) * 2 - 1,
-((event.clientY - rect.top) / rect.height) * 2 + 1,
);
raycaster.setFromCamera(mouse, camera);
const intersects = raycaster.intersectObjects(
Array.from(machineManager.machines.values().map((m) => m.group)),
);
return intersects.map((i) => i.object.userData.id);
}
function garbageCollectGroup(group: THREE.Group) {
for (const child of group.children) {
@@ -64,7 +87,7 @@ export function useMachineClick() {
/*Gloabl signal*/
const [worldMode, setWorldMode] = createSignal<
"default" | "select" | "service" | "create"
"default" | "select" | "service" | "create" | "move"
>("select");
export { worldMode, setWorldMode };
@@ -88,7 +111,7 @@ export function CubeScene(props: {
let controls: MapControls;
// Raycaster for clicking
const raycaster = new THREE.Raycaster();
let initBase: THREE.Mesh | undefined;
let actionBase: THREE.Mesh | undefined;
// Create background scene
const bgScene = new THREE.Scene();
@@ -111,6 +134,10 @@ export function CubeScene(props: {
position: { x: 0, y: 0, z: 0 },
spherical: { radius: 0, theta: 0, phi: 0 },
});
// Context menu state
const [contextOpen, setContextOpen] = createSignal(false);
const [menuPos, setMenuPos] = createSignal<{ x: number; y: number }>();
const [menuIntersection, setMenuIntersection] = createSignal<string[]>([]);
// Grid configuration
const GRID_SIZE = 1;
@@ -126,8 +153,10 @@ export function CubeScene(props: {
const BASE_COLOR = 0xecfdff;
const BASE_EMISSIVE = 0x0c0c0c;
const CREATE_BASE_COLOR = 0x636363;
const ACTION_BASE_COLOR = 0x636363;
const CREATE_BASE_EMISSIVE = 0xc5fad7;
const MOVE_BASE_EMISSIVE = 0xb2d7ff;
function createCubeBase(
cube_pos: [number, number, number],
@@ -350,15 +379,15 @@ export function CubeScene(props: {
);
// Important create CubeBase depends on sharedBaseGeometry
initBase = createCubeBase(
actionBase = createCubeBase(
[1, BASE_HEIGHT / 2, 1],
1,
CREATE_BASE_COLOR,
ACTION_BASE_COLOR,
CREATE_BASE_EMISSIVE,
);
initBase.visible = false;
actionBase.visible = false;
scene.add(initBase);
scene.add(actionBase);
// const spherical = new THREE.Spherical();
// spherical.setFromVector3(camera.position);
@@ -387,9 +416,9 @@ export function CubeScene(props: {
createEffect(
on(worldMode, (mode) => {
if (mode === "create") {
initBase!.visible = true;
actionBase!.visible = true;
} else {
initBase!.visible = false;
actionBase!.visible = false;
}
renderLoop.requestRender();
}),
@@ -426,11 +455,20 @@ export function CubeScene(props: {
console.error("Error creating cube:", error);
})
.finally(() => {
if (initBase) initBase.visible = false;
if (actionBase) actionBase.visible = false;
setWorldMode("default");
});
}
if (worldMode() === "move") {
console.log("sanpped");
const currId = menuIntersection().at(0);
const pos = cursorPosition();
if (!currId || !pos) return;
props.setMachinePos(currId, pos);
setWorldMode("select");
}
const rect = renderer.domElement.getBoundingClientRect();
const mouse = new THREE.Vector2(
@@ -484,18 +522,28 @@ export function CubeScene(props: {
renderLoop.requestRender();
};
const handleMouseDown = (e: MouseEvent) => {
if (e.button === 2) {
e.preventDefault();
e.stopPropagation();
const intersection = intersectMachines(
e,
renderer,
camera,
machineManager,
raycaster,
);
if (!intersection.length) return;
setMenuIntersection(intersection);
setMenuPos({ x: e.clientX, y: e.clientY });
setContextOpen(true);
}
};
renderer.domElement.addEventListener("mousedown", handleMouseDown);
renderer.domElement.addEventListener("mousemove", onMouseMove);
window.addEventListener("resize", handleResize);
// For debugging,
// TODO: Remove in production
window.addEventListener(
"contextmenu",
(e) => {
e.stopPropagation();
},
{ capture: true },
);
// Initial render
renderLoop.requestRender();
@@ -522,12 +570,12 @@ export function CubeScene(props: {
renderer.domElement.removeEventListener("mousemove", onMouseMove);
window.removeEventListener("resize", handleResize);
if (initBase) {
initBase.geometry.dispose();
if (Array.isArray(initBase.material)) {
initBase.material.forEach((material) => material.dispose());
if (actionBase) {
actionBase.geometry.dispose();
if (Array.isArray(actionBase.material)) {
actionBase.material.forEach((material) => material.dispose());
} else {
initBase.material.dispose();
actionBase.material.dispose();
}
}
@@ -543,10 +591,18 @@ export function CubeScene(props: {
renderLoop.requestRender();
};
const onMouseMove = (event: MouseEvent) => {
if (worldMode() !== "create") return;
if (!initBase) return;
if (!(worldMode() === "create" || worldMode() === "move")) return;
if (!actionBase) return;
initBase.visible = true;
console.log("Mouse move in create/move mode");
actionBase.visible = true;
(actionBase.material as THREE.MeshPhongMaterial).emissive.set(
worldMode() === "create" ? CREATE_BASE_EMISSIVE : MOVE_BASE_EMISSIVE,
);
// Calculate mouse position in normalized device coordinates
// (-1 to +1) for both components
const rect = renderer.domElement.getBoundingClientRect();
const mouse = new THREE.Vector2(
@@ -577,21 +633,48 @@ export function CubeScene(props: {
}
if (
Math.abs(initBase.position.x - snapped.x) > 0.01 ||
Math.abs(initBase.position.z - snapped.z) > 0.01
Math.abs(actionBase.position.x - snapped.x) > 0.01 ||
Math.abs(actionBase.position.z - snapped.z) > 0.01
) {
// Only request render if the position actually changed
initBase.position.set(snapped.x, 0, snapped.z);
actionBase.position.set(snapped.x, 0, snapped.z);
setCursorPosition([snapped.x, snapped.z]); // Update next position for cube creation
renderLoop.requestRender();
}
}
};
createEffect(() => {
if (contextOpen()) {
// Disable canvas pointer events so menu can receive events
renderer.domElement.style.pointerEvents = "none";
labelRenderer.domElement.style.pointerEvents = "none";
} else {
// Re-enable canvas interactions
renderer.domElement.style.pointerEvents = "auto";
labelRenderer.domElement.style.pointerEvents = "none"; // keep labels non-interactive
}
});
const handleMenuSelect = (mode: "move") => {
setWorldMode(mode);
console.log("Menu selected, new World mode", worldMode());
};
const machinesQuery = useMachinesQuery(props.clanURI);
return (
<>
<Show when={contextOpen()}>
<Portal mount={document.body}>
<Menu
onSelect={handleMenuSelect}
intersect={menuIntersection()}
x={menuPos()!.x - 10}
y={menuPos()!.y - 10}
close={() => setContextOpen(false)}
/>
</Portal>
</Show>
<div
class={cx(
"cubes-scene-container",