upatde(fabrik): zone + herbe
🔍 Lint / 🪄 Check lint (pull_request) Has been cancelled
🔍 Lint / 🎨 Check format (pull_request) Has been cancelled
🔍 Lint / 🔎 Typecheck (pull_request) Has been cancelled
📊 Quality / 🔒 Security Audit (pull_request) Has been cancelled
📊 Quality / 📋 Dependency Freshness (pull_request) Has been cancelled
📊 Quality / 📦 Bundle Size (pull_request) Has been cancelled
🔍 Lint / 🏗 Build (pull_request) Has been cancelled

This commit is contained in:
Tom Boullay
2026-06-01 00:14:39 +02:00
parent c33d973f12
commit 27b4a2c392
12 changed files with 176 additions and 11 deletions
+29 -4
View File
@@ -18,6 +18,7 @@ import {
useTerrainHeightSampler,
} from "@/hooks/three/useTerrainHeight";
import { WorldBoundsCollision } from "@/world/collision/WorldBoundsCollision";
import { flattenLaFabrikTerrainFootprint } from "@/data/world/laFabrikConfig";
import type { MapNode } from "@/types/map/mapScene";
import type { OctreeReadyHandler } from "@/types/three/three";
import type { SceneLoadingChangeHandler } from "@/types/world/sceneLoading";
@@ -213,7 +214,7 @@ function CollisionModelInstance({
modelUrl: string;
onLoaded: () => void;
terrainHeight: TerrainHeightSampler;
}): React.JSX.Element {
}): React.JSX.Element | null {
const { position, rotation, scale } = node;
const normalizedScale = normalizeMapScale(scale);
const { scene } = useLoggedGLTF(modelUrl, {
@@ -223,22 +224,46 @@ function CollisionModelInstance({
scale: normalizedScale,
});
const sceneInstance = useClonedObject(scene);
const collisionSceneInstance = useMemo(() => {
if (node.name === "terrain") {
flattenLaFabrikTerrainFootprint(
sceneInstance,
position,
rotation,
normalizedScale,
);
}
return sceneInstance;
}, [node.name, normalizedScale, position, rotation, sceneInstance]);
const collisionPosition = useMemo(() => {
if (node.name === "terrain") return position;
const [x, y, z] = position;
const height = terrainHeight.getHeight(x, z);
const bottomOffset = getObjectBottomOffset(sceneInstance, normalizedScale);
const bottomOffset = getObjectBottomOffset(
collisionSceneInstance,
normalizedScale,
);
return [x, height !== null ? height + bottomOffset : y, z] as const;
}, [node.name, normalizedScale, position, sceneInstance, terrainHeight]);
}, [
node.name,
normalizedScale,
position,
collisionSceneInstance,
terrainHeight,
]);
useEffect(() => {
onLoaded();
}, [onLoaded]);
if (node.name === "lafabrik") {
return null;
}
return (
<primitive
object={sceneInstance}
object={collisionSceneInstance}
position={collisionPosition}
rotation={rotation}
scale={normalizedScale}
+8
View File
@@ -18,6 +18,7 @@ import {
SUN_Z_MIN,
SUN_Z_STEP,
} from "@/data/world/lightingConfig";
import { LA_FABRIK_INTERIOR_LIGHT_POSITION } from "@/data/world/laFabrikConfig";
import { useDebugFolder } from "@/hooks/debug/useDebugFolder";
import { LIGHTING_STATE } from "@/world/lightingState";
@@ -121,6 +122,13 @@ export function Lighting(): React.JSX.Element {
castShadow
/>
<object3D ref={sunTarget} />
<pointLight
position={LA_FABRIK_INTERIOR_LIGHT_POSITION}
color="#dbeafe"
intensity={1.2}
distance={14}
decay={1.6}
/>
</>
);
}
+5 -2
View File
@@ -3,15 +3,18 @@ import { AnimatedModel } from "@/components/three/models/AnimatedModel";
import {
CHARACTER_CONFIGS,
CHARACTER_IDS,
type CharacterConfig,
type CharacterId,
} from "@/data/world/characters/characterConfig";
import { useTerrainSnappedPosition } from "@/hooks/three/useTerrainHeight";
import { useCharacterDebugStore } from "@/managers/stores/useCharacterDebugStore";
function CharacterModel({ id }: { id: CharacterId }): React.JSX.Element {
const config = CHARACTER_CONFIGS[id];
const config: CharacterConfig = CHARACTER_CONFIGS[id];
const state = useCharacterDebugStore((store) => store.characters[id]);
const position = useTerrainSnappedPosition(state.position);
const snappedPosition = useTerrainSnappedPosition(state.position);
const position =
config.snapToTerrain === false ? state.position : snappedPosition;
return (
<AnimatedModel
+16
View File
@@ -8,6 +8,11 @@ import {
GRASS_COLORS,
GRASS_CONFIG,
} from "@/data/world/grassConfig";
import {
LA_FABRIK_CENTER,
LA_FABRIK_HALF_EXTENTS,
LA_FABRIK_ROTATION_Y,
} from "@/data/world/laFabrikConfig";
import {
grassFragmentShader,
grassVertexShader,
@@ -169,6 +174,17 @@ function createGrassMaterial(
uMaxBladeHeight: { value: GRASS_CONFIG.maxBladeHeight },
uRandomHeightAmount: { value: GRASS_CONFIG.randomHeightAmount },
uSurfaceOffset: { value: GRASS_CONFIG.surfaceOffset },
uLaFabrikCenter: {
value: new THREE.Vector2(LA_FABRIK_CENTER[0], LA_FABRIK_CENTER[2]),
},
uLaFabrikHalfExtents: {
value: new THREE.Vector2(
LA_FABRIK_HALF_EXTENTS.x,
LA_FABRIK_HALF_EXTENTS.z,
),
},
uLaFabrikRotation: { value: LA_FABRIK_ROTATION_Y },
uLaFabrikNoGrassFeather: { value: 1.4 },
},
});
}
+16
View File
@@ -43,6 +43,10 @@ export const grassVertexShader = /* glsl */ `
uniform float uMaxBladeHeight;
uniform float uRandomHeightAmount;
uniform float uSurfaceOffset;
uniform vec2 uLaFabrikCenter;
uniform vec2 uLaFabrikHalfExtents;
uniform float uLaFabrikRotation;
uniform float uLaFabrikNoGrassFeather;
float random(vec2 st) {
return fract(sin(dot(st.xy, vec2(12.9898, 78.233))) * 43758.5453123);
@@ -132,6 +136,18 @@ export const grassVertexShader = /* glsl */ `
smoothstep(uBoundingBoxMax.z, uBoundingBoxMax.z - 2.0, worldPos.z);
heightModifier *= edgeFade * mix(0.45, 1.0, clumpMask);
vec2 laFabrikDelta = worldPos.xz - uLaFabrikCenter;
float laFabrikCos = cos(-uLaFabrikRotation);
float laFabrikSin = sin(-uLaFabrikRotation);
vec2 laFabrikLocal = vec2(
laFabrikDelta.x * laFabrikCos - laFabrikDelta.y * laFabrikSin,
laFabrikDelta.x * laFabrikSin + laFabrikDelta.y * laFabrikCos
);
vec2 laFabrikDistance = abs(laFabrikLocal) - uLaFabrikHalfExtents;
float laFabrikOutsideDistance = max(laFabrikDistance.x, laFabrikDistance.y);
float laFabrikGrassMask = smoothstep(0.0, uLaFabrikNoGrassFeather, laFabrikOutsideDistance);
heightModifier *= laFabrikGrassMask;
float sideFactor = (color.r == 0.1) ? 1.0 : (color.b == 0.1) ? -1.0 : 0.0;
float tipFactor = color.g;
float width = smoothstep(0.02, uMaxBladeHeight * 0.85, heightModifier) * uBladeWidth * bladeVisibility;
@@ -17,7 +17,8 @@ export function GeneratedMapNodeInstance({
node,
onLoaded,
}: GeneratedMapNodeInstanceProps): React.JSX.Element | null {
const position = useTerrainSnappedPosition(node.position);
const snappedPosition = useTerrainSnappedPosition(node.position);
const position = node.name === "lafabrik" ? node.position : snappedPosition;
const scale = normalizeMapScale(node.scale);
if (node.name === "ecole") {