fix: bug on textute vegetation item
🔍 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-05-29 02:00:35 +02:00
parent 89044a18ec
commit f7b4a07e41
4 changed files with 66 additions and 121 deletions
@@ -2,7 +2,7 @@ import { useEffect, useRef } from "react";
import { useGLTF } from "@react-three/drei"; import { useGLTF } from "@react-three/drei";
import { useThree } from "@react-three/fiber"; import { useThree } from "@react-three/fiber";
import * as THREE from "three"; import * as THREE from "three";
import { mergeBufferGeometries } from "three-stdlib"; import { mergeGeometries } from "three/addons/utils/BufferGeometryUtils.js";
import type { Vector3Tuple } from "@/types/three/three"; import type { Vector3Tuple } from "@/types/three/three";
import { optimizeGLTFSceneTextures } from "@/utils/three/optimizeGLTFScene"; import { optimizeGLTFSceneTextures } from "@/utils/three/optimizeGLTFScene";
@@ -102,7 +102,7 @@ function createMergedMeshes(scene: THREE.Group): MergedMeshData[] {
}; };
} }
const geometry = mergeBufferGeometries(group.geometries, false); const geometry = mergeGeometries(group.geometries, false);
for (const sourceGeometry of group.geometries) { for (const sourceGeometry of group.geometries) {
sourceGeometry.dispose(); sourceGeometry.dispose();
+1 -1
View File
@@ -3,7 +3,7 @@ export const GRASS_CONFIG = {
patchSize: 30, patchSize: 30,
bladeCount: 32000, bladeCount: 32000,
bladeWidth: 0.08, bladeWidth: 0.08,
maxBladeHeight: 0.56, maxBladeHeight: 0.67,
randomHeightAmount: 0.25, randomHeightAmount: 0.25,
surfaceOffset: 0.025, surfaceOffset: 0.025,
heightTextureSize: 128, heightTextureSize: 128,
+19 -74
View File
@@ -2,7 +2,6 @@ import { useEffect, useMemo, useRef } from "react";
import * as THREE from "three"; import * as THREE from "three";
import { useGLTF } from "@react-three/drei"; import { useGLTF } from "@react-three/drei";
import { useThree } from "@react-three/fiber"; import { useThree } from "@react-three/fiber";
import { mergeBufferGeometries } from "three-stdlib";
import { import {
normalizeMapScale, normalizeMapScale,
useTerrainHeightSampler, useTerrainHeightSampler,
@@ -23,11 +22,6 @@ interface MeshData {
material: THREE.Material | THREE.Material[]; material: THREE.Material | THREE.Material[];
} }
interface MeshMergeGroup {
geometries: THREE.BufferGeometry[];
material: THREE.Material | THREE.Material[];
}
const meshDataCache = new Map<string, MeshData[]>(); const meshDataCache = new Map<string, MeshData[]>();
function cloneMaterial( function cloneMaterial(
@@ -38,46 +32,29 @@ function cloneMaterial(
: material.clone(); : material.clone();
} }
function disposeMaterialOnly(
material: THREE.Material | THREE.Material[],
): void {
if (Array.isArray(material)) {
for (const item of material) {
item.dispose();
}
return;
}
material.dispose();
}
function disposeInstancedMapMesh(mesh: THREE.InstancedMesh): void { function disposeInstancedMapMesh(mesh: THREE.InstancedMesh): void {
mesh.dispose(); mesh.dispose();
} }
function createGeometrySignature(geometry: THREE.BufferGeometry): string { function hasFinitePositions(geometry: THREE.BufferGeometry): boolean {
const attributes = Object.entries(geometry.attributes) const position = geometry.getAttribute("position");
.map(([name, attribute]) => { if (!position) return false;
return `${name}:${attribute.itemSize}:${attribute.normalized}`;
})
.sort()
.join("|");
return `${geometry.index ? "indexed" : "non-indexed"}:${attributes}`; for (let index = 0; index < position.count; index++) {
} if (
!Number.isFinite(position.getX(index)) ||
function createMaterialKey( !Number.isFinite(position.getY(index)) ||
material: THREE.Material | THREE.Material[], !Number.isFinite(position.getZ(index))
): string { ) {
if (Array.isArray(material)) { return false;
return material.map((item) => item.uuid).join("|"); }
} }
return material.uuid; return true;
} }
function extractMeshes(scene: THREE.Group): MeshData[] { function extractMeshes(scene: THREE.Group): MeshData[] {
const groups = new Map<string, MeshMergeGroup>(); const meshes: MeshData[] = [];
scene.updateMatrixWorld(true); scene.updateMatrixWorld(true);
scene.traverse((child) => { scene.traverse((child) => {
@@ -85,50 +62,18 @@ function extractMeshes(scene: THREE.Group): MeshData[] {
const geometry = child.geometry.clone(); const geometry = child.geometry.clone();
geometry.applyMatrix4(child.matrixWorld); geometry.applyMatrix4(child.matrixWorld);
const material = child.material; if (!hasFinitePositions(geometry)) {
const key = `${createMaterialKey(material)}:${createGeometrySignature(geometry)}`; geometry.dispose();
const group = groups.get(key);
if (group) {
group.geometries.push(geometry);
return; return;
} }
groups.set(key, { meshes.push({
geometries: [geometry], geometry,
material: cloneMaterial(material), material: cloneMaterial(child.material),
}); });
}); });
return [...groups.values()] return meshes;
.map((group) => {
if (group.geometries.length === 1) {
const [geometry] = group.geometries;
if (!geometry) return null;
return {
geometry,
material: group.material,
};
}
const mergedGeometry = mergeBufferGeometries(group.geometries, false);
for (const geometry of group.geometries) {
geometry.dispose();
}
if (!mergedGeometry) {
disposeMaterialOnly(group.material);
return null;
}
return {
geometry: mergedGeometry,
material: group.material,
};
})
.filter((meshData): meshData is MeshData => meshData !== null);
} }
function setInstanceMatrices( function setInstanceMatrices(
+44 -44
View File
@@ -2,7 +2,6 @@ import { useEffect, useMemo, useRef } from "react";
import * as THREE from "three"; import * as THREE from "three";
import { useGLTF } from "@react-three/drei"; import { useGLTF } from "@react-three/drei";
import { useFrame, useThree } from "@react-three/fiber"; import { useFrame, useThree } from "@react-three/fiber";
import { mergeBufferGeometries } from "three-stdlib";
import { useTerrainHeightSampler } from "@/hooks/three/useTerrainHeight"; import { useTerrainHeightSampler } from "@/hooks/three/useTerrainHeight";
import type { VegetationInstance } from "@/types/map/mapScene"; import type { VegetationInstance } from "@/types/map/mapScene";
import { useWind } from "@/hooks/world/useWind"; import { useWind } from "@/hooks/world/useWind";
@@ -38,6 +37,7 @@ interface VegetationWindUniforms {
} }
const meshDataCache = new Map<string, MeshData[]>(); const meshDataCache = new Map<string, MeshData[]>();
const VEGETATION_ALPHA_TEST = 0.35;
function updateVegetationWindUniforms( function updateVegetationWindUniforms(
uniforms: VegetationWindUniforms, uniforms: VegetationWindUniforms,
@@ -90,6 +90,15 @@ function applyVegetationWindMaterial(
}; };
windMaterial.userData.windUniforms = windUniforms; windMaterial.userData.windUniforms = windUniforms;
windMaterial.alphaTest = Math.max(
windMaterial.alphaTest,
VEGETATION_ALPHA_TEST,
);
windMaterial.transparent = false;
windMaterial.depthTest = true;
windMaterial.depthWrite = true;
windMaterial.side = THREE.DoubleSide;
windMaterial.needsUpdate = true;
windMaterial.onBeforeCompile = (shader) => { windMaterial.onBeforeCompile = (shader) => {
shader.uniforms.uVegetationWindTime = windUniforms.time; shader.uniforms.uVegetationWindTime = windUniforms.time;
@@ -130,11 +139,25 @@ function applyVegetationWindMaterial(
return windMaterial; return windMaterial;
} }
function hasFinitePositions(geometry: THREE.BufferGeometry): boolean {
const position = geometry.getAttribute("position");
if (!position) return false;
for (let index = 0; index < position.count; index++) {
if (
!Number.isFinite(position.getX(index)) ||
!Number.isFinite(position.getY(index)) ||
!Number.isFinite(position.getZ(index))
) {
return false;
}
}
return true;
}
function extractMeshes(scene: THREE.Group): MeshData[] { function extractMeshes(scene: THREE.Group): MeshData[] {
const meshesByMaterial = new Map< const meshes: MeshData[] = [];
string,
{ geometries: THREE.BufferGeometry[]; material: THREE.Material }
>();
scene.updateMatrixWorld(true); scene.updateMatrixWorld(true);
scene.traverse((child) => { scene.traverse((child) => {
@@ -147,41 +170,19 @@ function extractMeshes(scene: THREE.Group): MeshData[] {
const geometry = child.geometry.clone(); const geometry = child.geometry.clone();
geometry.applyMatrix4(child.matrixWorld); geometry.applyMatrix4(child.matrixWorld);
if (!hasFinitePositions(geometry)) {
const existing = meshesByMaterial.get(material.uuid); geometry.dispose();
if (existing) { return;
existing.geometries.push(geometry);
} else {
meshesByMaterial.set(material.uuid, {
geometries: [geometry],
material: material.clone(),
});
} }
addWindWeightAttribute(geometry);
meshes.push({
geometry,
material: applyVegetationWindMaterial(material.clone()),
});
}); });
return [...meshesByMaterial.values()] return meshes;
.map(({ geometries, material }) => {
const mergedGeometry = mergeBufferGeometries(geometries, false);
for (const geometry of geometries) {
if (geometry !== mergedGeometry) {
geometry.dispose();
}
}
if (!mergedGeometry) {
material.dispose();
return null;
}
addWindWeightAttribute(mergedGeometry);
return {
geometry: mergedGeometry,
material: applyVegetationWindMaterial(material),
};
})
.filter((meshData): meshData is MeshData => meshData !== null);
} }
function createInstanceMatrices( function createInstanceMatrices(
@@ -194,18 +195,17 @@ function createInstanceMatrices(
const position = new THREE.Vector3(); const position = new THREE.Vector3();
const rotation = new THREE.Euler(); const rotation = new THREE.Euler();
const quaternion = new THREE.Quaternion(); const quaternion = new THREE.Quaternion();
const scale = new THREE.Vector3(); const scale = new THREE.Vector3(
scaleMultiplier,
scaleMultiplier,
scaleMultiplier,
);
for (const instance of instances) { for (const instance of instances) {
const matrix = new THREE.Matrix4(); const matrix = new THREE.Matrix4();
position.set(...instance.position); position.set(...instance.position);
scale.set( position.y += -geometryBottomY * scaleMultiplier;
instance.scale[0] * scaleMultiplier,
instance.scale[1] * scaleMultiplier,
instance.scale[2] * scaleMultiplier,
);
position.y += -geometryBottomY * scale.y;
rotation.set( rotation.set(
instance.rotation[0] + rotationOffset[0], instance.rotation[0] + rotationOffset[0],
instance.rotation[1] + rotationOffset[1], instance.rotation[1] + rotationOffset[1],