This commit is contained in:
2026-04-27 11:14:43 +02:00
parent 393b653cca
commit eb0db21d29
7 changed files with 61 additions and 54 deletions
+18 -22
View File
@@ -14,11 +14,7 @@ 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 {
GrabInteractableHandle,
InteractableHandle,
TriggerInteractableHandle,
} from "@/types/interaction";
import type { InteractableHandle, InteractableKind } from "@/types/interaction";
interface InteractableObjectBaseProps {
label: string;
@@ -41,6 +37,13 @@ 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();
@@ -50,6 +53,7 @@ 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 camera = useThree((state) => state.camera);
const groupRef = useRef<THREE.Group>(null);
const debugSphereRef = useRef<THREE.Mesh>(null);
@@ -61,27 +65,19 @@ export function InteractableObject(
);
useEffect(() => {
if (props.kind === "grab") {
const current = handle.current as GrabInteractableHandle;
current.label = label;
current.onPress = onPress;
current.onRelease = props.onRelease;
const current = handle.current as MutableInteractableHandle;
current.kind = kind;
current.label = label;
current.onPress = onPress;
if (kind === "grab" && onRelease) {
current.onRelease = onRelease;
return;
}
delete current.onRelease;
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]);
}, [kind, label, onPress, onRelease]);
const setupInteractionDebugFolder = useCallback((folder: GUI) => {
folder
+4 -4
View File
@@ -1,3 +1,5 @@
import type { Vector3Tuple } from "@/types/3d";
export const PLAYER_EYE_HEIGHT = 1.75;
export const PLAYER_CAPSULE_RADIUS = 0.35;
@@ -9,7 +11,5 @@ export const PLAYER_MAX_DELTA = 0.05;
export const PLAYER_ACCELERATION_MULTIPLIER = 9;
export const PLAYER_XZ_DAMPING_FACTOR = 8;
export const PLAYER_SPAWN_X = 0;
export const PLAYER_SPAWN_Z = 0;
export const PLAYER_SPAWN_Y_GAME = 100;
export const PLAYER_SPAWN_Y_PHYSICS = 3;
export const PLAYER_SPAWN_POSITION_GAME: Vector3Tuple = [0, 100, 0];
export const PLAYER_SPAWN_POSITION_PHYSICS: Vector3Tuple = [0, 3, 0];
@@ -4,11 +4,13 @@ import {
DEBUG_CAMERA_MAX_DISTANCE,
DEBUG_CAMERA_MIN_DISTANCE,
} from "@/data/debugConfig";
import {
import { PLAYER_EYE_HEIGHT, PLAYER_SPAWN_POSITION_GAME } from "@/data/playerConfig";
const DEBUG_CAMERA_TARGET = [
PLAYER_SPAWN_POSITION_GAME[0],
PLAYER_EYE_HEIGHT,
PLAYER_SPAWN_X,
PLAYER_SPAWN_Z,
} from "@/data/playerConfig";
PLAYER_SPAWN_POSITION_GAME[2],
] as const;
export function DebugCameraControls(): React.JSX.Element {
return (
@@ -17,7 +19,7 @@ export function DebugCameraControls(): React.JSX.Element {
dampingFactor={DEBUG_CAMERA_DAMPING_FACTOR}
minDistance={DEBUG_CAMERA_MIN_DISTANCE}
maxDistance={DEBUG_CAMERA_MAX_DISTANCE}
target={[PLAYER_SPAWN_X, PLAYER_EYE_HEIGHT, PLAYER_SPAWN_Z]}
target={DEBUG_CAMERA_TARGET}
/>
);
}
+7 -5
View File
@@ -1,8 +1,8 @@
import { useState } from "react";
import type { Octree } from "three/addons/math/Octree.js";
import {
PLAYER_SPAWN_Y_GAME,
PLAYER_SPAWN_Y_PHYSICS,
PLAYER_SPAWN_POSITION_GAME,
PLAYER_SPAWN_POSITION_PHYSICS,
} from "@/data/playerConfig";
import { useCameraMode } from "@/hooks/debug/useCameraMode";
import { useSceneMode } from "@/hooks/debug/useSceneMode";
@@ -18,6 +18,10 @@ export function World(): React.JSX.Element {
const cameraMode = useCameraMode();
const sceneMode = useSceneMode();
const [octree, setOctree] = useState<Octree | null>(null);
const playerSpawnPosition =
sceneMode === "game"
? PLAYER_SPAWN_POSITION_GAME
: PLAYER_SPAWN_POSITION_PHYSICS;
return (
<>
@@ -35,9 +39,7 @@ export function World(): React.JSX.Element {
{cameraMode !== "debug" ? (
<PlayerComponent
octree={octree}
spawnY={
sceneMode === "game" ? PLAYER_SPAWN_Y_GAME : PLAYER_SPAWN_Y_PHYSICS
}
spawnPosition={playerSpawnPosition}
/>
) : null}
</>
+6 -6
View File
@@ -1,29 +1,29 @@
import { useEffect } from "react";
import { useThree } from "@react-three/fiber";
import type { Octree } from "three/addons/math/Octree.js";
import { PLAYER_SPAWN_X, PLAYER_SPAWN_Z } from "@/data/playerConfig";
import type { Vector3Tuple } from "@/types/3d";
import { PlayerCamera } from "@/world/player/PlayerCamera";
import { PlayerController } from "@/world/player/PlayerController";
interface PlayerComponentProps {
octree: Octree | null;
spawnY: number;
spawnPosition: Vector3Tuple;
}
export function PlayerComponent({
spawnY,
spawnPosition,
octree,
}: PlayerComponentProps): React.JSX.Element {
const camera = useThree((state) => state.camera);
useEffect(() => {
camera.position.set(PLAYER_SPAWN_X, spawnY, PLAYER_SPAWN_Z);
}, [camera, spawnY]);
camera.position.set(...spawnPosition);
}, [camera, spawnPosition]);
return (
<>
<PlayerCamera />
<PlayerController octree={octree} />
<PlayerController octree={octree} spawnPosition={spawnPosition} />
</>
);
}
+17 -10
View File
@@ -20,12 +20,11 @@ import {
PLAYER_GRAVITY,
PLAYER_JUMP_SPEED,
PLAYER_MAX_DELTA,
PLAYER_SPAWN_X,
PLAYER_SPAWN_Z,
PLAYER_WALK_SPEED,
PLAYER_XZ_DAMPING_FACTOR,
} from "@/data/playerConfig";
import { InteractionManager } from "@/stateManager/InteractionManager";
import type { Vector3Tuple } from "@/types/3d";
type Keys = {
forward: boolean;
@@ -45,6 +44,7 @@ const DEFAULT_KEYS: Keys = {
interface PlayerControllerProps {
octree: Octree | null;
spawnPosition: Vector3Tuple;
}
const _forward = new THREE.Vector3();
@@ -52,8 +52,12 @@ const _right = new THREE.Vector3();
const _wishDir = new THREE.Vector3();
const _up = new THREE.Vector3(0, 1, 0);
const _translateVec = new THREE.Vector3();
const _collisionCorrection = new THREE.Vector3();
export function PlayerController({ octree }: PlayerControllerProps): null {
export function PlayerController({
octree,
spawnPosition,
}: PlayerControllerProps): null {
const camera = useThree((state) => state.camera);
const keys = useRef<Keys>({ ...DEFAULT_KEYS });
const velocity = useRef(new THREE.Vector3());
@@ -69,14 +73,17 @@ export function PlayerController({ octree }: PlayerControllerProps): null {
);
useEffect(() => {
const spawnY = camera.position.y;
capsule.current.start.set(
PLAYER_SPAWN_X,
spawnY - PLAYER_EYE_HEIGHT + PLAYER_CAPSULE_RADIUS,
PLAYER_SPAWN_Z,
spawnPosition[0],
spawnPosition[1] - PLAYER_EYE_HEIGHT + PLAYER_CAPSULE_RADIUS,
spawnPosition[2],
);
capsule.current.end.set(PLAYER_SPAWN_X, spawnY, PLAYER_SPAWN_Z);
}, [camera]);
capsule.current.end.set(...spawnPosition);
velocity.current.set(0, 0, 0);
onFloor.current = false;
wantsJump.current = false;
camera.position.copy(capsule.current.end);
}, [camera, spawnPosition]);
useEffect(() => {
const interaction = InteractionManager.getInstance();
@@ -215,7 +222,7 @@ export function PlayerController({ octree }: PlayerControllerProps): null {
}
capsule.current.translate(
result.normal.clone().multiplyScalar(result.depth),
_collisionCorrection.copy(result.normal).multiplyScalar(result.depth),
);
}
}
+2 -2
View File
@@ -1,12 +1,12 @@
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import path from "node:path";
import { fileURLToPath } from "node:url";
export default defineConfig({
plugins: [react()],
resolve: {
alias: {
"@": path.resolve(__dirname, "./src"),
"@": fileURLToPath(new URL("./src", import.meta.url)),
},
},
});