ui/scene: init move machine
This commit is contained in:
@@ -5,6 +5,7 @@ import {
|
|||||||
onMount,
|
onMount,
|
||||||
on,
|
on,
|
||||||
JSX,
|
JSX,
|
||||||
|
Show,
|
||||||
} from "solid-js";
|
} from "solid-js";
|
||||||
import "./cubes.css";
|
import "./cubes.css";
|
||||||
|
|
||||||
@@ -22,6 +23,28 @@ import { renderLoop } from "./RenderLoop";
|
|||||||
import { ObjectRegistry } from "./ObjectRegistry";
|
import { ObjectRegistry } from "./ObjectRegistry";
|
||||||
import { MachineManager } from "./MachineManager";
|
import { MachineManager } from "./MachineManager";
|
||||||
import cx from "classnames";
|
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) {
|
function garbageCollectGroup(group: THREE.Group) {
|
||||||
for (const child of group.children) {
|
for (const child of group.children) {
|
||||||
@@ -64,7 +87,7 @@ export function useMachineClick() {
|
|||||||
|
|
||||||
/*Gloabl signal*/
|
/*Gloabl signal*/
|
||||||
const [worldMode, setWorldMode] = createSignal<
|
const [worldMode, setWorldMode] = createSignal<
|
||||||
"default" | "select" | "service" | "create"
|
"default" | "select" | "service" | "create" | "move"
|
||||||
>("select");
|
>("select");
|
||||||
export { worldMode, setWorldMode };
|
export { worldMode, setWorldMode };
|
||||||
|
|
||||||
@@ -88,7 +111,7 @@ export function CubeScene(props: {
|
|||||||
let controls: MapControls;
|
let controls: MapControls;
|
||||||
// Raycaster for clicking
|
// Raycaster for clicking
|
||||||
const raycaster = new THREE.Raycaster();
|
const raycaster = new THREE.Raycaster();
|
||||||
let initBase: THREE.Mesh | undefined;
|
let actionBase: THREE.Mesh | undefined;
|
||||||
|
|
||||||
// Create background scene
|
// Create background scene
|
||||||
const bgScene = new THREE.Scene();
|
const bgScene = new THREE.Scene();
|
||||||
@@ -111,6 +134,10 @@ export function CubeScene(props: {
|
|||||||
position: { x: 0, y: 0, z: 0 },
|
position: { x: 0, y: 0, z: 0 },
|
||||||
spherical: { radius: 0, theta: 0, phi: 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
|
// Grid configuration
|
||||||
const GRID_SIZE = 1;
|
const GRID_SIZE = 1;
|
||||||
@@ -126,8 +153,10 @@ export function CubeScene(props: {
|
|||||||
const BASE_COLOR = 0xecfdff;
|
const BASE_COLOR = 0xecfdff;
|
||||||
const BASE_EMISSIVE = 0x0c0c0c;
|
const BASE_EMISSIVE = 0x0c0c0c;
|
||||||
|
|
||||||
const CREATE_BASE_COLOR = 0x636363;
|
const ACTION_BASE_COLOR = 0x636363;
|
||||||
|
|
||||||
const CREATE_BASE_EMISSIVE = 0xc5fad7;
|
const CREATE_BASE_EMISSIVE = 0xc5fad7;
|
||||||
|
const MOVE_BASE_EMISSIVE = 0xb2d7ff;
|
||||||
|
|
||||||
function createCubeBase(
|
function createCubeBase(
|
||||||
cube_pos: [number, number, number],
|
cube_pos: [number, number, number],
|
||||||
@@ -350,15 +379,15 @@ export function CubeScene(props: {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Important create CubeBase depends on sharedBaseGeometry
|
// Important create CubeBase depends on sharedBaseGeometry
|
||||||
initBase = createCubeBase(
|
actionBase = createCubeBase(
|
||||||
[1, BASE_HEIGHT / 2, 1],
|
[1, BASE_HEIGHT / 2, 1],
|
||||||
1,
|
1,
|
||||||
CREATE_BASE_COLOR,
|
ACTION_BASE_COLOR,
|
||||||
CREATE_BASE_EMISSIVE,
|
CREATE_BASE_EMISSIVE,
|
||||||
);
|
);
|
||||||
initBase.visible = false;
|
actionBase.visible = false;
|
||||||
|
|
||||||
scene.add(initBase);
|
scene.add(actionBase);
|
||||||
|
|
||||||
// const spherical = new THREE.Spherical();
|
// const spherical = new THREE.Spherical();
|
||||||
// spherical.setFromVector3(camera.position);
|
// spherical.setFromVector3(camera.position);
|
||||||
@@ -387,9 +416,9 @@ export function CubeScene(props: {
|
|||||||
createEffect(
|
createEffect(
|
||||||
on(worldMode, (mode) => {
|
on(worldMode, (mode) => {
|
||||||
if (mode === "create") {
|
if (mode === "create") {
|
||||||
initBase!.visible = true;
|
actionBase!.visible = true;
|
||||||
} else {
|
} else {
|
||||||
initBase!.visible = false;
|
actionBase!.visible = false;
|
||||||
}
|
}
|
||||||
renderLoop.requestRender();
|
renderLoop.requestRender();
|
||||||
}),
|
}),
|
||||||
@@ -426,11 +455,20 @@ export function CubeScene(props: {
|
|||||||
console.error("Error creating cube:", error);
|
console.error("Error creating cube:", error);
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
if (initBase) initBase.visible = false;
|
if (actionBase) actionBase.visible = false;
|
||||||
|
|
||||||
setWorldMode("default");
|
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 rect = renderer.domElement.getBoundingClientRect();
|
||||||
const mouse = new THREE.Vector2(
|
const mouse = new THREE.Vector2(
|
||||||
@@ -484,18 +522,28 @@ export function CubeScene(props: {
|
|||||||
renderLoop.requestRender();
|
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);
|
renderer.domElement.addEventListener("mousemove", onMouseMove);
|
||||||
|
|
||||||
window.addEventListener("resize", handleResize);
|
window.addEventListener("resize", handleResize);
|
||||||
// For debugging,
|
|
||||||
// TODO: Remove in production
|
|
||||||
window.addEventListener(
|
|
||||||
"contextmenu",
|
|
||||||
(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
},
|
|
||||||
{ capture: true },
|
|
||||||
);
|
|
||||||
|
|
||||||
// Initial render
|
// Initial render
|
||||||
renderLoop.requestRender();
|
renderLoop.requestRender();
|
||||||
@@ -522,12 +570,12 @@ export function CubeScene(props: {
|
|||||||
renderer.domElement.removeEventListener("mousemove", onMouseMove);
|
renderer.domElement.removeEventListener("mousemove", onMouseMove);
|
||||||
window.removeEventListener("resize", handleResize);
|
window.removeEventListener("resize", handleResize);
|
||||||
|
|
||||||
if (initBase) {
|
if (actionBase) {
|
||||||
initBase.geometry.dispose();
|
actionBase.geometry.dispose();
|
||||||
if (Array.isArray(initBase.material)) {
|
if (Array.isArray(actionBase.material)) {
|
||||||
initBase.material.forEach((material) => material.dispose());
|
actionBase.material.forEach((material) => material.dispose());
|
||||||
} else {
|
} else {
|
||||||
initBase.material.dispose();
|
actionBase.material.dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -543,10 +591,18 @@ export function CubeScene(props: {
|
|||||||
renderLoop.requestRender();
|
renderLoop.requestRender();
|
||||||
};
|
};
|
||||||
const onMouseMove = (event: MouseEvent) => {
|
const onMouseMove = (event: MouseEvent) => {
|
||||||
if (worldMode() !== "create") return;
|
if (!(worldMode() === "create" || worldMode() === "move")) return;
|
||||||
if (!initBase) 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 rect = renderer.domElement.getBoundingClientRect();
|
||||||
const mouse = new THREE.Vector2(
|
const mouse = new THREE.Vector2(
|
||||||
@@ -577,21 +633,48 @@ export function CubeScene(props: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
Math.abs(initBase.position.x - snapped.x) > 0.01 ||
|
Math.abs(actionBase.position.x - snapped.x) > 0.01 ||
|
||||||
Math.abs(initBase.position.z - snapped.z) > 0.01
|
Math.abs(actionBase.position.z - snapped.z) > 0.01
|
||||||
) {
|
) {
|
||||||
// Only request render if the position actually changed
|
// 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
|
setCursorPosition([snapped.x, snapped.z]); // Update next position for cube creation
|
||||||
renderLoop.requestRender();
|
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);
|
const machinesQuery = useMachinesQuery(props.clanURI);
|
||||||
|
|
||||||
return (
|
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
|
<div
|
||||||
class={cx(
|
class={cx(
|
||||||
"cubes-scene-container",
|
"cubes-scene-container",
|
||||||
|
|||||||
Reference in New Issue
Block a user