clean branch-scoped code quality issues

This commit is contained in:
Tom Boullay
2026-04-28 14:23:37 +02:00
parent 9818e719ce
commit 64b53a762d
15 changed files with 218 additions and 242 deletions
+43 -22
View File
@@ -14,7 +14,7 @@ import { useDebugFolder } from "@/hooks/debug/useDebugFolder";
import { InteractionManager } from "@/stateManager/InteractionManager";
import { INTERACTION_RADIUS } from "@/data/interaction/interactionConfig";
import type { Vector3Tuple } from "@/types/3d";
import type { InteractableHandle, InteractableKind } from "@/types/interaction";
import type { InteractableHandle } from "@/types/interaction";
interface InteractableObjectBaseProps {
label: string;
@@ -37,46 +37,67 @@ type InteractableObjectProps =
| TriggerInteractableObjectProps
| GrabInteractableObjectProps;
type MutableInteractableHandle = {
kind: InteractableKind;
label: string;
onPress: () => void;
onRelease?: () => void;
};
const _cameraPos = new THREE.Vector3();
const _cameraDir = new THREE.Vector3();
const _objectPos = new THREE.Vector3();
const _raycaster = new THREE.Raycaster();
function createInteractableHandle(
props: InteractableObjectProps,
): InteractableHandle {
if (props.kind === "grab") {
return {
kind: props.kind,
label: props.label,
onPress: props.onPress,
onRelease: props.onRelease,
};
}
return {
kind: props.kind,
label: props.label,
onPress: props.onPress,
};
}
export function InteractableObject(
props: InteractableObjectProps,
): React.JSX.Element {
const { kind, label, position, bodyRef, onPress, children } = props;
const onRelease = props.kind === "grab" ? props.onRelease : undefined;
const onRelease = props.kind === "grab" ? props.onRelease : null;
const camera = useThree((state) => state.camera);
const groupRef = useRef<THREE.Group>(null);
const debugSphereRef = useRef<THREE.Mesh>(null);
const handle = useRef<InteractableHandle>(
props.kind === "grab"
? { kind: props.kind, label, onPress, onRelease: props.onRelease }
: { kind: props.kind, label, onPress },
);
const handle = useRef<InteractableHandle>(createInteractableHandle(props));
useEffect(() => {
const current = handle.current as MutableInteractableHandle;
current.kind = kind;
current.label = label;
current.onPress = onPress;
const currentHandle = handle.current;
if (currentHandle.kind === kind) {
currentHandle.label = label;
currentHandle.onPress = onPress;
if (currentHandle.kind === "grab") {
if (!onRelease) return;
currentHandle.onRelease = onRelease;
}
if (kind === "grab" && onRelease) {
current.onRelease = onRelease;
return;
}
delete current.onRelease;
return undefined;
if (kind === "grab") {
if (!onRelease) return;
handle.current = { kind, label, onPress, onRelease };
} else {
handle.current = { kind, label, onPress };
}
const manager = InteractionManager.getInstance();
if (manager.getState().focused === currentHandle) {
manager.setFocused(handle.current);
}
}, [kind, label, onPress, onRelease]);
const setupInteractionDebugFolder = useCallback((folder: GUI) => {
+32 -47
View File
@@ -31,6 +31,20 @@ interface EditorControlsProps {
isPlayerMode?: boolean;
}
const TRANSFORM_OPTIONS = [
{ mode: "translate", label: "Translate", shortcut: "T", Icon: Move3D },
{ mode: "rotate", label: "Rotate", shortcut: "R", Icon: RotateCw },
{ mode: "scale", label: "Scale", shortcut: "S", Icon: Expand },
] as const;
const EDITOR_SHORTCUTS = [
["Click", "Select object"],
["T / R / S", "Transform mode"],
["Ctrl Z / Y", "Undo / redo"],
["Esc", "Deselect"],
["WASD", "Move when locked"],
] as const;
export function EditorControls({
transformMode,
onTransformModeChange,
@@ -69,33 +83,18 @@ export function EditorControls({
</div>
<div className="editor-transform-buttons">
<button
className={`editor-transform-button ${transformMode === "translate" ? "active" : ""}`}
onClick={() => onTransformModeChange("translate")}
aria-pressed={transformMode === "translate"}
>
<Move3D size={16} aria-hidden="true" />
<span>Translate</span>
<kbd>T</kbd>
</button>
<button
className={`editor-transform-button ${transformMode === "rotate" ? "active" : ""}`}
onClick={() => onTransformModeChange("rotate")}
aria-pressed={transformMode === "rotate"}
>
<RotateCw size={16} aria-hidden="true" />
<span>Rotate</span>
<kbd>R</kbd>
</button>
<button
className={`editor-transform-button ${transformMode === "scale" ? "active" : ""}`}
onClick={() => onTransformModeChange("scale")}
aria-pressed={transformMode === "scale"}
>
<Expand size={16} aria-hidden="true" />
<span>Scale</span>
<kbd>S</kbd>
</button>
{TRANSFORM_OPTIONS.map(({ mode, label, shortcut, Icon }) => (
<button
key={mode}
className={`editor-transform-button ${transformMode === mode ? "active" : ""}`}
onClick={() => onTransformModeChange(mode)}
aria-pressed={transformMode === mode}
>
<Icon size={16} aria-hidden="true" />
<span>{label}</span>
<kbd>{shortcut}</kbd>
</button>
))}
</div>
<div className="editor-history-buttons">
@@ -203,26 +202,12 @@ export function EditorControls({
</div>
<dl className="editor-shortcuts-list">
<div>
<dt>Click</dt>
<dd>Select object</dd>
</div>
<div>
<dt>T / R / S</dt>
<dd>Transform mode</dd>
</div>
<div>
<dt>Ctrl Z / Y</dt>
<dd>Undo / redo</dd>
</div>
<div>
<dt>Esc</dt>
<dd>Deselect</dd>
</div>
<div>
<dt>WASD</dt>
<dd>Move when locked</dd>
</div>
{EDITOR_SHORTCUTS.map(([keys, description]) => (
<div key={keys}>
<dt>{keys}</dt>
<dd>{description}</dd>
</div>
))}
</dl>
</section>
+50 -30
View File
@@ -29,6 +29,12 @@ interface EditorNodeCommonProps {
onHoverNode: (index: number | null) => void;
}
interface EditorNodePointerHandlers {
onClick: (event: ThreeEvent<MouseEvent>) => void;
onPointerEnter: (event: ThreeEvent<PointerEvent>) => void;
onPointerLeave: (event: ThreeEvent<PointerEvent>) => void;
}
function applyNodeTransform(object: THREE.Object3D, node: MapNode): void {
object.position.set(...node.position);
object.rotation.set(...node.rotation);
@@ -88,6 +94,36 @@ function cloneHighlightedMaterial(
return clone;
}
function getNodeHighlightColor(
isSelected: boolean,
isHovered: boolean,
): string | null {
if (isSelected) return "#ffffff";
if (isHovered) return "#b8b8b8";
return null;
}
function createEditorNodePointerHandlers(
index: number,
onSelectNode: (index: number | null) => void,
onHoverNode: (index: number | null) => void,
): EditorNodePointerHandlers {
return {
onClick: (event) => {
event.stopPropagation();
onSelectNode(index);
},
onPointerEnter: (event) => {
event.stopPropagation();
onHoverNode(index);
},
onPointerLeave: (event) => {
event.stopPropagation();
onHoverNode(null);
},
};
}
export function EditorMap({
sceneData,
selectedNodeIndex,
@@ -224,15 +260,16 @@ function EditorModelNode({
const { scene } = useGLTF(modelUrl);
const sceneInstance = useMemo(() => scene.clone(true), [scene]);
const pointerHandlers = createEditorNodePointerHandlers(
index,
onSelectNode,
onHoverNode,
);
useRegisteredEditorNode(groupRef, index, node, objectsMapRef);
useEffect(() => {
if (!groupRef.current) return;
const highlightColor = isSelected
? "#ffffff"
: isHovered
? "#b8b8b8"
: null;
const highlightColor = getNodeHighlightColor(isSelected, isHovered);
groupRef.current.traverse((child) => {
if (!(child instanceof THREE.Mesh)) {
@@ -288,18 +325,7 @@ function EditorModelNode({
position={node.position}
rotation={node.rotation}
scale={node.scale}
onClick={(e: ThreeEvent<MouseEvent>) => {
e.stopPropagation();
onSelectNode(index);
}}
onPointerEnter={(e: ThreeEvent<PointerEvent>) => {
e.stopPropagation();
onHoverNode(index);
}}
onPointerLeave={(e: ThreeEvent<PointerEvent>) => {
e.stopPropagation();
onHoverNode(null);
}}
{...pointerHandlers}
/>
);
}
@@ -314,9 +340,14 @@ function EditorFallbackNode({
onHoverNode,
}: EditorNodeCommonProps) {
const meshRef = useRef<THREE.Mesh>(null);
const pointerHandlers = createEditorNodePointerHandlers(
index,
onSelectNode,
onHoverNode,
);
useRegisteredEditorNode(meshRef, index, node, objectsMapRef);
const color = isSelected ? "#ffffff" : isHovered ? "#b8b8b8" : "#6f6f6f";
const color = getNodeHighlightColor(isSelected, isHovered) ?? "#6f6f6f";
return (
<mesh
@@ -324,18 +355,7 @@ function EditorFallbackNode({
position={node.position}
rotation={node.rotation}
scale={node.scale}
onClick={(e: ThreeEvent<MouseEvent>) => {
e.stopPropagation();
onSelectNode(index);
}}
onPointerEnter={(e: ThreeEvent<PointerEvent>) => {
e.stopPropagation();
onHoverNode(index);
}}
onPointerLeave={(e: ThreeEvent<PointerEvent>) => {
e.stopPropagation();
onHoverNode(null);
}}
{...pointerHandlers}
>
<boxGeometry args={[1, 1, 1]} />
<meshStandardMaterial color={color} />