Refactor simulation and viewer architecture
This commit is contained in:
131
apps/viewer/src/viewerInteraction.ts
Normal file
131
apps/viewer/src/viewerInteraction.ts
Normal file
@@ -0,0 +1,131 @@
|
||||
import * as THREE from "three";
|
||||
import { getSelectionGroup } from "./viewerSelection";
|
||||
import type { Selectable, SelectionGroup, WorldState } from "./viewerTypes";
|
||||
|
||||
export function pickSelectableAtClientPosition(
|
||||
renderer: THREE.WebGLRenderer,
|
||||
raycaster: THREE.Raycaster,
|
||||
mouse: THREE.Vector2,
|
||||
camera: THREE.Camera,
|
||||
selectableTargets: Map<THREE.Object3D, Selectable>,
|
||||
clientX: number,
|
||||
clientY: number,
|
||||
) {
|
||||
const bounds = renderer.domElement.getBoundingClientRect();
|
||||
mouse.x = ((clientX - bounds.left) / bounds.width) * 2 - 1;
|
||||
mouse.y = -(((clientY - bounds.top) / bounds.height) * 2 - 1);
|
||||
raycaster.setFromCamera(mouse, camera);
|
||||
const hit = raycaster.intersectObjects([...selectableTargets.keys()], false)[0];
|
||||
return hit ? selectableTargets.get(hit.object) : undefined;
|
||||
}
|
||||
|
||||
export function updateHoverLabel(params: {
|
||||
dragMode?: string;
|
||||
hoverLabelEl: HTMLDivElement;
|
||||
selection: Selectable | undefined;
|
||||
activeSystemId?: string;
|
||||
world?: WorldState;
|
||||
point: THREE.Vector2;
|
||||
}) {
|
||||
const {
|
||||
dragMode,
|
||||
hoverLabelEl,
|
||||
selection,
|
||||
activeSystemId,
|
||||
world,
|
||||
point,
|
||||
} = params;
|
||||
|
||||
if (dragMode) {
|
||||
hoverLabelEl.hidden = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!selection || selection.kind !== "system" || selection.id === activeSystemId) {
|
||||
hoverLabelEl.hidden = true;
|
||||
return;
|
||||
}
|
||||
|
||||
const system = world?.systems.get(selection.id);
|
||||
if (!system) {
|
||||
hoverLabelEl.hidden = true;
|
||||
return;
|
||||
}
|
||||
|
||||
hoverLabelEl.hidden = false;
|
||||
hoverLabelEl.textContent = system.label;
|
||||
hoverLabelEl.style.left = `${point.x + 14}px`;
|
||||
hoverLabelEl.style.top = `${point.y + 14}px`;
|
||||
}
|
||||
|
||||
export function updateMarqueeBox(
|
||||
marqueeEl: HTMLDivElement,
|
||||
dragStart: THREE.Vector2,
|
||||
dragLast: THREE.Vector2,
|
||||
) {
|
||||
const minX = Math.min(dragStart.x, dragLast.x);
|
||||
const minY = Math.min(dragStart.y, dragLast.y);
|
||||
const maxX = Math.max(dragStart.x, dragLast.x);
|
||||
const maxY = Math.max(dragStart.y, dragLast.y);
|
||||
marqueeEl.style.left = `${minX}px`;
|
||||
marqueeEl.style.top = `${minY}px`;
|
||||
marqueeEl.style.width = `${maxX - minX}px`;
|
||||
marqueeEl.style.height = `${maxY - minY}px`;
|
||||
}
|
||||
|
||||
export function hideMarqueeBox(marqueeEl: HTMLDivElement) {
|
||||
marqueeEl.style.display = "none";
|
||||
marqueeEl.style.width = "0";
|
||||
marqueeEl.style.height = "0";
|
||||
}
|
||||
|
||||
export function completeMarqueeSelection(params: {
|
||||
renderer: THREE.WebGLRenderer;
|
||||
camera: THREE.Camera;
|
||||
dragStart: THREE.Vector2;
|
||||
dragLast: THREE.Vector2;
|
||||
selectableTargets: Map<THREE.Object3D, Selectable>;
|
||||
}) {
|
||||
const {
|
||||
renderer,
|
||||
camera,
|
||||
dragStart,
|
||||
dragLast,
|
||||
selectableTargets,
|
||||
} = params;
|
||||
|
||||
const bounds = renderer.domElement.getBoundingClientRect();
|
||||
const minX = Math.min(dragStart.x, dragLast.x);
|
||||
const minY = Math.min(dragStart.y, dragLast.y);
|
||||
const maxX = Math.max(dragStart.x, dragLast.x);
|
||||
const maxY = Math.max(dragStart.y, dragLast.y);
|
||||
const grouped = new Map<SelectionGroup, Selectable[]>();
|
||||
|
||||
for (const [object, selectable] of selectableTargets.entries()) {
|
||||
if (object instanceof THREE.Sprite && !object.visible) {
|
||||
continue;
|
||||
}
|
||||
if (!object.visible) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const worldPosition = new THREE.Vector3();
|
||||
object.getWorldPosition(worldPosition);
|
||||
worldPosition.project(camera);
|
||||
const screenX = ((worldPosition.x + 1) * 0.5) * bounds.width;
|
||||
const screenY = ((1 - worldPosition.y) * 0.5) * bounds.height;
|
||||
if (screenX < minX || screenX > maxX || screenY < minY || screenY > maxY) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const group = getSelectionGroup(selectable);
|
||||
const list = grouped.get(group) ?? [];
|
||||
if (!list.some((entry) => JSON.stringify(entry) === JSON.stringify(selectable))) {
|
||||
list.push(selectable);
|
||||
}
|
||||
grouped.set(group, list);
|
||||
}
|
||||
|
||||
return [...grouped.entries()]
|
||||
.sort((left, right) => right[1].length - left[1].length)[0]?.[1] ?? [];
|
||||
}
|
||||
Reference in New Issue
Block a user