fix: archi

This commit is contained in:
2026-04-27 10:53:50 +02:00
parent 8c84663472
commit 393b653cca
16 changed files with 242 additions and 192 deletions
+55 -25
View File
@@ -1,7 +1,8 @@
import { useEffect, useRef } from "react";
import { useCallback, useEffect, useRef } from "react";
import { useFrame, useThree } from "@react-three/fiber";
import type { RapierRigidBody } from "@react-three/rapier";
import * as THREE from "three";
import type GUI from "lil-gui";
import type { RefObject } from "react";
import {
INTERACTION_DEBUG_SPHERE_COLOR,
@@ -13,54 +14,83 @@ import { useDebugFolder } from "@/hooks/debug/useDebugFolder";
import { InteractionManager } from "@/stateManager/InteractionManager";
import { INTERACTION_RADIUS } from "@/data/interactionConfig";
import type { Vector3Tuple } from "@/types/3d";
import type { InteractableHandle, InteractableKind } from "@/types/interaction";
import type {
GrabInteractableHandle,
InteractableHandle,
TriggerInteractableHandle,
} from "@/types/interaction";
interface InteractableObjectProps {
kind: InteractableKind;
interface InteractableObjectBaseProps {
label: string;
position: Vector3Tuple;
bodyRef?: RefObject<RapierRigidBody | null>;
onPress: () => void;
onRelease?: () => void;
children: React.ReactNode;
}
interface TriggerInteractableObjectProps extends InteractableObjectBaseProps {
kind: "trigger";
}
interface GrabInteractableObjectProps extends InteractableObjectBaseProps {
kind: "grab";
onRelease: () => void;
}
type InteractableObjectProps =
| TriggerInteractableObjectProps
| GrabInteractableObjectProps;
const _cameraPos = new THREE.Vector3();
const _cameraDir = new THREE.Vector3();
const _objectPos = new THREE.Vector3();
const _raycaster = new THREE.Raycaster();
export function InteractableObject({
kind,
label,
position,
bodyRef,
onPress,
onRelease = () => {},
children,
}: InteractableObjectProps): React.JSX.Element {
export function InteractableObject(
props: InteractableObjectProps,
): React.JSX.Element {
const { kind, label, position, bodyRef, onPress, children } = props;
const camera = useThree((state) => state.camera);
const groupRef = useRef<THREE.Group>(null);
const debugSphereRef = useRef<THREE.Mesh>(null);
const handle = useRef<InteractableHandle>({
kind,
label,
onPress,
onRelease,
});
const handle = useRef<InteractableHandle>(
props.kind === "grab"
? { kind: props.kind, label, onPress, onRelease: props.onRelease }
: { kind: props.kind, label, onPress },
);
useEffect(() => {
handle.current.onPress = onPress;
handle.current.onRelease = onRelease;
});
if (props.kind === "grab") {
const current = handle.current as GrabInteractableHandle;
current.label = label;
current.onPress = onPress;
current.onRelease = props.onRelease;
return;
}
useDebugFolder("Interaction", (folder) => {
return undefined;
}, [label, onPress, props]);
useEffect(() => {
if (kind === "grab") {
return undefined;
}
const current = handle.current as TriggerInteractableHandle;
current.label = label;
current.onPress = onPress;
return undefined;
}, [kind, label, onPress]);
const setupInteractionDebugFolder = useCallback((folder: GUI) => {
folder
.add({ radius: INTERACTION_RADIUS }, "radius")
.name("Interaction radius")
.disable();
});
}, []);
useDebugFolder("Interaction", setupInteractionDebugFolder);
useFrame(() => {
const group = groupRef.current;
+11 -2
View File
@@ -35,7 +35,7 @@ export class AudioManager {
logger.error("AudioManager", "Failed to play sound", {
path,
error,
error: AudioManager._toLogValue(error),
});
});
}
@@ -66,11 +66,20 @@ export class AudioManager {
return pooledAudio;
}
return existingPool[0]!;
const recycledAudio = existingPool[0];
if (recycledAudio) return recycledAudio;
}
const initialAudio = new Audio(path);
this._audioPools.set(path, [initialAudio]);
return initialAudio;
}
private static _toLogValue(error: unknown): Error | DOMException | string {
if (error instanceof Error || error instanceof DOMException) {
return error;
}
return String(error);
}
}
+10 -3
View File
@@ -1,4 +1,5 @@
import type {
GrabInteractableHandle,
InteractableHandle,
InteractionSnapshot,
} from "@/types/interaction";
@@ -8,7 +9,7 @@ export class InteractionManager {
private _focused: InteractableHandle | null = null;
private _holding = false;
private _holdingHandle: InteractableHandle | null = null;
private _holdingHandle: GrabInteractableHandle | null = null;
private _snapshot: InteractionSnapshot = {
focused: null,
holding: false,
@@ -40,8 +41,14 @@ export class InteractionManager {
pressInteract(): void {
if (!this._focused) return;
this._holding = this._focused.kind === "grab";
if (this._holding) this._holdingHandle = this._focused;
if (this._focused.kind === "grab") {
this._holding = true;
this._holdingHandle = this._focused;
} else {
this._holding = false;
this._holdingHandle = null;
}
this._focused.onPress();
this._emit();
}
+12 -2
View File
@@ -1,12 +1,22 @@
export type InteractableKind = "grab" | "trigger";
export interface InteractableHandle {
kind: InteractableKind;
export interface TriggerInteractableHandle {
kind: "trigger";
label: string;
onPress: () => void;
}
export interface GrabInteractableHandle {
kind: "grab";
label: string;
onPress: () => void;
onRelease: () => void;
}
export type InteractableHandle =
| TriggerInteractableHandle
| GrabInteractableHandle;
export interface InteractionSnapshot {
focused: InteractableHandle | null;
holding: boolean;
+12 -1
View File
@@ -1,6 +1,17 @@
export type LogLevel = "debug" | "info" | "warn" | "error";
export type LogContext = Record<string, unknown>;
export type LogValue =
| string
| number
| boolean
| null
| undefined
| Error
| DOMException
| { [key: string]: LogValue }
| LogValue[];
export type LogContext = Readonly<Record<string, LogValue>>;
export interface LogEntry {
timestamp: string;
+2 -1
View File
@@ -1,5 +1,6 @@
import GUI from "lil-gui";
import type { CameraMode, SceneMode } from "@/types/debug";
import { isDebugEnabled } from "@/utils/debug/isDebugEnabled";
export class Debug {
private static instance: Debug | null = null;
@@ -28,7 +29,7 @@ export class Debug {
}
private constructor() {
this.active = new URLSearchParams(window.location.search).has("debug");
this.active = isDebugEnabled();
this.gui = this.active ? new GUI({ title: "La-Fabrik Debug" }) : null;
if (this.gui) {
+7
View File
@@ -0,0 +1,7 @@
export function isDebugEnabled(): boolean {
if (typeof window === "undefined") {
return false;
}
return new URLSearchParams(window.location.search).has("debug");
}
+2 -3
View File
@@ -4,6 +4,7 @@ import type {
LogLevel,
LoggerConfig,
} from "@/types/logger";
import { isDebugEnabled } from "@/utils/debug/isDebugEnabled";
const LEVEL_PRIORITY: Record<LogLevel, number> = {
debug: 10,
@@ -102,9 +103,7 @@ function resolveMinLevel(): LogLevel {
return "info";
}
const debugEnabled = new URLSearchParams(window.location.search).has("debug");
return debugEnabled ? "debug" : "info";
return isDebugEnabled() ? "debug" : "info";
}
export const logger = new Logger({
+3 -4
View File
@@ -1,4 +1,4 @@
import { useState, useCallback } from "react";
import { useState } from "react";
import type { Octree } from "three/addons/math/Octree.js";
import {
PLAYER_SPAWN_Y_GAME,
@@ -18,7 +18,6 @@ export function World(): React.JSX.Element {
const cameraMode = useCameraMode();
const sceneMode = useSceneMode();
const [octree, setOctree] = useState<Octree | null>(null);
const onOctreeReady = useCallback((o: Octree) => setOctree(o), []);
return (
<>
@@ -28,9 +27,9 @@ export function World(): React.JSX.Element {
{cameraMode === "debug" ? <DebugCameraControls /> : null}
{sceneMode === "game" ? (
<Map onOctreeReady={onOctreeReady} />
<Map onOctreeReady={setOctree} />
) : (
<TestScene onOctreeReady={onOctreeReady} />
<TestScene onOctreeReady={setOctree} />
)}
{cameraMode !== "debug" ? (
+2 -2
View File
@@ -6,13 +6,13 @@ import { PlayerCamera } from "@/world/player/PlayerCamera";
import { PlayerController } from "@/world/player/PlayerController";
interface PlayerComponentProps {
octree?: Octree | null;
octree: Octree | null;
spawnY: number;
}
export function PlayerComponent({
octree = null,
spawnY,
octree,
}: PlayerComponentProps): React.JSX.Element {
const camera = useThree((state) => state.camera);
-5
View File
@@ -123,11 +123,6 @@ export function PlayerController({ octree }: PlayerControllerProps): null {
case MOVE_RIGHT_KEY:
keys.current.right = false;
break;
case INTERACT_KEY:
if (interaction.getState().focused?.kind === "trigger") {
interaction.releaseInteract();
}
break;
default:
return;
}