fix(review): address audit findings before merge
🔍 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
🔍 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:
@@ -1,6 +1,7 @@
|
||||
import {
|
||||
GAME_SCENE_FALLBACK_BACKGROUND_COLOR,
|
||||
GAME_SCENE_SKY_FALLBACK_MODEL_PATH,
|
||||
GAME_SCENE_SKY_FALLBACK_MODEL_SCALE,
|
||||
GAME_SCENE_SKY_MODEL_PATH,
|
||||
GAME_SCENE_SKY_MODEL_SCALE,
|
||||
PHYSICS_SCENE_BACKGROUND_COLOR,
|
||||
@@ -36,6 +37,7 @@ export function Environment(): React.JSX.Element {
|
||||
{showSky ? (
|
||||
<SkyModel
|
||||
fallbackColor={GAME_SCENE_FALLBACK_BACKGROUND_COLOR}
|
||||
fallbackModelScale={GAME_SCENE_SKY_FALLBACK_MODEL_SCALE}
|
||||
fallbackModelPath={GAME_SCENE_SKY_FALLBACK_MODEL_PATH}
|
||||
modelPath={GAME_SCENE_SKY_MODEL_PATH}
|
||||
scale={GAME_SCENE_SKY_MODEL_SCALE}
|
||||
|
||||
@@ -4,25 +4,15 @@ import {
|
||||
REPAIR_MISSION_POSITION_ENTRIES,
|
||||
REPAIR_MISSION_TRIGGERS,
|
||||
} from "@/data/gameplay/repairMissionAnchors";
|
||||
import {
|
||||
INTRO_STAGE_ANCHOR,
|
||||
OUTRO_STAGE_ANCHOR,
|
||||
} from "@/data/gameplay/gameStageAnchors";
|
||||
import { useGameStore } from "@/managers/stores/useGameStore";
|
||||
import { useRepairMissionAnchorStore } from "@/managers/stores/useRepairMissionAnchorStore";
|
||||
import type { RepairMissionId } from "@/types/gameplay/repairMission";
|
||||
import type { RepairMissionTriggerConfig } from "@/types/gameplay/repairMission";
|
||||
import type { Vector3Tuple } from "@/types/three/three";
|
||||
|
||||
const FALLBACK_REPAIR_MISSION_POSITIONS = new Map(
|
||||
REPAIR_MISSION_POSITION_ENTRIES.map(({ mission, position }) => [
|
||||
mission,
|
||||
position,
|
||||
]),
|
||||
);
|
||||
|
||||
function getRepairMissionPosition(
|
||||
mission: RepairMissionId,
|
||||
anchors: Partial<Record<RepairMissionId, Vector3Tuple>>,
|
||||
): Vector3Tuple | undefined {
|
||||
return anchors[mission] ?? FALLBACK_REPAIR_MISSION_POSITIONS.get(mission);
|
||||
}
|
||||
import { getRepairMissionPosition } from "@/utils/gameplay/repairMissionPosition";
|
||||
|
||||
interface StageAnchorProps {
|
||||
color: string;
|
||||
@@ -89,9 +79,7 @@ export function GameStageContent(): React.JSX.Element {
|
||||
|
||||
return (
|
||||
<>
|
||||
{mainState === "intro" ? (
|
||||
<StageAnchor color="#7dd3fc" position={[0, 4, 0]} />
|
||||
) : null}
|
||||
{mainState === "intro" ? <StageAnchor {...INTRO_STAGE_ANCHOR} /> : null}
|
||||
{REPAIR_MISSION_POSITION_ENTRIES.map(({ mission }) => {
|
||||
const position = getRepairMissionPosition(mission, anchors);
|
||||
if (!position) return null;
|
||||
@@ -102,9 +90,7 @@ export function GameStageContent(): React.JSX.Element {
|
||||
{REPAIR_MISSION_TRIGGERS.map((config) => (
|
||||
<RepairMissionTrigger key={config.mission} config={config} />
|
||||
))}
|
||||
{mainState === "outro" ? (
|
||||
<StageAnchor color="#fb7185" position={[0, 6, 10]} scale={1.25} />
|
||||
) : null}
|
||||
{mainState === "outro" ? <StageAnchor {...OUTRO_STAGE_ANCHOR} /> : null}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
+4
-4
@@ -7,7 +7,7 @@ import {
|
||||
import { useCameraMode } from "@/hooks/debug/useCameraMode";
|
||||
import { useEnvironmentDebug } from "@/hooks/debug/useEnvironmentDebug";
|
||||
import { useMapPerformanceDebug } from "@/hooks/debug/useMapPerformanceDebug";
|
||||
import { usePersonnageDebug } from "@/hooks/debug/usePersonnageDebug";
|
||||
import { useCharacterDebug } from "@/hooks/debug/useCharacterDebug";
|
||||
import { useSceneMode } from "@/hooks/debug/useSceneMode";
|
||||
import { useHandTrackingSnapshot } from "@/hooks/handTracking/useHandTrackingSnapshot";
|
||||
import { useWorldSceneLoading } from "@/hooks/world/useWorldSceneLoading";
|
||||
@@ -29,7 +29,7 @@ import { GameMusic } from "@/world/GameMusic";
|
||||
import { Lighting } from "@/world/Lighting";
|
||||
import { GameMap } from "@/world/GameMap";
|
||||
import { GameStageContent } from "@/world/GameStageContent";
|
||||
import { PersonnageSystem } from "@/world/personnages/PersonnageSystem";
|
||||
import { CharacterSystem } from "@/world/characters/CharacterSystem";
|
||||
import { Player } from "@/world/player/Player";
|
||||
import { TestMap } from "@/world/debug/TestMap";
|
||||
import type { SceneLoadingChangeHandler } from "@/types/world/sceneLoading";
|
||||
@@ -41,7 +41,7 @@ interface WorldProps {
|
||||
export function World({ onLoadingStateChange }: WorldProps): React.JSX.Element {
|
||||
useEnvironmentDebug();
|
||||
useMapPerformanceDebug();
|
||||
usePersonnageDebug();
|
||||
useCharacterDebug();
|
||||
|
||||
const cameraMode = useCameraMode();
|
||||
const sceneMode = useSceneMode();
|
||||
@@ -90,7 +90,7 @@ export function World({ onLoadingStateChange }: WorldProps): React.JSX.Element {
|
||||
onLoadingStateChange={onLoadingStateChange}
|
||||
onOctreeReady={handleOctreeReady}
|
||||
/>
|
||||
{showGameStage ? <PersonnageSystem /> : null}
|
||||
{showGameStage && mainState !== "ebike" ? <CharacterSystem /> : null}
|
||||
{showGameStage ? (
|
||||
<Physics>
|
||||
<GameStageLoaded onLoaded={handleGameStageLoaded} />
|
||||
|
||||
+12
-12
@@ -1,16 +1,16 @@
|
||||
import { Suspense } from "react";
|
||||
import { AnimatedModel } from "@/components/three/models/AnimatedModel";
|
||||
import {
|
||||
PERSONNAGE_CONFIGS,
|
||||
PERSONNAGE_IDS,
|
||||
type PersonnageId,
|
||||
} from "@/data/world/personnages/personnageConfig";
|
||||
CHARACTER_CONFIGS,
|
||||
CHARACTER_IDS,
|
||||
type CharacterId,
|
||||
} from "@/data/world/characters/characterConfig";
|
||||
import { useTerrainSnappedPosition } from "@/hooks/three/useTerrainHeight";
|
||||
import { usePersonnageDebugStore } from "@/managers/stores/usePersonnageDebugStore";
|
||||
import { useCharacterDebugStore } from "@/managers/stores/useCharacterDebugStore";
|
||||
|
||||
function PersonnageModel({ id }: { id: PersonnageId }): React.JSX.Element {
|
||||
const config = PERSONNAGE_CONFIGS[id];
|
||||
const state = usePersonnageDebugStore((store) => store.personnages[id]);
|
||||
function CharacterModel({ id }: { id: CharacterId }): React.JSX.Element {
|
||||
const config = CHARACTER_CONFIGS[id];
|
||||
const state = useCharacterDebugStore((store) => store.characters[id]);
|
||||
const position = useTerrainSnappedPosition(state.position);
|
||||
|
||||
return (
|
||||
@@ -24,12 +24,12 @@ function PersonnageModel({ id }: { id: PersonnageId }): React.JSX.Element {
|
||||
);
|
||||
}
|
||||
|
||||
export function PersonnageSystem(): React.JSX.Element {
|
||||
export function CharacterSystem(): React.JSX.Element {
|
||||
return (
|
||||
<group name="personnage-system">
|
||||
{PERSONNAGE_IDS.map((id) => (
|
||||
<group name="character-system">
|
||||
{CHARACTER_IDS.map((id) => (
|
||||
<Suspense key={id} fallback={null}>
|
||||
<PersonnageModel id={id} />
|
||||
<CharacterModel id={id} />
|
||||
</Suspense>
|
||||
))}
|
||||
</group>
|
||||
@@ -24,89 +24,84 @@ function random01(seed: number): number {
|
||||
return value - Math.floor(value);
|
||||
}
|
||||
|
||||
function pushVector(target: number[], value: THREE.Vector3): void {
|
||||
target.push(value.x, value.y, value.z);
|
||||
}
|
||||
|
||||
function pushColor(target: number[], value: THREE.Color): void {
|
||||
target.push(value.r, value.g, value.b);
|
||||
}
|
||||
const GRASS_COLOR_VALUES = GRASS_COLORS.map((color) => new THREE.Color(color));
|
||||
const MARKER_COLOR_VALUES = [0.1, 0, 0, 0, 0, 0.1, 1, 1, 1] as const;
|
||||
|
||||
function createGrassGeometry(density: number): THREE.BufferGeometry {
|
||||
const positions: number[] = [];
|
||||
const colors: number[] = [];
|
||||
const uvs: number[] = [];
|
||||
const bladeOrigins: number[] = [];
|
||||
const yaws: number[] = [];
|
||||
const bladeCount = Math.round(GRASS_CONFIG.bladeCount * density);
|
||||
const vertexCount = bladeCount * 3;
|
||||
const positions = new Float32Array(vertexCount * 3);
|
||||
const markerColorValues = new Float32Array(vertexCount * 3);
|
||||
const bladeColorValues = new Float32Array(vertexCount * 3);
|
||||
const uvs = new Float32Array(vertexCount * 2);
|
||||
const bladeOrigins = new Float32Array(vertexCount * 3);
|
||||
const yaws = new Float32Array(vertexCount * 3);
|
||||
const halfPatchSize = GRASS_CONFIG.patchSize * 0.5;
|
||||
|
||||
for (let index = 0; index < bladeCount; index++) {
|
||||
const seed = index * 997;
|
||||
const origin = new THREE.Vector3(
|
||||
random01(seed + 1) * GRASS_CONFIG.patchSize - halfPatchSize,
|
||||
0,
|
||||
random01(seed + 2) * GRASS_CONFIG.patchSize - halfPatchSize,
|
||||
);
|
||||
const originX = random01(seed + 1) * GRASS_CONFIG.patchSize - halfPatchSize;
|
||||
const originY = 0;
|
||||
const originZ = random01(seed + 2) * GRASS_CONFIG.patchSize - halfPatchSize;
|
||||
const yawAngle = random01(seed + 3) * Math.PI * 2;
|
||||
const yaw = new THREE.Vector3(Math.sin(yawAngle), 0, -Math.cos(yawAngle));
|
||||
const yawX = Math.sin(yawAngle);
|
||||
const yawY = 0;
|
||||
const yawZ = -Math.cos(yawAngle);
|
||||
const colorIndex = Math.floor(random01(seed + 4) * GRASS_COLORS.length);
|
||||
const color = new THREE.Color(GRASS_COLORS[colorIndex] ?? GRASS_COLORS[0]);
|
||||
const markerColors = [
|
||||
new THREE.Color(0.1, 0, 0),
|
||||
new THREE.Color(0, 0, 0.1),
|
||||
new THREE.Color(1, 1, 1),
|
||||
] as const;
|
||||
const uv = new THREE.Vector2(
|
||||
origin.x / GRASS_CONFIG.patchSize + 0.5,
|
||||
origin.z / GRASS_CONFIG.patchSize + 0.5,
|
||||
);
|
||||
const color = GRASS_COLOR_VALUES[colorIndex] ?? GRASS_COLOR_VALUES[0];
|
||||
const uvX = originX / GRASS_CONFIG.patchSize + 0.5;
|
||||
const uvY = originZ / GRASS_CONFIG.patchSize + 0.5;
|
||||
|
||||
for (let vertexIndex = 0; vertexIndex < 3; vertexIndex++) {
|
||||
pushVector(positions, origin);
|
||||
pushColor(colors, markerColors[vertexIndex] ?? markerColors[2]);
|
||||
pushVector(bladeOrigins, origin);
|
||||
pushVector(yaws, yaw);
|
||||
pushColor(colors, color);
|
||||
uvs.push(uv.x, uv.y);
|
||||
const vertexOffset = index * 3 + vertexIndex;
|
||||
const vectorOffset = vertexOffset * 3;
|
||||
const uvOffset = vertexOffset * 2;
|
||||
const markerOffset = vertexIndex * 3;
|
||||
|
||||
positions[vectorOffset] = originX;
|
||||
positions[vectorOffset + 1] = originY;
|
||||
positions[vectorOffset + 2] = originZ;
|
||||
|
||||
markerColorValues[vectorOffset] = MARKER_COLOR_VALUES[markerOffset] ?? 1;
|
||||
markerColorValues[vectorOffset + 1] =
|
||||
MARKER_COLOR_VALUES[markerOffset + 1] ?? 1;
|
||||
markerColorValues[vectorOffset + 2] =
|
||||
MARKER_COLOR_VALUES[markerOffset + 2] ?? 1;
|
||||
|
||||
bladeColorValues[vectorOffset] = color?.r ?? 0;
|
||||
bladeColorValues[vectorOffset + 1] = color?.g ?? 0;
|
||||
bladeColorValues[vectorOffset + 2] = color?.b ?? 0;
|
||||
|
||||
bladeOrigins[vectorOffset] = originX;
|
||||
bladeOrigins[vectorOffset + 1] = originY;
|
||||
bladeOrigins[vectorOffset + 2] = originZ;
|
||||
|
||||
yaws[vectorOffset] = yawX;
|
||||
yaws[vectorOffset + 1] = yawY;
|
||||
yaws[vectorOffset + 2] = yawZ;
|
||||
|
||||
uvs[uvOffset] = uvX;
|
||||
uvs[uvOffset + 1] = uvY;
|
||||
}
|
||||
}
|
||||
|
||||
const geometry = new THREE.BufferGeometry();
|
||||
const markerColorValues: number[] = [];
|
||||
const bladeColorValues: number[] = [];
|
||||
|
||||
for (let index = 0; index < colors.length; index += 6) {
|
||||
markerColorValues.push(
|
||||
colors[index] ?? 0,
|
||||
colors[index + 1] ?? 0,
|
||||
colors[index + 2] ?? 0,
|
||||
);
|
||||
bladeColorValues.push(
|
||||
colors[index + 3] ?? 0,
|
||||
colors[index + 4] ?? 0,
|
||||
colors[index + 5] ?? 0,
|
||||
);
|
||||
}
|
||||
|
||||
geometry.setAttribute(
|
||||
"position",
|
||||
new THREE.Float32BufferAttribute(positions, 3),
|
||||
);
|
||||
geometry.setAttribute("position", new THREE.BufferAttribute(positions, 3));
|
||||
geometry.setAttribute(
|
||||
"color",
|
||||
new THREE.Float32BufferAttribute(markerColorValues, 3),
|
||||
new THREE.BufferAttribute(markerColorValues, 3),
|
||||
);
|
||||
geometry.setAttribute(
|
||||
"aBladeColor",
|
||||
new THREE.Float32BufferAttribute(bladeColorValues, 3),
|
||||
new THREE.BufferAttribute(bladeColorValues, 3),
|
||||
);
|
||||
geometry.setAttribute("uv", new THREE.Float32BufferAttribute(uvs, 2));
|
||||
geometry.setAttribute("uv", new THREE.BufferAttribute(uvs, 2));
|
||||
geometry.setAttribute(
|
||||
"aBladeOrigin",
|
||||
new THREE.Float32BufferAttribute(bladeOrigins, 3),
|
||||
new THREE.BufferAttribute(bladeOrigins, 3),
|
||||
);
|
||||
geometry.setAttribute("aYaw", new THREE.Float32BufferAttribute(yaws, 3));
|
||||
geometry.setAttribute("aYaw", new THREE.BufferAttribute(yaws, 3));
|
||||
geometry.computeVertexNormals();
|
||||
geometry.computeBoundingSphere();
|
||||
|
||||
|
||||
@@ -65,6 +65,10 @@ function createTerrainGrassSampler(
|
||||
const terrainMatrix = createTerrainMatrix(position, rotation, scale);
|
||||
const inverseTerrainMatrix = terrainMatrix.clone().invert();
|
||||
const normalMatrix = new THREE.Matrix3().getNormalMatrix(terrainMatrix);
|
||||
const localOrigin = new THREE.Vector3();
|
||||
const localDirection = DOWN.clone().transformDirection(inverseTerrainMatrix);
|
||||
const fallbackNormal = new THREE.Vector3(0, 1, 0);
|
||||
const hits: THREE.Intersection[] = [];
|
||||
const raycaster = new THREE.Raycaster(
|
||||
new THREE.Vector3(),
|
||||
DOWN,
|
||||
@@ -94,14 +98,11 @@ function createTerrainGrassSampler(
|
||||
};
|
||||
|
||||
const sample = (x: number, z: number): TerrainGrassSample | null => {
|
||||
const localOrigin = new THREE.Vector3(x, RAYCAST_Y, z).applyMatrix4(
|
||||
inverseTerrainMatrix,
|
||||
);
|
||||
const localDirection =
|
||||
DOWN.clone().transformDirection(inverseTerrainMatrix);
|
||||
|
||||
localOrigin.set(x, RAYCAST_Y, z).applyMatrix4(inverseTerrainMatrix);
|
||||
raycaster.set(localOrigin, localDirection);
|
||||
const hit = raycaster.intersectObjects(meshes, false)[0];
|
||||
hits.length = 0;
|
||||
raycaster.intersectObjects(meshes, false, hits);
|
||||
const hit = hits[0];
|
||||
if (!hit) return null;
|
||||
|
||||
const normal = hit.face?.normal
|
||||
@@ -112,7 +113,7 @@ function createTerrainGrassSampler(
|
||||
|
||||
return {
|
||||
position: hit.point.clone().applyMatrix4(terrainMatrix),
|
||||
normal: normal ?? new THREE.Vector3(0, 1, 0),
|
||||
normal: normal ?? fallbackNormal.clone(),
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { EcoleModel } from "@/components/three/world/EcoleModel";
|
||||
import { FermeVerticaleModel } from "@/components/three/world/FermeVerticaleModel";
|
||||
import { GenerateurModel } from "@/components/three/world/GenerateurModel";
|
||||
import { LafabrikModel } from "@/components/three/world/LafabrikModel";
|
||||
import { LaFabrikMapModel } from "@/components/three/world/LaFabrikMapModel";
|
||||
import {
|
||||
normalizeMapScale,
|
||||
useTerrainSnappedPosition,
|
||||
@@ -55,7 +55,7 @@ export function GeneratedMapNodeInstance({
|
||||
|
||||
if (node.name === "lafabrik") {
|
||||
return (
|
||||
<LafabrikModel
|
||||
<LaFabrikMapModel
|
||||
position={position}
|
||||
rotation={node.rotation}
|
||||
scale={scale}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useEffect, useMemo, useRef } from "react";
|
||||
import * as THREE from "three";
|
||||
import { useGLTF } from "@react-three/drei";
|
||||
import { useThree } from "@react-three/fiber";
|
||||
import { mergeGeometries } from "three/addons/utils/BufferGeometryUtils.js";
|
||||
import { mergeBufferGeometries } from "three-stdlib";
|
||||
import {
|
||||
normalizeMapScale,
|
||||
useTerrainHeightSampler,
|
||||
@@ -112,7 +112,7 @@ function extractMeshes(scene: THREE.Group): MeshData[] {
|
||||
};
|
||||
}
|
||||
|
||||
const mergedGeometry = mergeGeometries(group.geometries, false);
|
||||
const mergedGeometry = mergeBufferGeometries(group.geometries, false);
|
||||
|
||||
for (const geometry of group.geometries) {
|
||||
geometry.dispose();
|
||||
|
||||
@@ -16,9 +16,10 @@ import {
|
||||
} from "@/data/world/mapInstancingConfig";
|
||||
import { useMapInstancingData } from "@/hooks/world/useMapInstancingData";
|
||||
import type { MapAssetInstance } from "@/types/map/mapScene";
|
||||
import { createWorldInstanceChunks } from "@/utils/world/chunkInstances";
|
||||
|
||||
interface MapInstancingSystemProps {
|
||||
onlyModelName?: string | null;
|
||||
onlyMapName?: string | null;
|
||||
streaming?: boolean;
|
||||
}
|
||||
|
||||
@@ -30,53 +31,24 @@ interface MapAssetChunk {
|
||||
instances: MapAssetInstance[];
|
||||
}
|
||||
|
||||
function getChunkKey(instance: MapAssetInstance): string {
|
||||
const [x, , z] = instance.position;
|
||||
const chunkX = Math.floor(x / CHUNK_CONFIG.chunkSize);
|
||||
const chunkZ = Math.floor(z / CHUNK_CONFIG.chunkSize);
|
||||
return `${chunkX}:${chunkZ}`;
|
||||
}
|
||||
|
||||
function createMapAssetChunks(
|
||||
type: MapInstancingAssetType,
|
||||
config: MapInstancingAssetConfig,
|
||||
instances: MapAssetInstance[],
|
||||
): MapAssetChunk[] {
|
||||
const chunks = new Map<string, MapAssetInstance[]>();
|
||||
|
||||
for (const instance of instances) {
|
||||
const key = getChunkKey(instance);
|
||||
const chunk = chunks.get(key);
|
||||
|
||||
if (chunk) {
|
||||
chunk.push(instance);
|
||||
} else {
|
||||
chunks.set(key, [instance]);
|
||||
}
|
||||
}
|
||||
|
||||
return [...chunks.entries()].map(([chunkKey, chunkInstances]) => {
|
||||
const center = chunkInstances.reduce(
|
||||
(sum, instance) => {
|
||||
sum.x += instance.position[0];
|
||||
sum.z += instance.position[2];
|
||||
return sum;
|
||||
},
|
||||
{ x: 0, z: 0 },
|
||||
);
|
||||
|
||||
return createWorldInstanceChunks(instances).map((chunk) => {
|
||||
return {
|
||||
key: `${type}:${chunkKey}`,
|
||||
key: `${type}:${chunk.chunkKey}`,
|
||||
config,
|
||||
centerX: center.x / chunkInstances.length,
|
||||
centerZ: center.z / chunkInstances.length,
|
||||
instances: chunkInstances,
|
||||
centerX: chunk.centerX,
|
||||
centerZ: chunk.centerZ,
|
||||
instances: chunk.instances,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export function MapInstancingSystem({
|
||||
onlyModelName = null,
|
||||
onlyMapName = null,
|
||||
streaming = true,
|
||||
}: MapInstancingSystemProps): React.JSX.Element | null {
|
||||
const cameraMode = useCameraMode();
|
||||
@@ -96,7 +68,7 @@ export function MapInstancingSystem({
|
||||
return MAP_INSTANCING_ASSET_TYPES.flatMap((type) => {
|
||||
const config = MAP_INSTANCING_ASSETS[type];
|
||||
|
||||
if (onlyModelName && config.mapName !== onlyModelName) return [];
|
||||
if (onlyMapName && config.mapName !== onlyMapName) return [];
|
||||
|
||||
if (
|
||||
!config.enabled ||
|
||||
@@ -110,7 +82,7 @@ export function MapInstancingSystem({
|
||||
|
||||
return createMapAssetChunks(type, config, instances);
|
||||
});
|
||||
}, [data, groups, models, onlyModelName]);
|
||||
}, [data, groups, models, onlyMapName]);
|
||||
|
||||
const visibleChunks = useVisibleWorldChunks(chunks, streamingEnabled);
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useLayoutEffect } from "react";
|
||||
import { useThree } from "@react-three/fiber";
|
||||
import type { Octree } from "three/addons/math/Octree.js";
|
||||
import type { Octree } from "three-stdlib";
|
||||
import type { Vector3Tuple } from "@/types/three/three";
|
||||
import { PlayerCamera } from "@/world/player/PlayerCamera";
|
||||
import { PlayerController } from "@/world/player/PlayerController";
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { useEffect, useLayoutEffect, useRef } from "react";
|
||||
import { useFrame, useThree } from "@react-three/fiber";
|
||||
import * as THREE from "three";
|
||||
import { Capsule } from "three/addons/math/Capsule.js";
|
||||
import type { Octree } from "three/addons/math/Octree.js";
|
||||
import { Capsule, type Octree } from "three-stdlib";
|
||||
import {
|
||||
INTERACT_KEY,
|
||||
JUMP_KEY,
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useEffect, useMemo, useRef } from "react";
|
||||
import * as THREE from "three";
|
||||
import { useGLTF } from "@react-three/drei";
|
||||
import { useFrame, useThree } from "@react-three/fiber";
|
||||
import { mergeGeometries } from "three/addons/utils/BufferGeometryUtils.js";
|
||||
import { mergeBufferGeometries } from "three-stdlib";
|
||||
import { useTerrainHeightSampler } from "@/hooks/three/useTerrainHeight";
|
||||
import type { VegetationInstance } from "@/types/map/mapScene";
|
||||
import { useWind } from "@/hooks/world/useWind";
|
||||
@@ -161,7 +161,7 @@ function extractMeshes(scene: THREE.Group): MeshData[] {
|
||||
|
||||
return [...meshesByMaterial.values()]
|
||||
.map(({ geometries, material }) => {
|
||||
const mergedGeometry = mergeGeometries(geometries, false);
|
||||
const mergedGeometry = mergeBufferGeometries(geometries, false);
|
||||
|
||||
for (const geometry of geometries) {
|
||||
if (geometry !== mergedGeometry) {
|
||||
|
||||
@@ -15,9 +15,10 @@ import {
|
||||
VEGETATION_TYPES,
|
||||
type VegetationType,
|
||||
} from "@/data/world/vegetationConfig";
|
||||
import { createWorldInstanceChunks } from "@/utils/world/chunkInstances";
|
||||
|
||||
interface VegetationSystemProps {
|
||||
onlyModelName?: string | null;
|
||||
onlyMapName?: string | null;
|
||||
streaming?: boolean;
|
||||
}
|
||||
|
||||
@@ -35,42 +36,15 @@ interface VegetationChunk {
|
||||
instances: VegetationInstance[];
|
||||
}
|
||||
|
||||
function getChunkKey(instance: VegetationInstance): string {
|
||||
const [x, , z] = instance.position;
|
||||
const chunkX = Math.floor(x / CHUNK_CONFIG.chunkSize);
|
||||
const chunkZ = Math.floor(z / CHUNK_CONFIG.chunkSize);
|
||||
return `${chunkX}:${chunkZ}`;
|
||||
}
|
||||
|
||||
function createVegetationChunks(
|
||||
type: VegetationType,
|
||||
instances: VegetationInstance[],
|
||||
): VegetationChunk[] {
|
||||
const config = VEGETATION_TYPES[type];
|
||||
const chunks = new Map<string, VegetationInstance[]>();
|
||||
|
||||
for (const instance of instances) {
|
||||
const key = getChunkKey(instance);
|
||||
const chunk = chunks.get(key);
|
||||
if (chunk) {
|
||||
chunk.push(instance);
|
||||
} else {
|
||||
chunks.set(key, [instance]);
|
||||
}
|
||||
}
|
||||
|
||||
return [...chunks.entries()].map(([chunkKey, chunkInstances]) => {
|
||||
const center = chunkInstances.reduce(
|
||||
(sum, instance) => {
|
||||
sum.x += instance.position[0];
|
||||
sum.z += instance.position[2];
|
||||
return sum;
|
||||
},
|
||||
{ x: 0, z: 0 },
|
||||
);
|
||||
|
||||
return createWorldInstanceChunks(instances).map((chunk) => {
|
||||
return {
|
||||
key: `${type}:${chunkKey}`,
|
||||
key: `${type}:${chunk.chunkKey}`,
|
||||
type,
|
||||
modelPath: config.modelPath,
|
||||
scaleMultiplier: config.scaleMultiplier,
|
||||
@@ -78,15 +52,15 @@ function createVegetationChunks(
|
||||
receiveShadow: config.receiveShadow,
|
||||
windStrength: config.windStrength,
|
||||
rotationOffset: config.rotationOffset,
|
||||
centerX: center.x / chunkInstances.length,
|
||||
centerZ: center.z / chunkInstances.length,
|
||||
instances: chunkInstances,
|
||||
centerX: chunk.centerX,
|
||||
centerZ: chunk.centerZ,
|
||||
instances: chunk.instances,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export function VegetationSystem({
|
||||
onlyModelName = null,
|
||||
onlyMapName = null,
|
||||
streaming = true,
|
||||
}: VegetationSystemProps): React.JSX.Element | null {
|
||||
const cameraMode = useCameraMode();
|
||||
@@ -106,7 +80,7 @@ export function VegetationSystem({
|
||||
return VEGETATION_TYPE_KEYS.flatMap((type) => {
|
||||
const config = VEGETATION_TYPES[type];
|
||||
|
||||
if (onlyModelName && config.mapName !== onlyModelName) return [];
|
||||
if (onlyMapName && config.mapName !== onlyMapName) return [];
|
||||
|
||||
if (!config.enabled) return [];
|
||||
if (!isMapModelVisible(config.mapName, { groups, models })) return [];
|
||||
@@ -116,7 +90,7 @@ export function VegetationSystem({
|
||||
|
||||
return createVegetationChunks(type, entry.instances);
|
||||
});
|
||||
}, [data, groups, models, onlyModelName]);
|
||||
}, [data, groups, models, onlyMapName]);
|
||||
|
||||
const visibleChunks = useVisibleWorldChunks(chunks, streamingEnabled);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user