fix(repair): keep ebike at zone Y in test scene
Adds an opt-out 'snapToTerrain' prop on Ebike so the parked position
keeps the explicit Y supplied by callers instead of resolving against
the world terrain GLTF. TestMap passes snapToTerrain={false} since it
does not render the world terrain — without this the bike was being
positioned at the invisible terrain height, far above the test floor,
and looked missing.
This commit is contained in:
@@ -33,9 +33,19 @@ const _up = new THREE.Vector3(0, 1, 0);
|
||||
|
||||
interface EbikeProps {
|
||||
position: Vector3Tuple;
|
||||
/**
|
||||
* When true (default), the parked position is snapped to the world terrain
|
||||
* height. Pass false in test scenes that don't render the world terrain so
|
||||
* the bike stays at the explicit Y of {@link position} instead of floating
|
||||
* at the (invisible) terrain height.
|
||||
*/
|
||||
snapToTerrain?: boolean;
|
||||
}
|
||||
|
||||
export function Ebike({ position }: EbikeProps): React.JSX.Element {
|
||||
export function Ebike({
|
||||
position,
|
||||
snapToTerrain = true,
|
||||
}: EbikeProps): React.JSX.Element {
|
||||
const groupRef = useRef<THREE.Group>(null);
|
||||
const { scene } = useLoggedGLTF(EBIKE_MODEL_PATH, {
|
||||
scope: "Ebike",
|
||||
@@ -45,7 +55,7 @@ export function Ebike({ position }: EbikeProps): React.JSX.Element {
|
||||
const terrainHeight = useTerrainHeightSampler();
|
||||
const parkedPosition = useMemo<Vector3Tuple>(() => {
|
||||
const [x, y, z] = position;
|
||||
const height = terrainHeight.getHeight(x, z) ?? y;
|
||||
const height = snapToTerrain ? (terrainHeight.getHeight(x, z) ?? y) : y;
|
||||
const bottomOffset = getObjectBottomOffset(model, [
|
||||
EBIKE_WORLD_SCALE,
|
||||
EBIKE_WORLD_SCALE,
|
||||
@@ -53,7 +63,7 @@ export function Ebike({ position }: EbikeProps): React.JSX.Element {
|
||||
]);
|
||||
|
||||
return [x, height + bottomOffset, z];
|
||||
}, [model, position, terrainHeight]);
|
||||
}, [model, position, snapToTerrain, terrainHeight]);
|
||||
const movementMode = useGameStore((state) => state.player.movementMode);
|
||||
const mainState = useGameStore((state) => state.mainState);
|
||||
const ebikeStep = useGameStore((state) => state.ebike.currentStep);
|
||||
@@ -135,7 +145,9 @@ export function Ebike({ position }: EbikeProps): React.JSX.Element {
|
||||
// SpotLight target must be in the scene to define the cone direction.
|
||||
useEffect(() => {
|
||||
threeScene.add(headlightTarget);
|
||||
return () => { threeScene.remove(headlightTarget); };
|
||||
return () => {
|
||||
threeScene.remove(headlightTarget);
|
||||
};
|
||||
}, [threeScene, headlightTarget]);
|
||||
|
||||
// Link the target to the SpotLight once it mounts.
|
||||
@@ -192,7 +204,9 @@ export function Ebike({ position }: EbikeProps): React.JSX.Element {
|
||||
console.log("[Ebike] Fork found:", (forkNode as THREE.Object3D).name);
|
||||
} else {
|
||||
const names: string[] = [];
|
||||
model.traverse((c) => { if (c.name) names.push(c.name); });
|
||||
model.traverse((c) => {
|
||||
if (c.name) names.push(c.name);
|
||||
});
|
||||
console.warn("[Ebike] Fork not found. All nodes:", names);
|
||||
}
|
||||
}, [model]);
|
||||
@@ -460,7 +474,11 @@ export function Ebike({ position }: EbikeProps): React.JSX.Element {
|
||||
>
|
||||
<mesh>
|
||||
<sphereGeometry args={[8, 15, 12]} />
|
||||
<meshBasicMaterial colorWrite={false} color={"red"} depthWrite={false} />
|
||||
<meshBasicMaterial
|
||||
colorWrite={false}
|
||||
color={"red"}
|
||||
depthWrite={false}
|
||||
/>
|
||||
</mesh>
|
||||
</InteractableObject>
|
||||
|
||||
@@ -469,7 +487,12 @@ export function Ebike({ position }: EbikeProps): React.JSX.Element {
|
||||
Speedmeter: upper-half arc overlay, renderOrder 10 001
|
||||
rotation: Math.PI/2 radians = 90° (NOT the number 90 which = ~116.6°) */}
|
||||
<group position={[2, 6, 0]} rotation={[0, -80, 0]}>
|
||||
<EbikeSpeedmeter width={3} height={1.5} position={[0, 0.4, 0]} gaugeInnerR={0.33} gaugeOuterR={0.445}
|
||||
<EbikeSpeedmeter
|
||||
width={3}
|
||||
height={1.5}
|
||||
position={[0, 0.4, 0]}
|
||||
gaugeInnerR={0.33}
|
||||
gaugeOuterR={0.445}
|
||||
gaugeWidth={2.5}
|
||||
gaugeHeight={2.1}
|
||||
gaugeOffsetX={0}
|
||||
|
||||
@@ -241,7 +241,7 @@ export function TestMap({ onOctreeReady }: TestMapProps): React.JSX.Element {
|
||||
<RepairPlaygroundZoneMarker color={zone.color} />
|
||||
</group>
|
||||
{zone.mission === "ebike" ? (
|
||||
<Ebike position={zone.position} />
|
||||
<Ebike position={zone.position} snapToTerrain={false} />
|
||||
) : null}
|
||||
<RepairGame mission={zone.mission} position={zone.position} />
|
||||
</group>
|
||||
|
||||
Reference in New Issue
Block a user