ui/scene: fix double click on move

This commit is contained in:
Johannes Kirschbauer
2025-09-02 14:09:16 +02:00
parent db84369000
commit 36f73d40b3
2 changed files with 71 additions and 54 deletions

View File

@@ -132,8 +132,6 @@ const ClanSceneController = (props: RouteSectionProps) => {
const navigate = useNavigate();
const [showService, setShowService] = createSignal(false);
const [currentPromise, setCurrentPromise] = createSignal<{
resolve: ({ id }: { id: string }) => void;
reject: (err: unknown) => void;
@@ -219,21 +217,9 @@ const ClanSceneController = (props: RouteSectionProps) => {
console.error("Error creating service instance", result.errors);
}
toast.success("Created");
setShowService(false);
setWorldMode("select");
};
createEffect(
on(worldMode, (mode) => {
if (mode === "service") {
setShowService(true);
} else {
// TODO: request soft close instead of forced close
setShowService(false);
}
}),
);
return (
<>
<Show when={loadingError()}>
@@ -268,11 +254,10 @@ const ClanSceneController = (props: RouteSectionProps) => {
isLoading={ctx.isLoading()}
cubesQuery={ctx.machinesQuery}
toolbarPopup={
<Show when={showService()}>
<Show when={worldMode() === "service"}>
<ServiceWorkflow
handleSubmit={handleSubmitService}
onClose={() => {
setShowService(false);
setWorldMode("select");
currentPromise()?.resolve({ id: "0" });
}}

View File

@@ -38,7 +38,7 @@ function intersectMachines(
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,
@@ -49,7 +49,10 @@ function intersectMachines(
Array.from(machineManager.machines.values().map((m) => m.group)),
);
return intersects.map((i) => i.object.userData.id);
return {
machines: intersects.map((i) => i.object.userData.id),
intersection: intersects,
};
}
function garbageCollectGroup(group: THREE.Group) {
@@ -129,6 +132,8 @@ export function CubeScene(props: {
let sharedCubeGeometry: THREE.BoxGeometry;
let sharedBaseGeometry: THREE.BoxGeometry;
let machineManager: MachineManager;
const [positionMode, setPositionMode] = createSignal<"grid" | "circle">(
"grid",
);
@@ -137,6 +142,7 @@ export function CubeScene(props: {
const [cancelMove, setCancelMove] = createSignal<NodeJS.Timeout>();
// TODO: Unify this with actionRepr position
const [cursorPosition, setCursorPosition] = createSignal<[number, number]>();
const [cameraInfo, setCameraInfo] = createSignal({
@@ -446,7 +452,7 @@ export function CubeScene(props: {
const registry = new ObjectRegistry();
const machineManager = new MachineManager(
machineManager = new MachineManager(
scene,
registry,
props.sceneStore,
@@ -545,7 +551,7 @@ export function CubeScene(props: {
};
const handleMouseDown = (e: MouseEvent) => {
const intersection = intersectMachines(
const { machines, intersection } = intersectMachines(
e,
renderer,
camera,
@@ -555,7 +561,7 @@ export function CubeScene(props: {
if (e.button === 0) {
// Left button
if (worldMode() === "select" && intersection.length) {
if (worldMode() === "select" && machines.length) {
// Disable controls to avoid conflict
controls.enabled = false;
@@ -563,7 +569,8 @@ export function CubeScene(props: {
const cancelMove = setTimeout(() => {
setIsDragging(true);
// Set machine as flying
setHighlightGroups({ move: new Set(intersection) });
setHighlightGroups({ move: new Set(machines) });
setWorldMode("move");
renderLoop.requestRender();
}, 500);
@@ -575,7 +582,7 @@ export function CubeScene(props: {
e.preventDefault();
e.stopPropagation();
if (!intersection.length) return;
setMenuIntersection(intersection);
setMenuIntersection(machines);
setMenuPos({ x: e.clientX, y: e.clientY });
setContextOpen(true);
}
@@ -649,6 +656,39 @@ export function CubeScene(props: {
});
});
const snapToGrid = (point: THREE.Vector3) => {
if (!props.sceneStore) return;
// Snap to grid
const snapped = new THREE.Vector3(
Math.round(point.x / GRID_SIZE) * GRID_SIZE,
0,
Math.round(point.z / GRID_SIZE) * GRID_SIZE,
);
// Skip snapping if there's already a cube at this position
const positions = Object.entries(props.sceneStore());
const intersects = positions.some(
([_id, p]) => p.position[0] === snapped.x && p.position[1] === snapped.z,
);
const movingMachine = Array.from(highlightGroups["move"] || [])[0];
const startingPos = positions.find(([_id, p]) => _id === movingMachine);
if (startingPos) {
const isStartingPos =
snapped.x === startingPos[1].position[0] &&
snapped.z === startingPos[1].position[1];
// If Intersect any other machine and not the one being moved
if (!isStartingPos && intersects) {
return;
}
} else {
if (intersects) {
return;
}
}
return snapped;
};
const onAddClick = (event: MouseEvent) => {
setPositionMode("grid");
setWorldMode("create");
@@ -678,37 +718,8 @@ export function CubeScene(props: {
if (intersects.length > 0) {
const point = intersects[0].point;
// Snap to grid
const snapped = new THREE.Vector3(
Math.round(point.x / GRID_SIZE) * GRID_SIZE,
0,
Math.round(point.z / GRID_SIZE) * GRID_SIZE,
);
// Skip snapping if there's already a cube at this position
if (props.sceneStore()) {
const positions = Object.entries(props.sceneStore());
const intersects = positions.some(
([_id, p]) =>
p.position[0] === snapped.x && p.position[1] === snapped.z,
);
const movingMachine = Array.from(highlightGroups["move"] || [])[0];
const startingPos = positions.find(([_id, p]) => _id === movingMachine);
if (startingPos) {
const isStartingPos =
snapped.x === startingPos[1].position[0] &&
snapped.z === startingPos[1].position[1];
// If Intersect any other machine and not the one being moved
if (!isStartingPos && intersects) {
return;
}
} else {
if (intersects) {
return;
}
}
}
const snapped = snapToGrid(point);
if (!snapped) return;
if (
Math.abs(actionRepr.position.x - snapped.x) > 0.01 ||
Math.abs(actionRepr.position.z - snapped.z) > 0.01
@@ -723,8 +734,29 @@ export function CubeScene(props: {
const handleMenuSelect = (mode: "move") => {
setWorldMode(mode);
setHighlightGroups({ move: new Set(menuIntersection()) });
// Find the position of the first selected machine
// Set the actionMachine position to that
const firstId = menuIntersection()[0];
if (firstId) {
const machine = machineManager.machines.get(firstId);
if (machine && actionMachine) {
actionMachine.position.set(
machine.group.position.x,
0,
machine.group.position.z,
);
setCursorPosition([machine.group.position.x, machine.group.position.z]);
}
}
};
createEffect(
on(worldMode, (mode) => {
console.log("World mode changed to", mode);
}),
);
const machinesQuery = useMachinesQuery(props.clanURI);
return (