clean branch-scoped code quality issues
This commit is contained in:
@@ -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) => {
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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} />
|
||||
|
||||
Reference in New Issue
Block a user