From f7b4a07e41e5bc3cfc4420d9731c1154eac31d87 Mon Sep 17 00:00:00 2001 From: Tom Boullay Date: Fri, 29 May 2026 02:00:35 +0200 Subject: [PATCH] fix: bug on textute vegetation item --- .../three/world/MergedStaticMapModel.tsx | 4 +- src/data/world/grassConfig.ts | 2 +- .../map-instancing/InstancedMapAsset.tsx | 93 ++++--------------- src/world/vegetation/InstancedVegetation.tsx | 88 +++++++++--------- 4 files changed, 66 insertions(+), 121 deletions(-) diff --git a/src/components/three/world/MergedStaticMapModel.tsx b/src/components/three/world/MergedStaticMapModel.tsx index c8ae920..67b7d18 100644 --- a/src/components/three/world/MergedStaticMapModel.tsx +++ b/src/components/three/world/MergedStaticMapModel.tsx @@ -2,7 +2,7 @@ import { useEffect, useRef } from "react"; import { useGLTF } from "@react-three/drei"; import { useThree } from "@react-three/fiber"; 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 { 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) { sourceGeometry.dispose(); diff --git a/src/data/world/grassConfig.ts b/src/data/world/grassConfig.ts index 599e09d..f99b88b 100644 --- a/src/data/world/grassConfig.ts +++ b/src/data/world/grassConfig.ts @@ -3,7 +3,7 @@ export const GRASS_CONFIG = { patchSize: 30, bladeCount: 32000, bladeWidth: 0.08, - maxBladeHeight: 0.56, + maxBladeHeight: 0.67, randomHeightAmount: 0.25, surfaceOffset: 0.025, heightTextureSize: 128, diff --git a/src/world/map-instancing/InstancedMapAsset.tsx b/src/world/map-instancing/InstancedMapAsset.tsx index 63e370d..4649f0a 100644 --- a/src/world/map-instancing/InstancedMapAsset.tsx +++ b/src/world/map-instancing/InstancedMapAsset.tsx @@ -2,7 +2,6 @@ import { useEffect, useMemo, useRef } from "react"; import * as THREE from "three"; import { useGLTF } from "@react-three/drei"; import { useThree } from "@react-three/fiber"; -import { mergeBufferGeometries } from "three-stdlib"; import { normalizeMapScale, useTerrainHeightSampler, @@ -23,11 +22,6 @@ interface MeshData { material: THREE.Material | THREE.Material[]; } -interface MeshMergeGroup { - geometries: THREE.BufferGeometry[]; - material: THREE.Material | THREE.Material[]; -} - const meshDataCache = new Map(); function cloneMaterial( @@ -38,46 +32,29 @@ function cloneMaterial( : 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 { mesh.dispose(); } -function createGeometrySignature(geometry: THREE.BufferGeometry): string { - const attributes = Object.entries(geometry.attributes) - .map(([name, attribute]) => { - return `${name}:${attribute.itemSize}:${attribute.normalized}`; - }) - .sort() - .join("|"); +function hasFinitePositions(geometry: THREE.BufferGeometry): boolean { + const position = geometry.getAttribute("position"); + if (!position) return false; - return `${geometry.index ? "indexed" : "non-indexed"}:${attributes}`; -} - -function createMaterialKey( - material: THREE.Material | THREE.Material[], -): string { - if (Array.isArray(material)) { - return material.map((item) => item.uuid).join("|"); + 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 material.uuid; + return true; } function extractMeshes(scene: THREE.Group): MeshData[] { - const groups = new Map(); + const meshes: MeshData[] = []; scene.updateMatrixWorld(true); scene.traverse((child) => { @@ -85,50 +62,18 @@ function extractMeshes(scene: THREE.Group): MeshData[] { const geometry = child.geometry.clone(); geometry.applyMatrix4(child.matrixWorld); - const material = child.material; - const key = `${createMaterialKey(material)}:${createGeometrySignature(geometry)}`; - const group = groups.get(key); - - if (group) { - group.geometries.push(geometry); + if (!hasFinitePositions(geometry)) { + geometry.dispose(); return; } - groups.set(key, { - geometries: [geometry], - material: cloneMaterial(material), + meshes.push({ + geometry, + material: cloneMaterial(child.material), }); }); - return [...groups.values()] - .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); + return meshes; } function setInstanceMatrices( diff --git a/src/world/vegetation/InstancedVegetation.tsx b/src/world/vegetation/InstancedVegetation.tsx index 91034eb..c5c76a0 100644 --- a/src/world/vegetation/InstancedVegetation.tsx +++ b/src/world/vegetation/InstancedVegetation.tsx @@ -2,7 +2,6 @@ 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 { mergeBufferGeometries } from "three-stdlib"; import { useTerrainHeightSampler } from "@/hooks/three/useTerrainHeight"; import type { VegetationInstance } from "@/types/map/mapScene"; import { useWind } from "@/hooks/world/useWind"; @@ -38,6 +37,7 @@ interface VegetationWindUniforms { } const meshDataCache = new Map(); +const VEGETATION_ALPHA_TEST = 0.35; function updateVegetationWindUniforms( uniforms: VegetationWindUniforms, @@ -90,6 +90,15 @@ function applyVegetationWindMaterial( }; 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) => { shader.uniforms.uVegetationWindTime = windUniforms.time; @@ -130,11 +139,25 @@ function applyVegetationWindMaterial( 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[] { - const meshesByMaterial = new Map< - string, - { geometries: THREE.BufferGeometry[]; material: THREE.Material } - >(); + const meshes: MeshData[] = []; scene.updateMatrixWorld(true); scene.traverse((child) => { @@ -147,41 +170,19 @@ function extractMeshes(scene: THREE.Group): MeshData[] { const geometry = child.geometry.clone(); geometry.applyMatrix4(child.matrixWorld); - - const existing = meshesByMaterial.get(material.uuid); - if (existing) { - existing.geometries.push(geometry); - } else { - meshesByMaterial.set(material.uuid, { - geometries: [geometry], - material: material.clone(), - }); + if (!hasFinitePositions(geometry)) { + geometry.dispose(); + return; } + addWindWeightAttribute(geometry); + + meshes.push({ + geometry, + material: applyVegetationWindMaterial(material.clone()), + }); }); - return [...meshesByMaterial.values()] - .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); + return meshes; } function createInstanceMatrices( @@ -194,18 +195,17 @@ function createInstanceMatrices( const position = new THREE.Vector3(); const rotation = new THREE.Euler(); const quaternion = new THREE.Quaternion(); - const scale = new THREE.Vector3(); + const scale = new THREE.Vector3( + scaleMultiplier, + scaleMultiplier, + scaleMultiplier, + ); for (const instance of instances) { const matrix = new THREE.Matrix4(); position.set(...instance.position); - scale.set( - instance.scale[0] * scaleMultiplier, - instance.scale[1] * scaleMultiplier, - instance.scale[2] * scaleMultiplier, - ); - position.y += -geometryBottomY * scale.y; + position.y += -geometryBottomY * scaleMultiplier; rotation.set( instance.rotation[0] + rotationOffset[0], instance.rotation[1] + rotationOffset[1],