From f6928d0205339784e57e7b8de79de56ce693bd67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Piellard?= Date: Mon, 16 Dec 2024 17:27:19 +0100 Subject: [PATCH 01/31] test: dedicated scene for boards --- src/test/main.ts | 10 +- src/test/test-board.ts | 253 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 260 insertions(+), 3 deletions(-) create mode 100644 src/test/test-board.ts diff --git a/src/test/main.ts b/src/test/main.ts index 750c3485..3bee7e36 100644 --- a/src/test/main.ts +++ b/src/test/main.ts @@ -1,7 +1,8 @@ import { ELogLevel, setVerbosity } from '../lib/index'; import { VoxelMap } from './map/voxel-map'; -import { type TestBase } from './test-base'; +import { TestBase } from './test-base'; +import { TestBoard } from './test-board'; import { TestParticles } from './test-particles'; import { TestPhysics } from './test-physics'; import { TestTerrain } from './test-terrain'; @@ -15,7 +16,7 @@ function createVoxelMap(): VoxelMap { const mapScaleXZ = 800; const mapScaleY = 200; const mapSeed = 'fixed_seed'; - const includeTreesInLod = false; + const includeTreesInLod = true; return new VoxelMap(mapScaleXZ, mapScaleY, mapSeed, includeTreesInLod); } @@ -27,9 +28,10 @@ enum ETest { TEXTURE_CUSTOMIZATION, PHYSICS, PARTICLES, + BOARD, } -const test = ETest.TERRAIN as ETest; +const test = ETest.BOARD as ETest; let testScene: TestBase; if (test === ETest.TERRAIN) { @@ -44,6 +46,8 @@ if (test === ETest.TERRAIN) { testScene = new TestPhysics(createVoxelMap()); } else if (test === ETest.PARTICLES) { testScene = new TestParticles(createVoxelMap()); +} else if (test === ETest.BOARD) { + testScene = new TestBoard(createVoxelMap()); } else { throw new Error(`Unknown test "${test}".`); } diff --git a/src/test/test-board.ts b/src/test/test-board.ts new file mode 100644 index 00000000..c8e12983 --- /dev/null +++ b/src/test/test-board.ts @@ -0,0 +1,253 @@ +import * as THREE from 'three-usage-test'; + +import { + BoardOverlaysHandler, + BoardRenderableFactory, + computeBoard, + EBoardSquareType, + EComputationMethod, + HeightmapViewer, + PromisesQueue, + TerrainViewer, + VoxelmapViewer, + VoxelmapVisibilityComputer, + VoxelmapWrapper, + type Board, + type BoardRenderable, + type IHeightmap, + type IVoxelMap +} from '../lib'; + +import { LineOfSight } from './board/line-of-sight'; +import { PathFinder } from './board/path-finder'; +import { TestBase } from './test-base'; +import { type ITerrainMap } from './test-terrain-base'; + +class TestBoard extends TestBase { + protected readonly terrainViewer: TerrainViewer; + + private readonly voxelmapViewer: VoxelmapViewer; + private readonly voxelmapVisibilityComputer: VoxelmapVisibilityComputer; + private readonly promisesQueue: PromisesQueue; + + private readonly map: VoxelmapWrapper; + + public constructor(map: IVoxelMap & IHeightmap & ITerrainMap) { + super(); + + this.camera.position.y = 150; + this.cameraControl.target.y = this.camera.position.y - 10; + + const dirLight = new THREE.DirectionalLight(0xffffff, 1); + dirLight.target.position.set(0, 0, 0); + dirLight.position.set(100, 50, 100); + this.scene.add(dirLight); + + const ambientLight = new THREE.AmbientLight(0xffffff); + this.scene.add(ambientLight); + + const chunkSize = { xz: 64, y: 64 }; + const minChunkIdY = Math.floor(map.minAltitude / chunkSize.y); + const maxChunkIdY = Math.floor(map.maxAltitude / chunkSize.y); + + this.voxelmapViewer = new VoxelmapViewer(minChunkIdY, maxChunkIdY, map.voxelMaterialsList, { + patchSize: chunkSize, + computationOptions: { + method: EComputationMethod.CPU_MULTITHREADED, + threadsCount: 4, + greedyMeshing: true, + }, + checkerboardType: 'xz', + voxelsChunkOrdering: 'zyx', + }); + this.voxelmapViewer.parameters.faces.checkerboardContrast = 0.01; + + const heightmapViewer = new HeightmapViewer(map, { + basePatchSize: chunkSize.xz, + maxLevel: 5, + voxelRatio: 2, + }); + + this.terrainViewer = new TerrainViewer(heightmapViewer, this.voxelmapViewer); + this.terrainViewer.parameters.lod.enabled = false; + this.scene.add(this.terrainViewer.container); + + this.voxelmapVisibilityComputer = new VoxelmapVisibilityComputer( + this.voxelmapViewer.patchSize, + this.voxelmapViewer.minChunkIdY, + this.voxelmapViewer.maxChunkIdY + ); + this.voxelmapVisibilityComputer.showMapAroundPosition({ x: 0, y: 0, z: 0 }, 200); + + this.setupBoard(map); + + this.map = new VoxelmapWrapper(map, chunkSize, minChunkIdY, maxChunkIdY, true); + this.map.onChange.push(modifiedPatchesIdsList => { + if (modifiedPatchesIdsList.length > 0) { + this.promisesQueue.cancelAll(); + for (const patchId of modifiedPatchesIdsList) { + this.voxelmapViewer.invalidatePatch(patchId); + } + } + }); + this.promisesQueue = new PromisesQueue(this.voxelmapViewer.maxPatchesComputedInParallel + 5); + + this.applyVisibility(); + setInterval(() => this.applyVisibility(), 200); + } + + protected override update(): void { + this.terrainViewer.update(); + } + + private applyVisibility(): void { + const patchesToDisplay = this.voxelmapVisibilityComputer.getRequestedPatches(); + const patchesIdToDisplay = patchesToDisplay.map(patchToDisplay => patchToDisplay.id); + + this.voxelmapViewer.setVisibility(patchesIdToDisplay); + + this.promisesQueue.cancelAll(); + for (const patchId of patchesIdToDisplay) { + if (this.voxelmapViewer.doesPatchRequireVoxelsData(patchId)) { + this.promisesQueue.run( + async () => { + if (this.voxelmapViewer.doesPatchRequireVoxelsData(patchId)) { + const voxelsChunkBox = this.voxelmapViewer.getPatchVoxelsBox(patchId); + const blockStart = voxelsChunkBox.min; + const blockEnd = voxelsChunkBox.max; + + const patchMapData = await this.map.getLocalMapData(blockStart, blockEnd); + const voxelsChunkData = Object.assign(patchMapData, { + size: new THREE.Vector3().subVectors(blockEnd, blockStart), + }); + // const computationStatus = + await this.voxelmapViewer.enqueuePatch(patchId, voxelsChunkData); + // console.log(`${patchId.asString} computation status: ${computationStatus}`); + } + }, + () => { + this.voxelmapViewer.dequeuePatch(patchId); + // console.log(`${patchId.asString} query & computation cancelled`); + } + ); + } + } + } + + private setupBoard(voxelMap: IVoxelMap & ITerrainMap): void { + const factory = new BoardRenderableFactory({ + voxelMaterialsList: voxelMap.voxelMaterialsList, + }); + + const testLineOfSight = false; + const testPathFinding = true; + + const boardContainer = new THREE.Group(); + this.scene.add(boardContainer); + let currentBoard: { + board: Board; + renderable: BoardRenderable; + } | null = null; + + const boardOverlaysHandler = new BoardOverlaysHandler({ board: { size: { x: 1, z: 1 }, origin: { x: 0, y: 0, z: 0 } } }); + + let lastBoardRequestId = -1; + const requestBoard = async (origin: THREE.Vector3Like) => { + lastBoardRequestId++; + const requestId = lastBoardRequestId; + + const boardRadius = 31; + const board = await computeBoard(voxelMap, origin, boardRadius); + const renderable = await factory.buildBoardRenderable(board); + boardOverlaysHandler.reset(board); + + if (lastBoardRequestId !== requestId) { + return; // another request was launched in the meantime + } + + boardContainer.clear(); + if (currentBoard) { + currentBoard.renderable.dispose(); + this.map.unregisterBoard(currentBoard.board); + boardOverlaysHandler.container.removeFromParent(); + } + currentBoard = { renderable, board }; + + if (!this.map.includeBoard) { + boardContainer.add(currentBoard.renderable.container); + } + this.map.registerBoard(currentBoard.board); + this.scene.add(boardOverlaysHandler.container); + + boardOverlaysHandler.clearSquares(); + + if (testLineOfSight) { + const lineOfSight = new LineOfSight({ + grid: { + size: board.size, + cells: board.squares.map(square => square.type === EBoardSquareType.OBSTACLE), + }, + }); + const gridVisibility = lineOfSight.computeCellsVisibility({ x: boardRadius, z: boardRadius }, 10); + const cellsVisibilities = gridVisibility.cells.filter(cell => { + return board.squares[cell.x + cell.z * board.size.x]!.type === EBoardSquareType.FLAT; + }); + const visibleSquares = cellsVisibilities.filter(cell => cell.visibility === 'visible'); + const obstructedSquares = cellsVisibilities.filter(cell => cell.visibility === 'hidden'); + boardOverlaysHandler.displaySquares(visibleSquares, new THREE.Color(0x00ff00)); + boardOverlaysHandler.displaySquares(obstructedSquares, new THREE.Color(0xff0000)); + } else if (testPathFinding) { + const pathFinder = new PathFinder({ + grid: { + size: board.size, + cells: board.squares.map(square => square.type === EBoardSquareType.FLAT), + }, + }); + + { + pathFinder.setOrigin({ x: boardRadius, z: boardRadius }); + const reachableCells = pathFinder.getReachableCells(10); + boardOverlaysHandler.displayBlob(0, reachableCells, new THREE.Color(0x88dd88), 0.5); + const path = pathFinder.findPathTo({ x: 31, z: 35 }); + if (path) { + boardOverlaysHandler.displaySquares(path, new THREE.Color(0x88dd88), 1); + } + } + + { + pathFinder.setOrigin({ x: boardRadius - 5, z: boardRadius - 5 }); + const reachableCells = pathFinder.getReachableCells(7); + boardOverlaysHandler.displayBlob(1, reachableCells, new THREE.Color(0xdd8888), 0.5); + } + } + }; + + const boardCenterContainer = new THREE.Group(); + const boardCenter = new THREE.Mesh(new THREE.BoxGeometry(1, 1, 1), new THREE.MeshPhongMaterial({ color: 0xffffff })); + boardCenter.position.set(0.5, 0.5, 0.5); + boardCenterContainer.add(boardCenter); + + const updateAltitude = () => { + const terrainSample = voxelMap.sampleHeightmapBaseTerrain( + Math.floor(boardCenterContainer.position.x), + Math.floor(boardCenterContainer.position.z) + ); + boardCenterContainer.position.setY(terrainSample.altitude + 1); + requestBoard(boardCenterContainer.position.clone()); + }; + updateAltitude(); + + const boardCenterControls = new THREE.TransformControls(this.camera, this.renderer.domElement); + boardCenterControls.showY = false; + boardCenterControls.addEventListener('dragging-changed', event => { + this.cameraControl.enabled = !event.value; + }); + boardCenterControls.addEventListener('change', updateAltitude); + boardCenterControls.attach(boardCenterContainer); + + this.scene.add(boardCenterContainer); + this.scene.add(boardCenterControls.getHelper()); + } +} + +export { TestBoard }; From 16273bfc011cb71f89004d621af5724fbd197e70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Piellard?= Date: Mon, 16 Dec 2024 18:22:48 +0100 Subject: [PATCH 02/31] test: light up board cells when clicked --- .../board/overlay/board-overlays-handler.ts | 43 +++++++++++++++++++ src/test/libs/three-usage-test.ts | 1 + src/test/main.ts | 2 +- src/test/test-board.ts | 15 ++++++- 4 files changed, 59 insertions(+), 2 deletions(-) diff --git a/src/lib/terrain/voxelmap/board/overlay/board-overlays-handler.ts b/src/lib/terrain/voxelmap/board/overlay/board-overlays-handler.ts index fd4820ea..2b06bcd8 100644 --- a/src/lib/terrain/voxelmap/board/overlay/board-overlays-handler.ts +++ b/src/lib/terrain/voxelmap/board/overlay/board-overlays-handler.ts @@ -13,10 +13,17 @@ type Parameters = { readonly board: InputBoard; }; +type RayIntersection = { + distance: number; + point: THREE.Vector3; + cellId: GridCoord; +}; + class BoardOverlaysHandler { private static readonly yShift = 0.001; public readonly container: THREE.Object3D; + private boardProperties: InputBoard; private overlaySquares: BoardOverlaySquares; private overlayBlob: BoardOverlayBlob; @@ -31,6 +38,11 @@ class BoardOverlaysHandler { this.container.add(this.overlaySquares.container); this.container.add(this.overlayBlob.container); this.container.position.set(params.board.origin.x, params.board.origin.y, params.board.origin.z); + + this.boardProperties = { + size: { ...params.board.size }, + origin: new THREE.Vector3().copy(params.board.origin), + }; } public clearSquares(): void { @@ -60,6 +72,32 @@ class BoardOverlaysHandler { } } + public rayIntersection(ray: THREE.Ray): RayIntersection | null { + if (ray.direction.y === 0) { + return null; + } + const delta = (this.boardProperties.origin.y - ray.origin.y) / ray.direction.y; + if (delta <= 0) { + return null; + } + + const toIntersection = ray.direction.clone().multiplyScalar(delta); + const intersectionPoint = new THREE.Vector3().addVectors(ray.origin, toIntersection); + const cellId = { + x: Math.floor(intersectionPoint.x) - this.boardProperties.origin.x, + z: Math.floor(intersectionPoint.z) - this.boardProperties.origin.z, + }; + if (cellId.x < 0 || cellId.z < 0 || cellId.x >= this.boardProperties.size.x || cellId.z >= this.boardProperties.size.z) { + return null; + } + + return { + distance: toIntersection.length(), + point: intersectionPoint, + cellId, + }; + } + public dispose(): void { this.overlayBlob.dispose(); this.overlaySquares.dispose(); @@ -76,6 +114,11 @@ class BoardOverlaysHandler { this.container.add(this.overlaySquares.container); this.container.add(this.overlayBlob.container); this.container.position.set(board.origin.x, board.origin.y, board.origin.z); + + this.boardProperties = { + size: { ...board.size }, + origin: new THREE.Vector3().copy(board.origin), + }; } private buildOverlays(gridSize: InputBoard['size']): { squares: BoardOverlaySquares; blob: BoardOverlayBlob } { diff --git a/src/test/libs/three-usage-test.ts b/src/test/libs/three-usage-test.ts index e4b1fd1b..2180961a 100644 --- a/src/test/libs/three-usage-test.ts +++ b/src/test/libs/three-usage-test.ts @@ -13,6 +13,7 @@ export { PCFSoftShadowMap, PerspectiveCamera, Ray, + Raycaster, SRGBColorSpace, Scene, SphereGeometry, diff --git a/src/test/main.ts b/src/test/main.ts index 3bee7e36..d7fb4914 100644 --- a/src/test/main.ts +++ b/src/test/main.ts @@ -1,7 +1,7 @@ import { ELogLevel, setVerbosity } from '../lib/index'; import { VoxelMap } from './map/voxel-map'; -import { TestBase } from './test-base'; +import { type TestBase } from './test-base'; import { TestBoard } from './test-board'; import { TestParticles } from './test-particles'; import { TestPhysics } from './test-physics'; diff --git a/src/test/test-board.ts b/src/test/test-board.ts index c8e12983..abfe7c20 100644 --- a/src/test/test-board.ts +++ b/src/test/test-board.ts @@ -15,7 +15,7 @@ import { type Board, type BoardRenderable, type IHeightmap, - type IVoxelMap + type IVoxelMap, } from '../lib'; import { LineOfSight } from './board/line-of-sight'; @@ -245,6 +245,19 @@ class TestBoard extends TestBase { boardCenterControls.addEventListener('change', updateAltitude); boardCenterControls.attach(boardCenterContainer); + const rayCaster = new THREE.Raycaster(); + window.addEventListener('click', event => { + const mouse = new THREE.Vector2( + (event.clientX / this.renderer.domElement.clientWidth) * 2 - 1, + -(event.clientY / this.renderer.domElement.clientHeight) * 2 + 1 + ); + rayCaster.setFromCamera(mouse, this.camera); + const intersection = boardOverlaysHandler.rayIntersection(rayCaster.ray); + if (intersection) { + boardOverlaysHandler.displaySquares([intersection.cellId], new THREE.Color(0x7777ff)); + } + }); + this.scene.add(boardCenterContainer); this.scene.add(boardCenterControls.getHelper()); } From a35fb3b38d3aa6669b8da8a1657deeeab8662453 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Piellard?= Date: Mon, 16 Dec 2024 18:59:52 +0100 Subject: [PATCH 03/31] test: add GUI for board testing --- src/test/test-board.ts | 45 +++++++++++++++++++++++++++--------------- 1 file changed, 29 insertions(+), 16 deletions(-) diff --git a/src/test/test-board.ts b/src/test/test-board.ts index abfe7c20..461a6434 100644 --- a/src/test/test-board.ts +++ b/src/test/test-board.ts @@ -1,3 +1,4 @@ +import GUI from 'lil-gui'; import * as THREE from 'three-usage-test'; import { @@ -32,9 +33,13 @@ class TestBoard extends TestBase { private readonly map: VoxelmapWrapper; + private readonly gui: GUI; + public constructor(map: IVoxelMap & IHeightmap & ITerrainMap) { super(); + this.gui = new GUI(); + this.camera.position.y = 150; this.cameraControl.target.y = this.camera.position.y - 10; @@ -139,8 +144,11 @@ class TestBoard extends TestBase { voxelMaterialsList: voxelMap.voxelMaterialsList, }); - const testLineOfSight = false; - const testPathFinding = true; + const parameters = { + boardRadius: 31, + testLineOfSight: false, + testPathFinding: true, + }; const boardContainer = new THREE.Group(); this.scene.add(boardContainer); @@ -151,13 +159,19 @@ class TestBoard extends TestBase { const boardOverlaysHandler = new BoardOverlaysHandler({ board: { size: { x: 1, z: 1 }, origin: { x: 0, y: 0, z: 0 } } }); + const boardCenterContainer = new THREE.Group(); + const boardCenter = new THREE.Mesh(new THREE.BoxGeometry(1, 1, 1), new THREE.MeshPhongMaterial({ color: 0xffffff })); + boardCenter.position.set(0.5, 0.5, 0.5); + boardCenterContainer.add(boardCenter); + let lastBoardRequestId = -1; - const requestBoard = async (origin: THREE.Vector3Like) => { + const requestBoard = async () => { lastBoardRequestId++; const requestId = lastBoardRequestId; - const boardRadius = 31; - const board = await computeBoard(voxelMap, origin, boardRadius); + const origin = boardCenterContainer.position.clone(); + + const board = await computeBoard(voxelMap, origin, parameters.boardRadius); const renderable = await factory.buildBoardRenderable(board); boardOverlaysHandler.reset(board); @@ -181,14 +195,14 @@ class TestBoard extends TestBase { boardOverlaysHandler.clearSquares(); - if (testLineOfSight) { + if (parameters.testLineOfSight) { const lineOfSight = new LineOfSight({ grid: { size: board.size, cells: board.squares.map(square => square.type === EBoardSquareType.OBSTACLE), }, }); - const gridVisibility = lineOfSight.computeCellsVisibility({ x: boardRadius, z: boardRadius }, 10); + const gridVisibility = lineOfSight.computeCellsVisibility({ x: parameters.boardRadius, z: parameters.boardRadius }, 10); const cellsVisibilities = gridVisibility.cells.filter(cell => { return board.squares[cell.x + cell.z * board.size.x]!.type === EBoardSquareType.FLAT; }); @@ -196,7 +210,7 @@ class TestBoard extends TestBase { const obstructedSquares = cellsVisibilities.filter(cell => cell.visibility === 'hidden'); boardOverlaysHandler.displaySquares(visibleSquares, new THREE.Color(0x00ff00)); boardOverlaysHandler.displaySquares(obstructedSquares, new THREE.Color(0xff0000)); - } else if (testPathFinding) { + } else if (parameters.testPathFinding) { const pathFinder = new PathFinder({ grid: { size: board.size, @@ -205,7 +219,7 @@ class TestBoard extends TestBase { }); { - pathFinder.setOrigin({ x: boardRadius, z: boardRadius }); + pathFinder.setOrigin({ x: parameters.boardRadius, z: parameters.boardRadius }); const reachableCells = pathFinder.getReachableCells(10); boardOverlaysHandler.displayBlob(0, reachableCells, new THREE.Color(0x88dd88), 0.5); const path = pathFinder.findPathTo({ x: 31, z: 35 }); @@ -215,25 +229,20 @@ class TestBoard extends TestBase { } { - pathFinder.setOrigin({ x: boardRadius - 5, z: boardRadius - 5 }); + pathFinder.setOrigin({ x: parameters.boardRadius - 5, z: parameters.boardRadius - 5 }); const reachableCells = pathFinder.getReachableCells(7); boardOverlaysHandler.displayBlob(1, reachableCells, new THREE.Color(0xdd8888), 0.5); } } }; - const boardCenterContainer = new THREE.Group(); - const boardCenter = new THREE.Mesh(new THREE.BoxGeometry(1, 1, 1), new THREE.MeshPhongMaterial({ color: 0xffffff })); - boardCenter.position.set(0.5, 0.5, 0.5); - boardCenterContainer.add(boardCenter); - const updateAltitude = () => { const terrainSample = voxelMap.sampleHeightmapBaseTerrain( Math.floor(boardCenterContainer.position.x), Math.floor(boardCenterContainer.position.z) ); boardCenterContainer.position.setY(terrainSample.altitude + 1); - requestBoard(boardCenterContainer.position.clone()); + requestBoard(); }; updateAltitude(); @@ -260,6 +269,10 @@ class TestBoard extends TestBase { this.scene.add(boardCenterContainer); this.scene.add(boardCenterControls.getHelper()); + + this.gui.add(parameters, 'boardRadius', 30, 60, 1).name('board radius').onChange(requestBoard); + this.gui.add(parameters, 'testLineOfSight').name('line of sight').onChange(requestBoard); + this.gui.add(parameters, 'testPathFinding').name('path finding').onChange(requestBoard); } } From 7ce6fff691cbd2262391c20021b064af8867dbc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Piellard?= Date: Sun, 29 Dec 2024 19:45:34 +0100 Subject: [PATCH 04/31] refactor: extract method --- src/test/main.ts | 53 ++++++++++++++++++++++++------------------------ 1 file changed, 26 insertions(+), 27 deletions(-) diff --git a/src/test/main.ts b/src/test/main.ts index d7fb4914..e5bc7923 100644 --- a/src/test/main.ts +++ b/src/test/main.ts @@ -12,15 +12,6 @@ import { TestWeather } from './test-weather'; setVerbosity(ELogLevel.WARN); -function createVoxelMap(): VoxelMap { - const mapScaleXZ = 800; - const mapScaleY = 200; - const mapSeed = 'fixed_seed'; - const includeTreesInLod = true; - - return new VoxelMap(mapScaleXZ, mapScaleY, mapSeed, includeTreesInLod); -} - enum ETest { TERRAIN, TERRAIN_OLD, @@ -31,25 +22,33 @@ enum ETest { BOARD, } -const test = ETest.BOARD as ETest; +function createVoxelMap(): VoxelMap { + const mapScaleXZ = 800; + const mapScaleY = 200; + const mapSeed = 'fixed_seed'; + const includeTreesInLod = true; + return new VoxelMap(mapScaleXZ, mapScaleY, mapSeed, includeTreesInLod); +} -let testScene: TestBase; -if (test === ETest.TERRAIN) { - testScene = new TestTerrain(createVoxelMap()); -} else if (test === ETest.TERRAIN_OLD) { - testScene = new TestTerrainAutonomous(createVoxelMap()); -} else if (test === ETest.WEATHER) { - testScene = new TestWeather(); -} else if (test === ETest.TEXTURE_CUSTOMIZATION) { - testScene = new TestTextureCustomization(); -} else if (test === ETest.PHYSICS) { - testScene = new TestPhysics(createVoxelMap()); -} else if (test === ETest.PARTICLES) { - testScene = new TestParticles(createVoxelMap()); -} else if (test === ETest.BOARD) { - testScene = new TestBoard(createVoxelMap()); -} else { - throw new Error(`Unknown test "${test}".`); +function buildTestScene(test: ETest): TestBase { + if (test === ETest.TERRAIN) { + return new TestTerrain(createVoxelMap()); + } else if (test === ETest.TERRAIN_OLD) { + return new TestTerrainAutonomous(createVoxelMap()); + } else if (test === ETest.WEATHER) { + return new TestWeather(); + } else if (test === ETest.TEXTURE_CUSTOMIZATION) { + return new TestTextureCustomization(); + } else if (test === ETest.PHYSICS) { + return new TestPhysics(createVoxelMap()); + } else if (test === ETest.PARTICLES) { + return new TestParticles(createVoxelMap()); + } else if (test === ETest.BOARD) { + return new TestBoard(createVoxelMap()); + } else { + throw new Error(`Unknown test "${test}".`); + } } +const testScene = buildTestScene(ETest.BOARD); testScene.start(); From c0e376d2436335c909010076e4b630047ce0b15b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Piellard?= Date: Sun, 29 Dec 2024 21:41:11 +0100 Subject: [PATCH 05/31] refactor: factorize code --- src/lib/effects/billboard/billboard-shader.ts | 12 +----------- src/lib/helpers/string.ts | 12 +++++++++++- .../merged/voxels-renderable-factory.ts | 12 +----------- 3 files changed, 13 insertions(+), 23 deletions(-) diff --git a/src/lib/effects/billboard/billboard-shader.ts b/src/lib/effects/billboard/billboard-shader.ts index 612b4439..2764aebc 100644 --- a/src/lib/effects/billboard/billboard-shader.ts +++ b/src/lib/effects/billboard/billboard-shader.ts @@ -1,16 +1,6 @@ -import { vec3ToString } from '../../helpers/string'; +import { applyReplacements, vec3ToString } from '../../helpers/string'; import * as THREE from '../../libs/three-usage'; -function applyReplacements(source: string, replacements: Record): string { - let result = source; - - for (const [source, replacement] of Object.entries(replacements)) { - result = result.replace(source, replacement); - } - - return result; -} - type UniformType = 'sampler2D' | 'float' | 'vec2' | 'vec3' | 'vec4'; type UniformDefinition = THREE.IUniform & { readonly type: UniformType }; diff --git a/src/lib/helpers/string.ts b/src/lib/helpers/string.ts index 6895e266..404271d5 100644 --- a/src/lib/helpers/string.ts +++ b/src/lib/helpers/string.ts @@ -8,4 +8,14 @@ function vec3ToString(vec3: THREE.Vector3Like, separator: string = 'x'): string return `${vec3.x}${separator}${vec3.y}${separator}${vec3.z}`; } -export { vec2ToString, vec3ToString }; +function applyReplacements(source: string, replacements: Record): string { + let result = source; + + for (const [source, replacement] of Object.entries(replacements)) { + result = result.replace(source, replacement); + } + + return result; +} + +export { applyReplacements, vec2ToString, vec3ToString }; diff --git a/src/lib/terrain/voxelmap/voxelsRenderable/voxelsRenderableFactory/merged/voxels-renderable-factory.ts b/src/lib/terrain/voxelmap/voxelsRenderable/voxelsRenderableFactory/merged/voxels-renderable-factory.ts index 4a77650d..f677eaf9 100644 --- a/src/lib/terrain/voxelmap/voxelsRenderable/voxelsRenderableFactory/merged/voxels-renderable-factory.ts +++ b/src/lib/terrain/voxelmap/voxelsRenderable/voxelsRenderableFactory/merged/voxels-renderable-factory.ts @@ -1,4 +1,4 @@ -import { vec3ToString } from '../../../../../helpers/string'; +import { applyReplacements, vec3ToString } from '../../../../../helpers/string'; import * as THREE from '../../../../../libs/three-usage'; import { type IVoxelMaterial, type VoxelsChunkSize } from '../../../i-voxelmap'; import { EVoxelsDisplayMode, type VoxelsMaterial, type VoxelsMaterialUniforms, type VoxelsMaterials } from '../../voxels-material'; @@ -43,16 +43,6 @@ abstract class VoxelsRenderableFactory extends VoxelsRenderableFactoryBase { private readonly materialsTemplates: VoxelsMaterials; private buildThreeJsVoxelsMaterial(parameters: VoxelsMaterialParameters): VoxelsMaterial { - function applyReplacements(source: string, replacements: Record): string { - let result = source; - - for (const [source, replacement] of Object.entries(replacements)) { - result = result.replace(source, replacement); - } - - return result; - } - const cstVoxelAo = 'VOXELS_AO'; const cstVoxelNoise = 'VOXELS_NOISE'; const cstVoxelRounded = 'VOXELS_ROUNDED'; From 1ed5b6021cd0937d4b7a3148fb4af9a066d260bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Piellard?= Date: Mon, 30 Dec 2024 22:29:53 +0100 Subject: [PATCH 06/31] test: base scene for grass --- .eslintrc | 3 + src/lib/effects/grass/grass-patches.ts | 121 ++++++++++++++++++ src/lib/index.ts | 4 +- src/lib/libs/three-usage.ts | 3 + src/test/libs/three-usage-test.ts | 4 + src/test/main.ts | 14 ++- src/test/test-grass.ts | 166 +++++++++++++++++++++++++ test/resources/grass2.glb | Bin 0 -> 9056 bytes 8 files changed, 311 insertions(+), 4 deletions(-) create mode 100644 src/lib/effects/grass/grass-patches.ts create mode 100644 src/test/test-grass.ts create mode 100644 test/resources/grass2.glb diff --git a/.eslintrc b/.eslintrc index 6364f70f..804623d7 100644 --- a/.eslintrc +++ b/.eslintrc @@ -38,6 +38,9 @@ "no-undef": "off", "no-unused-vars": "off", "no-use-before-define": "off", // handled by TS + "no-void": ["error", { + "allowAsStatement": true + }], "@typescript-eslint/consistent-type-exports": [ "error", { diff --git a/src/lib/effects/grass/grass-patches.ts b/src/lib/effects/grass/grass-patches.ts new file mode 100644 index 00000000..de0f69d2 --- /dev/null +++ b/src/lib/effects/grass/grass-patches.ts @@ -0,0 +1,121 @@ +import { applyReplacements } from '../../helpers/string'; +import * as THREE from '../../libs/three-usage'; + +const params = { + minDissolve: 0, +}; + +type ClutterMaterial = { + readonly material: THREE.MeshPhongMaterial; + readonly uniforms: { + uDissolveThreshold: THREE.IUniform; + }; +}; + +function buildNoiseTexture(resolution: number): THREE.DataTexture { + const textureWidth = resolution; + const textureHeight = resolution; + const textureData = new Uint8Array(textureWidth * textureHeight); + + for (let i = 0; i < textureData.length; i++) { + textureData[i] = 250 * Math.random(); + } + + const texture = new THREE.DataTexture(textureData, textureWidth, textureHeight, THREE.RedFormat); + texture.needsUpdate = true; + return texture; +} + +function buildMaterial(): ClutterMaterial { + const phongMaterial = new THREE.MeshPhongMaterial({ color: 0xffffff }); + phongMaterial.customProgramCacheKey = () => `prop_phong_material`; + + const noiseTextureSize = 64; + const noiseTexture = buildNoiseTexture(noiseTextureSize); + noiseTexture.wrapS = THREE.RepeatWrapping; + noiseTexture.wrapT = THREE.RepeatWrapping; + noiseTexture.magFilter = THREE.LinearFilter; + const dissolveUniform: THREE.IUniform = { value: params.minDissolve }; + + const customUniforms = { + uNoiseTexture: { value: noiseTexture }, + uDissolveThreshold: dissolveUniform, + }; + + phongMaterial.onBeforeCompile = parameters => { + parameters.uniforms = { + ...parameters.uniforms, + ...customUniforms, + }; + + parameters.vertexShader = applyReplacements(parameters.vertexShader, { + 'void main() {': ` + + in float aDissolveRatio; + out float vDissolveRatio; + + void main() { + vDissolveRatio = aDissolveRatio; + `, + }); + + parameters.fragmentShader = applyReplacements(parameters.fragmentShader, { + 'void main() {': ` + uniform sampler2D uNoiseTexture; + uniform float uDissolveThreshold; + + in float vDissolveRatio; + + void main() { + float noise = texture(uNoiseTexture, gl_FragCoord.xy / ${noiseTextureSize.toFixed(1)}).r; + if (noise < max(vDissolveRatio,uDissolveThreshold)) { + discard; + } + `, + }); + }; + return { + material: phongMaterial, + uniforms: customUniforms, + }; +} + +type Paramerers = { + count: number; + bufferGeometry: THREE.BufferGeometry; +}; + +class GrassPatchesBatch { + public get object3D() { + return this.instancedMesh; + } + + public minDissolve: number = 0; + private readonly instancedMesh: THREE.InstancedMesh; + private readonly material: ClutterMaterial; + private readonly dissolveAttribute: THREE.InstancedBufferAttribute; + + public constructor(params: Paramerers) { + this.dissolveAttribute = new THREE.InstancedBufferAttribute(new Float32Array(params.count), 1); + params.bufferGeometry.setAttribute('aDissolveRatio', this.dissolveAttribute); + + this.material = buildMaterial(); + this.instancedMesh = new THREE.InstancedMesh(params.bufferGeometry, this.material.material, params.count); + this.instancedMesh.count = params.count; + } + + public update(): void { + this.material.uniforms.uDissolveThreshold.value = this.minDissolve; + } + + public setDissolve(index: number, dissolveRatio: number): void { + this.dissolveAttribute.array[index] = dissolveRatio; + this.dissolveAttribute.needsUpdate = true; + } + + public setPosition(index: number, position: THREE.Vector2Like): void { + this.instancedMesh.setMatrixAt(index, new THREE.Matrix4().makeTranslation(new THREE.Vector3(position.x, 0, position.y))); + } +} + +export { GrassPatchesBatch, params }; diff --git a/src/lib/index.ts b/src/lib/index.ts index 33875aed..48cb07ee 100644 --- a/src/lib/index.ts +++ b/src/lib/index.ts @@ -3,7 +3,7 @@ export { ELogLevel, setVerbosity } from './helpers/logger'; export { HeightmapViewer } from './terrain/heightmap/heightmap-viewer'; export type { IHeightmap, IHeightmapCoords, IHeightmapSample } from './terrain/heightmap/i-heightmap'; export { TerrainViewer } from './terrain/terrain-viewer'; -export { EBoardSquareType, computeBoard, type Board, type BoardSquare } from './terrain/voxelmap/board/board'; +export { computeBoard, EBoardSquareType, type Board, type BoardSquare } from './terrain/voxelmap/board/board'; export { BoardRenderableFactory, type BoardRenderable } from './terrain/voxelmap/board/board-renderable-factory'; export { BoardOverlaysHandler } from './terrain/voxelmap/board/overlay/board-overlays-handler'; export { VoxelmapWrapper } from './terrain/voxelmap/board/voxelmap-wrapper'; @@ -43,3 +43,5 @@ export { Snow } from './effects/weather/snow'; export { GpuInstancedBillboard } from './effects/weather/weather-particles-base'; export { CustomizableTexture } from './helpers/customizable-texture'; + +export { GrassPatchesBatch } from './effects/grass/grass-patches'; diff --git a/src/lib/libs/three-usage.ts b/src/lib/libs/three-usage.ts index 9a83c8ae..23142cf9 100644 --- a/src/lib/libs/three-usage.ts +++ b/src/lib/libs/three-usage.ts @@ -16,6 +16,7 @@ export { InstancedMesh, InterleavedBuffer, InterleavedBufferAttribute, + LinearFilter, Matrix4, Mesh, MeshBasicMaterial, @@ -30,10 +31,12 @@ export { type Ray, RedFormat, RedIntegerFormat, + RepeatWrapping, RGBAFormat, ShaderMaterial, Sphere, SrcAlphaFactor, + SRGBColorSpace, Texture, TextureLoader, Uint32BufferAttribute, diff --git a/src/test/libs/three-usage-test.ts b/src/test/libs/three-usage-test.ts index 2180961a..3cf48d2c 100644 --- a/src/test/libs/three-usage-test.ts +++ b/src/test/libs/three-usage-test.ts @@ -9,7 +9,11 @@ export { DirectionalLight, Frustum, GridHelper, + LinearMipMapLinearFilter, + Matrix3, Matrix4, + NearestFilter, + Object3D, PCFSoftShadowMap, PerspectiveCamera, Ray, diff --git a/src/test/main.ts b/src/test/main.ts index e5bc7923..a774b370 100644 --- a/src/test/main.ts +++ b/src/test/main.ts @@ -3,6 +3,7 @@ import { ELogLevel, setVerbosity } from '../lib/index'; import { VoxelMap } from './map/voxel-map'; import { type TestBase } from './test-base'; import { TestBoard } from './test-board'; +import { TestGrass } from './test-grass'; import { TestParticles } from './test-particles'; import { TestPhysics } from './test-physics'; import { TestTerrain } from './test-terrain'; @@ -20,6 +21,7 @@ enum ETest { PHYSICS, PARTICLES, BOARD, + GRASS, } function createVoxelMap(): VoxelMap { @@ -30,7 +32,7 @@ function createVoxelMap(): VoxelMap { return new VoxelMap(mapScaleXZ, mapScaleY, mapSeed, includeTreesInLod); } -function buildTestScene(test: ETest): TestBase { +async function buildTestScene(test: ETest): Promise { if (test === ETest.TERRAIN) { return new TestTerrain(createVoxelMap()); } else if (test === ETest.TERRAIN_OLD) { @@ -45,10 +47,16 @@ function buildTestScene(test: ETest): TestBase { return new TestParticles(createVoxelMap()); } else if (test === ETest.BOARD) { return new TestBoard(createVoxelMap()); + } else if (test === ETest.GRASS) { + return TestGrass.instanciate(); } else { throw new Error(`Unknown test "${test}".`); } } -const testScene = buildTestScene(ETest.BOARD); -testScene.start(); +async function start(): Promise { + const testScene = await buildTestScene(ETest.GRASS); + testScene.start(); +} + +void start(); diff --git a/src/test/test-grass.ts b/src/test/test-grass.ts new file mode 100644 index 00000000..996e0c36 --- /dev/null +++ b/src/test/test-grass.ts @@ -0,0 +1,166 @@ +import GUI from 'lil-gui'; +import * as THREE from 'three-usage-test'; + +import { GrassPatchesBatch } from '../lib'; + +import { TestBase } from './test-base'; + +type GrassParticle = { + readonly position: THREE.Vector2Like; + visible: boolean; + lastChangeTimestamp: number; +}; + +class TestGrass extends TestBase { + private readonly gui: GUI; + + private readonly grass: GrassPatchesBatch; + private readonly particles: ReadonlyArray; + + private readonly fakeCamera: THREE.Object3D; + + private readonly parameters = { + viewRadius: 20, + transitionTime: 0.25, + useFakeCamera: true, + }; + + public static async instanciate(): Promise { + const gltfLoader = new THREE.GLTFLoader(); + + const scene = await gltfLoader.loadAsync('resources/grass2.glb'); + + let bufferGeometry: THREE.BufferGeometry | null = null; + scene.scene.traverse(object => { + if ((object as THREE.Mesh).isMesh) { + bufferGeometry = (object as THREE.Mesh).geometry; + } + }); + + if (!bufferGeometry) { + throw new Error('Failed to load buffer geometry'); + } + + return new TestGrass(bufferGeometry); + } + + private constructor(bufferGeometry: THREE.BufferGeometry) { + super(); + + this.camera.position.set(10, 10, 10); + this.cameraControl.target.set(0, 1, 0); + + const dirLight = new THREE.DirectionalLight(0xffffff, 1); + dirLight.target.position.set(0, 0, 0); + dirLight.position.set(100, 50, 100); + this.scene.add(dirLight); + + const ambientLight = new THREE.AmbientLight(0xffffff); + this.scene.add(ambientLight); + + const count = 10000; + this.grass = new GrassPatchesBatch({ + count, + bufferGeometry, // : new THREE.SphereGeometry(1), + }); + this.scene.add(this.grass.object3D); + + const particles: GrassParticle[] = []; + for (let i = 0; i < count; i++) { + particles.push({ + position: { + x: 100 * (Math.random() - 0.5), + y: 100 * (Math.random() - 0.5), + }, + visible: false, + lastChangeTimestamp: -Infinity, + }); + } + this.particles = particles; + + this.particles.forEach((particle: GrassParticle, index: number) => { + this.grass.setPosition(index, particle.position); + }); + + this.fakeCamera = new THREE.Object3D(); + const boardCenterControls = new THREE.TransformControls(this.camera, this.renderer.domElement); + boardCenterControls.showY = false; + boardCenterControls.addEventListener('dragging-changed', event => { + this.cameraControl.enabled = !event.value; + }); + boardCenterControls.attach(this.fakeCamera); + this.scene.add(this.fakeCamera); + this.scene.add(boardCenterControls.getHelper()); + + const groundSize = 1000; + const voxelsInGroundTexture = 20; + const groundTextureSize = 5 * voxelsInGroundTexture; + const groundTextureBuffer = new Uint8Array(groundTextureSize * groundTextureSize * 4); + for (let i = 0; i < groundTextureSize * groundTextureSize; i++) { + const rand = 255 * (Math.random() - 0.5) * 0.2; + groundTextureBuffer[4 * i + 0] = THREE.clamp(0 + rand, 0, 255); + groundTextureBuffer[4 * i + 1] = THREE.clamp(185 + rand, 0, 255); + groundTextureBuffer[4 * i + 2] = THREE.clamp(20 + rand, 0, 255); + groundTextureBuffer[4 * i + 3] = 255; + } + const groundTexture = new THREE.DataTexture(groundTextureBuffer, groundTextureSize, groundTextureSize); + groundTexture.needsUpdate = true; + groundTexture.wrapS = THREE.RepeatWrapping; + groundTexture.wrapT = THREE.RepeatWrapping; + groundTexture.matrix = new THREE.Matrix3().makeScale(groundSize / voxelsInGroundTexture, groundSize / voxelsInGroundTexture); + groundTexture.matrixAutoUpdate = false; + groundTexture.minFilter = THREE.LinearMipMapLinearFilter; + groundTexture.magFilter = THREE.NearestFilter; + const ground = new THREE.Mesh(new THREE.PlaneGeometry(1, 1), new THREE.MeshPhongMaterial({ map: groundTexture })); + ground.rotateX(-Math.PI / 2); + ground.scale.set(groundSize, groundSize, 1); + this.scene.add(ground); + + this.gui = new GUI(); + this.gui.show(); + this.gui.add(this.grass, 'minDissolve', 0, 1, 0.01).name('Min dissolve'); + this.gui.add(this.parameters, 'viewRadius', 0, 200, 1).name('View distance'); + this.gui.add(this.parameters, 'transitionTime', 0, 2, 0.01).name('Transition time'); + this.gui.add(ground, 'visible').name('Show ground'); + this.gui.add(this.parameters, 'useFakeCamera').name('Fake camera'); + } + + protected override update(): void { + this.grass.update(); + + const camera = this.parameters.useFakeCamera ? this.fakeCamera : this.camera; + const cameraPosition = camera.getWorldPosition(new THREE.Vector3()); + cameraPosition.setY(0); + + const particlePosition = new THREE.Vector3(); + this.particles.forEach((particle: GrassParticle, index: number) => { + particlePosition.set(particle.position.x, 0, particle.position.y); + + const shouldBeVisible = cameraPosition.distanceTo(particlePosition) < this.parameters.viewRadius; + if (particle.visible !== shouldBeVisible) { + particle.visible = shouldBeVisible; + particle.lastChangeTimestamp = performance.now(); + } + + this.grass.setDissolve(index, this.getParticleDissolveRatio(particle)); + }); + } + + private getParticleDissolveRatio(particle: GrassParticle): number { + if (this.parameters.transitionTime <= 0) { + return 1 - +particle.visible; + } + + const transitionStep = (0.001 * (performance.now() - particle.lastChangeTimestamp)) / this.parameters.transitionTime; + + if (transitionStep <= 0) { + return particle.visible ? 1 : 0; + } else if (transitionStep >= 1) { + return particle.visible ? 0 : 1; + } else { + return particle.visible ? 1 - transitionStep : transitionStep; + } + } +} + +export { TestGrass }; diff --git a/test/resources/grass2.glb b/test/resources/grass2.glb new file mode 100644 index 0000000000000000000000000000000000000000..25bc860b40c5dd476eb77794f426838b23c60182 GIT binary patch literal 9056 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 Ilh~8dhg#g}L;wH) literal 0 HcmV?d00001 From c6a922d3173f91d2f260aaffde17f0a9aa3f4d44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Piellard?= Date: Wed, 1 Jan 2025 12:25:59 +0100 Subject: [PATCH 07/31] test: display player as a sphere --- src/lib/effects/grass/grass-patches.ts | 4 ++-- src/test/test-grass.ts | 19 +++++++++++++------ 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/src/lib/effects/grass/grass-patches.ts b/src/lib/effects/grass/grass-patches.ts index de0f69d2..7c77b394 100644 --- a/src/lib/effects/grass/grass-patches.ts +++ b/src/lib/effects/grass/grass-patches.ts @@ -113,8 +113,8 @@ class GrassPatchesBatch { this.dissolveAttribute.needsUpdate = true; } - public setPosition(index: number, position: THREE.Vector2Like): void { - this.instancedMesh.setMatrixAt(index, new THREE.Matrix4().makeTranslation(new THREE.Vector3(position.x, 0, position.y))); + public setMatrix(index: number, matrix: THREE.Matrix4): void { + this.instancedMesh.setMatrixAt(index, matrix); } } diff --git a/src/test/test-grass.ts b/src/test/test-grass.ts index 996e0c36..97bbcf67 100644 --- a/src/test/test-grass.ts +++ b/src/test/test-grass.ts @@ -22,7 +22,7 @@ class TestGrass extends TestBase { private readonly parameters = { viewRadius: 20, transitionTime: 0.25, - useFakeCamera: true, + centerOnPlayer: true, }; public static async instanciate(): Promise { @@ -47,12 +47,12 @@ class TestGrass extends TestBase { private constructor(bufferGeometry: THREE.BufferGeometry) { super(); - this.camera.position.set(10, 10, 10); + this.camera.position.set(5, 5, 5); this.cameraControl.target.set(0, 1, 0); const dirLight = new THREE.DirectionalLight(0xffffff, 1); dirLight.target.position.set(0, 0, 0); - dirLight.position.set(100, 50, 100); + dirLight.position.set(100, 50, 80); this.scene.add(dirLight); const ambientLight = new THREE.AmbientLight(0xffffff); @@ -79,15 +79,22 @@ class TestGrass extends TestBase { this.particles = particles; this.particles.forEach((particle: GrassParticle, index: number) => { - this.grass.setPosition(index, particle.position); + const matrix = new THREE.Matrix4().multiplyMatrices( + new THREE.Matrix4().makeTranslation(new THREE.Vector3(particle.position.x, 0, particle.position.y)), + new THREE.Matrix4().makeRotationY(Math.PI / 2 * Math.floor(4 * Math.random())), + ); + this.grass.setMatrix(index, matrix); }); this.fakeCamera = new THREE.Object3D(); + this.fakeCamera.position.set(0, 0.5, 0); const boardCenterControls = new THREE.TransformControls(this.camera, this.renderer.domElement); boardCenterControls.showY = false; boardCenterControls.addEventListener('dragging-changed', event => { this.cameraControl.enabled = !event.value; }); + const fakePlayer = new THREE.Mesh(new THREE.SphereGeometry(0.5, 12, 12), new THREE.MeshBasicMaterial({ color: 0xffffff, wireframe: true })); + this.fakeCamera.add(fakePlayer); boardCenterControls.attach(this.fakeCamera); this.scene.add(this.fakeCamera); this.scene.add(boardCenterControls.getHelper()); @@ -122,13 +129,13 @@ class TestGrass extends TestBase { this.gui.add(this.parameters, 'viewRadius', 0, 200, 1).name('View distance'); this.gui.add(this.parameters, 'transitionTime', 0, 2, 0.01).name('Transition time'); this.gui.add(ground, 'visible').name('Show ground'); - this.gui.add(this.parameters, 'useFakeCamera').name('Fake camera'); + this.gui.add(this.parameters, 'centerOnPlayer').name('Center on player'); } protected override update(): void { this.grass.update(); - const camera = this.parameters.useFakeCamera ? this.fakeCamera : this.camera; + const camera = this.parameters.centerOnPlayer ? this.fakeCamera : this.camera; const cameraPosition = camera.getWorldPosition(new THREE.Vector3()); cameraPosition.setY(0); From 1b074e8e2fbc0fa6e5a15e1687365f05cce4ec6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Piellard?= Date: Wed, 1 Jan 2025 13:19:16 +0100 Subject: [PATCH 08/31] feat: player-reactive grass --- src/lib/effects/grass/grass-patches.ts | 37 +++++++++++++++++++++++-- src/test/test-grass.ts | 23 +++++++++++---- test/resources/grass-2d.glb | Bin 0 -> 1416 bytes test/resources/grass-2d.png | Bin 0 -> 702 bytes 4 files changed, 51 insertions(+), 9 deletions(-) create mode 100644 test/resources/grass-2d.glb create mode 100644 test/resources/grass-2d.png diff --git a/src/lib/effects/grass/grass-patches.ts b/src/lib/effects/grass/grass-patches.ts index 7c77b394..6a404a70 100644 --- a/src/lib/effects/grass/grass-patches.ts +++ b/src/lib/effects/grass/grass-patches.ts @@ -8,6 +8,7 @@ const params = { type ClutterMaterial = { readonly material: THREE.MeshPhongMaterial; readonly uniforms: { + uPlayerModelPosition: THREE.IUniform; uDissolveThreshold: THREE.IUniform; }; }; @@ -26,8 +27,7 @@ function buildNoiseTexture(resolution: number): THREE.DataTexture { return texture; } -function buildMaterial(): ClutterMaterial { - const phongMaterial = new THREE.MeshPhongMaterial({ color: 0xffffff }); +function customizeMaterial(phongMaterial: THREE.MeshPhongMaterial): ClutterMaterial { phongMaterial.customProgramCacheKey = () => `prop_phong_material`; const noiseTextureSize = 64; @@ -40,6 +40,7 @@ function buildMaterial(): ClutterMaterial { const customUniforms = { uNoiseTexture: { value: noiseTexture }, uDissolveThreshold: dissolveUniform, + uPlayerModelPosition: { value: new THREE.Vector3(Infinity, Infinity, Infinity) }, }; phongMaterial.onBeforeCompile = parameters => { @@ -51,12 +52,37 @@ function buildMaterial(): ClutterMaterial { parameters.vertexShader = applyReplacements(parameters.vertexShader, { 'void main() {': ` + uniform vec3 uPlayerModelPosition; + in float aDissolveRatio; out float vDissolveRatio; void main() { vDissolveRatio = aDissolveRatio; `, + // https://github.com/mrdoob/three.js/blob/dev/src/renderers/shaders/ShaderChunk/project_vertex.glsl.js + "#include ": ` + vec4 mvPosition = vec4( transformed, 1.0 ); + + #ifdef USE_BATCHING + mvPosition = batchingMatrix * mvPosition; + #endif + + #ifdef USE_INSTANCING + mvPosition = instanceMatrix * mvPosition; + #endif + + vec3 fromPlayer = mvPosition.xyz - uPlayerModelPosition; + float fromPlayerLength = length(fromPlayer) + 0.00001; + const float playerRadius = 0.6; + vec3 displacement = fromPlayer / fromPlayerLength * (playerRadius - fromPlayerLength) + * step(fromPlayerLength, playerRadius) * step(0.2, mvPosition.y); + mvPosition.xz += displacement.xz; + + mvPosition = modelViewMatrix * mvPosition; + + gl_Position = projectionMatrix * mvPosition; + `, }); parameters.fragmentShader = applyReplacements(parameters.fragmentShader, { @@ -83,6 +109,7 @@ function buildMaterial(): ClutterMaterial { type Paramerers = { count: number; bufferGeometry: THREE.BufferGeometry; + material: THREE.MeshPhongMaterial; }; class GrassPatchesBatch { @@ -91,6 +118,8 @@ class GrassPatchesBatch { } public minDissolve: number = 0; + public readonly playerPosition: THREE.Vector3; + private readonly instancedMesh: THREE.InstancedMesh; private readonly material: ClutterMaterial; private readonly dissolveAttribute: THREE.InstancedBufferAttribute; @@ -99,9 +128,11 @@ class GrassPatchesBatch { this.dissolveAttribute = new THREE.InstancedBufferAttribute(new Float32Array(params.count), 1); params.bufferGeometry.setAttribute('aDissolveRatio', this.dissolveAttribute); - this.material = buildMaterial(); + this.material = customizeMaterial(params.material); this.instancedMesh = new THREE.InstancedMesh(params.bufferGeometry, this.material.material, params.count); this.instancedMesh.count = params.count; + + this.playerPosition = this.material.uniforms.uPlayerModelPosition.value; } public update(): void { diff --git a/src/test/test-grass.ts b/src/test/test-grass.ts index 97bbcf67..05b5d6cb 100644 --- a/src/test/test-grass.ts +++ b/src/test/test-grass.ts @@ -28,7 +28,7 @@ class TestGrass extends TestBase { public static async instanciate(): Promise { const gltfLoader = new THREE.GLTFLoader(); - const scene = await gltfLoader.loadAsync('resources/grass2.glb'); + const scene = await gltfLoader.loadAsync('resources/grass-2d.glb'); let bufferGeometry: THREE.BufferGeometry | null = null; scene.scene.traverse(object => { @@ -41,10 +41,19 @@ class TestGrass extends TestBase { throw new Error('Failed to load buffer geometry'); } - return new TestGrass(bufferGeometry); + const texture = new THREE.TextureLoader().load("resources/grass-2d.png"); + texture.magFilter = THREE.NearestFilter; + texture.minFilter = THREE.NearestFilter; + const material = new THREE.MeshPhongMaterial({ + map: texture, + side: THREE.DoubleSide, + alphaTest: 0.5, + }) + + return new TestGrass(bufferGeometry, material); } - private constructor(bufferGeometry: THREE.BufferGeometry) { + private constructor(bufferGeometry: THREE.BufferGeometry, material: THREE.MeshPhongMaterial) { super(); this.camera.position.set(5, 5, 5); @@ -61,7 +70,8 @@ class TestGrass extends TestBase { const count = 10000; this.grass = new GrassPatchesBatch({ count, - bufferGeometry, // : new THREE.SphereGeometry(1), + bufferGeometry, + material, }); this.scene.add(this.grass.object3D); @@ -81,7 +91,7 @@ class TestGrass extends TestBase { this.particles.forEach((particle: GrassParticle, index: number) => { const matrix = new THREE.Matrix4().multiplyMatrices( new THREE.Matrix4().makeTranslation(new THREE.Vector3(particle.position.x, 0, particle.position.y)), - new THREE.Matrix4().makeRotationY(Math.PI / 2 * Math.floor(4 * Math.random())), + new THREE.Matrix4().makeRotationY(Math.PI / 2 * Math.random()),//Math.floor(4 * Math.random())), ); this.grass.setMatrix(index, matrix); }); @@ -93,7 +103,7 @@ class TestGrass extends TestBase { boardCenterControls.addEventListener('dragging-changed', event => { this.cameraControl.enabled = !event.value; }); - const fakePlayer = new THREE.Mesh(new THREE.SphereGeometry(0.5, 12, 12), new THREE.MeshBasicMaterial({ color: 0xffffff, wireframe: true })); + const fakePlayer = new THREE.Mesh(new THREE.SphereGeometry(0.5, 12, 12), new THREE.MeshPhongMaterial({ color: 0xffffff, wireframe: false })); this.fakeCamera.add(fakePlayer); boardCenterControls.attach(this.fakeCamera); this.scene.add(this.fakeCamera); @@ -133,6 +143,7 @@ class TestGrass extends TestBase { } protected override update(): void { + this.fakeCamera.getWorldPosition(this.grass.playerPosition); this.grass.update(); const camera = this.parameters.centerOnPlayer ? this.fakeCamera : this.camera; diff --git a/test/resources/grass-2d.glb b/test/resources/grass-2d.glb new file mode 100644 index 0000000000000000000000000000000000000000..c6d65894f78b10716e6790a640ede53645a07525 GIT binary patch literal 1416 zcma)5?~c+y5MS>+54|Vw8=23w_8(B1DBvx8yhZg)EHo1El1Shm`ecl&?Q^^HWPTRjULFOlcGcAw#gCYA{nE?qL9Mv{Z}1 zd6xk5Mreb!^eMo?J{1n_e0ILtB`~7#g`}qV)1k+xs%ZuYobXE8GTzFsz&Hv<0S~4r z!s9#&x)Uz837op^dZ+F-W$>okdQ)wHqbtxkd0TVc){FNVI0hykuzt`J6muq1^Ln_8 z!{~1n-HGqh=q#WgWSm|&8i$NB-nkrObp_ohD$olj4DV4lFkkPZs8O}ca+QjD!PYH9 zS2VS#$z_p5e=cjdf| zjwS6*$ChoJZvKxhjq=6}YngnzR&7*UvgfPx*IOX3zkA7PgR%j~kM9J{pswW2N4z$wjt zy+_D#y-9v87SF$Yeq8Z=f4)fdpV!yl@u^o(h_K_km%6Z{JQMiei^UK0RWe=J1s=oB zg+0>|C*$LOjy`D@I?9qObQH-YanMprAyFYeY$Kep*R+Vo@qXKw@TIiJqTph=Qq} zp`Ow2JB1`TQ-vi8F7W6)UNnJAaO&KpH-x^P?p65ntM43B-}%YX24-#D z@82sQxDr)oZ(m$z-Q{1&ci>3WtAh*J%O)|T-|t&|b(Uu=bHXK&)uu~>Ivxr%OcmIn zv)C{A#x0*q*Us-%=V!dd7CI?Fv(;0`?vBhc8KX2Zgz7~y$x%iRUi!AyIOoH!B{GcC}|b8@Q4+Xn9UD=&*IH?8Cd@hYsa z?csiJ-Vo%q?A%8oChqr~0`25zw6b7$!2jQ8{gOo+UY-F( Nf~TvW%Q~loCIH^i Date: Wed, 1 Jan 2025 13:31:55 +0100 Subject: [PATCH 09/31] fix: player position was sometimes no applied correctly --- src/lib/effects/grass/grass-patches.ts | 63 ++++++++++++++------------ src/test/test-grass.ts | 7 +-- 2 files changed, 39 insertions(+), 31 deletions(-) diff --git a/src/lib/effects/grass/grass-patches.ts b/src/lib/effects/grass/grass-patches.ts index 6a404a70..afc4ca82 100644 --- a/src/lib/effects/grass/grass-patches.ts +++ b/src/lib/effects/grass/grass-patches.ts @@ -27,7 +27,7 @@ function buildNoiseTexture(resolution: number): THREE.DataTexture { return texture; } -function customizeMaterial(phongMaterial: THREE.MeshPhongMaterial): ClutterMaterial { +function customizeMaterial(phongMaterial: THREE.MeshPhongMaterial, playerReactive: boolean): ClutterMaterial { phongMaterial.customProgramCacheKey = () => `prop_phong_material`; const noiseTextureSize = 64; @@ -60,31 +60,36 @@ function customizeMaterial(phongMaterial: THREE.MeshPhongMaterial): ClutterMater void main() { vDissolveRatio = aDissolveRatio; `, - // https://github.com/mrdoob/three.js/blob/dev/src/renderers/shaders/ShaderChunk/project_vertex.glsl.js - "#include ": ` - vec4 mvPosition = vec4( transformed, 1.0 ); - - #ifdef USE_BATCHING - mvPosition = batchingMatrix * mvPosition; - #endif - - #ifdef USE_INSTANCING - mvPosition = instanceMatrix * mvPosition; - #endif - - vec3 fromPlayer = mvPosition.xyz - uPlayerModelPosition; - float fromPlayerLength = length(fromPlayer) + 0.00001; - const float playerRadius = 0.6; - vec3 displacement = fromPlayer / fromPlayerLength * (playerRadius - fromPlayerLength) - * step(fromPlayerLength, playerRadius) * step(0.2, mvPosition.y); - mvPosition.xz += displacement.xz; - - mvPosition = modelViewMatrix * mvPosition; - - gl_Position = projectionMatrix * mvPosition; - `, }); + if (playerReactive) { + parameters.vertexShader = applyReplacements(parameters.vertexShader, { + // https://github.com/mrdoob/three.js/blob/dev/src/renderers/shaders/ShaderChunk/project_vertex.glsl.js + "#include ": ` + vec4 mvPosition = vec4( transformed, 1.0 ); + + #ifdef USE_BATCHING + mvPosition = batchingMatrix * mvPosition; + #endif + + #ifdef USE_INSTANCING + mvPosition = instanceMatrix * mvPosition; + #endif + + vec3 fromPlayer = mvPosition.xyz - uPlayerModelPosition; + float fromPlayerLength = length(fromPlayer) + 0.00001; + const float playerRadius = 0.6; + vec3 displacement = fromPlayer / fromPlayerLength * (playerRadius - fromPlayerLength) + * step(fromPlayerLength, playerRadius) * step(0.2, mvPosition.y); + mvPosition.xz += displacement.xz; + + mvPosition = modelViewMatrix * mvPosition; + + gl_Position = projectionMatrix * mvPosition; + `, + }); + } + parameters.fragmentShader = applyReplacements(parameters.fragmentShader, { 'void main() {': ` uniform sampler2D uNoiseTexture; @@ -118,7 +123,7 @@ class GrassPatchesBatch { } public minDissolve: number = 0; - public readonly playerPosition: THREE.Vector3; + public readonly playerWorldPosition = new THREE.Vector3(); private readonly instancedMesh: THREE.InstancedMesh; private readonly material: ClutterMaterial; @@ -128,14 +133,16 @@ class GrassPatchesBatch { this.dissolveAttribute = new THREE.InstancedBufferAttribute(new Float32Array(params.count), 1); params.bufferGeometry.setAttribute('aDissolveRatio', this.dissolveAttribute); - this.material = customizeMaterial(params.material); + this.material = customizeMaterial(params.material, true); this.instancedMesh = new THREE.InstancedMesh(params.bufferGeometry, this.material.material, params.count); this.instancedMesh.count = params.count; - - this.playerPosition = this.material.uniforms.uPlayerModelPosition.value; } public update(): void { + this.material.uniforms.uPlayerModelPosition.value.copy(this.playerWorldPosition).applyMatrix4( + this.object3D.matrixWorld.clone().invert() + ); + this.material.uniforms.uDissolveThreshold.value = this.minDissolve; } diff --git a/src/test/test-grass.ts b/src/test/test-grass.ts index 05b5d6cb..dfbcb5bd 100644 --- a/src/test/test-grass.ts +++ b/src/test/test-grass.ts @@ -136,14 +136,15 @@ class TestGrass extends TestBase { this.gui = new GUI(); this.gui.show(); this.gui.add(this.grass, 'minDissolve', 0, 1, 0.01).name('Min dissolve'); - this.gui.add(this.parameters, 'viewRadius', 0, 200, 1).name('View distance'); this.gui.add(this.parameters, 'transitionTime', 0, 2, 0.01).name('Transition time'); + this.gui.add(this.parameters, 'viewRadius', 0, 200, 1).name('View distance'); + this.gui.add(this.parameters, 'centerOnPlayer').name('Center view on player'); this.gui.add(ground, 'visible').name('Show ground'); - this.gui.add(this.parameters, 'centerOnPlayer').name('Center on player'); + this.gui.add(this.grass.object3D, 'visible').name('Show grass'); } protected override update(): void { - this.fakeCamera.getWorldPosition(this.grass.playerPosition); + this.fakeCamera.getWorldPosition(this.grass.playerWorldPosition); this.grass.update(); const camera = this.parameters.centerOnPlayer ? this.fakeCamera : this.camera; From 9504a1b070e1b4a63dcea51f478288b383c5e978 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Piellard?= Date: Wed, 1 Jan 2025 13:45:49 +0100 Subject: [PATCH 10/31] test: add 2d/3d grass toggle --- src/test/test-grass.ts | 130 ++++++++++++++------ test/resources/{grass2.glb => grass-3d.glb} | Bin 2 files changed, 91 insertions(+), 39 deletions(-) rename test/resources/{grass2.glb => grass-3d.glb} (100%) diff --git a/src/test/test-grass.ts b/src/test/test-grass.ts index dfbcb5bd..658ad96b 100644 --- a/src/test/test-grass.ts +++ b/src/test/test-grass.ts @@ -11,11 +11,58 @@ type GrassParticle = { lastChangeTimestamp: number; }; +type ClutterDefinition = { + readonly bufferGeometry: THREE.BufferGeometry; + readonly material: THREE.MeshPhongMaterial; +}; + +function extractBufferGeometry(object: THREE.Object3D): THREE.BufferGeometry { + let bufferGeometry: THREE.BufferGeometry | null = null; + object.traverse(child => { + if ((child as THREE.Mesh).isMesh) { + bufferGeometry = (child as THREE.Mesh).geometry; + } + }); + + if (!bufferGeometry) { + throw new Error('Failed to load buffer geometry'); + } + return bufferGeometry; +} + +async function getGrass2D(gltfLoader: THREE.GLTFLoader): Promise { + const glb = await gltfLoader.loadAsync('resources/grass-2d.glb'); + const bufferGeometry = extractBufferGeometry(glb.scene); + + const texture = new THREE.TextureLoader().load("resources/grass-2d.png"); + texture.magFilter = THREE.NearestFilter; + texture.minFilter = THREE.NearestFilter; + const material = new THREE.MeshPhongMaterial({ + map: texture, + side: THREE.DoubleSide, + alphaTest: 0.5, + }); + return { bufferGeometry, material }; +} + +async function getGrass3D(gltfLoader: THREE.GLTFLoader): Promise { + const glb = await gltfLoader.loadAsync('resources/grass-3d.glb'); + const bufferGeometry = extractBufferGeometry(glb.scene); + const material = new THREE.MeshPhongMaterial({ color: 0xffffff }); + return { bufferGeometry, material }; +} + +enum EGrassMode { + GRASS_2D = "2d", + GRASS_3D = "3d", +}; + class TestGrass extends TestBase { private readonly gui: GUI; - private readonly grass: GrassPatchesBatch; - private readonly particles: ReadonlyArray; + private readonly grass2D: GrassPatchesBatch; + private readonly grass3D: GrassPatchesBatch; + private readonly grassParticles: ReadonlyArray; private readonly fakeCamera: THREE.Object3D; @@ -23,37 +70,20 @@ class TestGrass extends TestBase { viewRadius: 20, transitionTime: 0.25, centerOnPlayer: true, + grassMode: EGrassMode.GRASS_2D, }; public static async instanciate(): Promise { const gltfLoader = new THREE.GLTFLoader(); + const [grass2D, grass3D] = await Promise.all([ + getGrass2D(gltfLoader), + getGrass3D(gltfLoader), + ]); - const scene = await gltfLoader.loadAsync('resources/grass-2d.glb'); - - let bufferGeometry: THREE.BufferGeometry | null = null; - scene.scene.traverse(object => { - if ((object as THREE.Mesh).isMesh) { - bufferGeometry = (object as THREE.Mesh).geometry; - } - }); - - if (!bufferGeometry) { - throw new Error('Failed to load buffer geometry'); - } - - const texture = new THREE.TextureLoader().load("resources/grass-2d.png"); - texture.magFilter = THREE.NearestFilter; - texture.minFilter = THREE.NearestFilter; - const material = new THREE.MeshPhongMaterial({ - map: texture, - side: THREE.DoubleSide, - alphaTest: 0.5, - }) - - return new TestGrass(bufferGeometry, material); + return new TestGrass(grass2D, grass3D); } - private constructor(bufferGeometry: THREE.BufferGeometry, material: THREE.MeshPhongMaterial) { + private constructor(grass2D: ClutterDefinition, grass3D: ClutterDefinition) { super(); this.camera.position.set(5, 5, 5); @@ -67,13 +97,21 @@ class TestGrass extends TestBase { const ambientLight = new THREE.AmbientLight(0xffffff); this.scene.add(ambientLight); + const grassContainer = new THREE.Object3D(); + this.scene.add(grassContainer); const count = 10000; - this.grass = new GrassPatchesBatch({ + this.grass2D = new GrassPatchesBatch({ + count, + bufferGeometry: grass2D.bufferGeometry, + material: grass2D.material, + }); + grassContainer.add(this.grass2D.object3D); + this.grass3D = new GrassPatchesBatch({ count, - bufferGeometry, - material, + bufferGeometry: grass3D.bufferGeometry, + material: grass3D.material, }); - this.scene.add(this.grass.object3D); + grassContainer.add(this.grass3D.object3D); const particles: GrassParticle[] = []; for (let i = 0; i < count; i++) { @@ -86,14 +124,15 @@ class TestGrass extends TestBase { lastChangeTimestamp: -Infinity, }); } - this.particles = particles; + this.grassParticles = particles; - this.particles.forEach((particle: GrassParticle, index: number) => { + this.grassParticles.forEach((particle: GrassParticle, index: number) => { const matrix = new THREE.Matrix4().multiplyMatrices( new THREE.Matrix4().makeTranslation(new THREE.Vector3(particle.position.x, 0, particle.position.y)), new THREE.Matrix4().makeRotationY(Math.PI / 2 * Math.random()),//Math.floor(4 * Math.random())), ); - this.grass.setMatrix(index, matrix); + this.grass2D.setMatrix(index, matrix); + this.grass3D.setMatrix(index, matrix); }); this.fakeCamera = new THREE.Object3D(); @@ -133,26 +172,36 @@ class TestGrass extends TestBase { ground.scale.set(groundSize, groundSize, 1); this.scene.add(ground); + const applyGrassMode = () => { + this.grass2D.object3D.visible = this.parameters.grassMode === EGrassMode.GRASS_2D; + this.grass3D.object3D.visible = this.parameters.grassMode === EGrassMode.GRASS_3D; + }; + applyGrassMode(); + this.gui = new GUI(); this.gui.show(); - this.gui.add(this.grass, 'minDissolve', 0, 1, 0.01).name('Min dissolve'); + this.gui.add(this.grass2D, 'minDissolve', 0, 1, 0.01).name('Min dissolve'); this.gui.add(this.parameters, 'transitionTime', 0, 2, 0.01).name('Transition time'); this.gui.add(this.parameters, 'viewRadius', 0, 200, 1).name('View distance'); this.gui.add(this.parameters, 'centerOnPlayer').name('Center view on player'); + this.gui.add(fakePlayer, 'visible').name('Show player'); this.gui.add(ground, 'visible').name('Show ground'); - this.gui.add(this.grass.object3D, 'visible').name('Show grass'); + this.gui.add(grassContainer, 'visible').name('Show grass'); + this.gui.add(this.parameters, "grassMode", Object.values(EGrassMode)).name("Grass type").onChange(applyGrassMode); } protected override update(): void { - this.fakeCamera.getWorldPosition(this.grass.playerWorldPosition); - this.grass.update(); + this.fakeCamera.getWorldPosition(this.grass2D.playerWorldPosition); + this.fakeCamera.getWorldPosition(this.grass3D.playerWorldPosition); + this.grass2D.update(); + this.grass3D.update(); const camera = this.parameters.centerOnPlayer ? this.fakeCamera : this.camera; const cameraPosition = camera.getWorldPosition(new THREE.Vector3()); cameraPosition.setY(0); const particlePosition = new THREE.Vector3(); - this.particles.forEach((particle: GrassParticle, index: number) => { + this.grassParticles.forEach((particle: GrassParticle, index: number) => { particlePosition.set(particle.position.x, 0, particle.position.y); const shouldBeVisible = cameraPosition.distanceTo(particlePosition) < this.parameters.viewRadius; @@ -161,7 +210,9 @@ class TestGrass extends TestBase { particle.lastChangeTimestamp = performance.now(); } - this.grass.setDissolve(index, this.getParticleDissolveRatio(particle)); + const particleDissolveRatio = this.getParticleDissolveRatio(particle); + this.grass2D.setDissolve(index, particleDissolveRatio); + this.grass3D.setDissolve(index, particleDissolveRatio); }); } @@ -183,3 +234,4 @@ class TestGrass extends TestBase { } export { TestGrass }; + diff --git a/test/resources/grass2.glb b/test/resources/grass-3d.glb similarity index 100% rename from test/resources/grass2.glb rename to test/resources/grass-3d.glb From c6bc8f432a8a490e69cfdd3e4cda7a931c242ee7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Piellard?= Date: Wed, 1 Jan 2025 14:15:16 +0100 Subject: [PATCH 11/31] test: use bluenoise for grass repartition --- ...repartition.ts => repeatable-bluenoise.ts} | 17 ++--- src/test/map/voxel-map.ts | 8 +-- src/test/test-grass.ts | 65 ++++++++++++++++--- 3 files changed, 68 insertions(+), 22 deletions(-) rename src/test/map/{trees/repartition.ts => repeatable-bluenoise.ts} (85%) diff --git a/src/test/map/trees/repartition.ts b/src/test/map/repeatable-bluenoise.ts similarity index 85% rename from src/test/map/trees/repartition.ts rename to src/test/map/repeatable-bluenoise.ts index a5b955ef..3abef23c 100644 --- a/src/test/map/trees/repartition.ts +++ b/src/test/map/repeatable-bluenoise.ts @@ -1,14 +1,15 @@ import alea from 'alea'; import type * as THREE from 'three-usage-test'; -import { safeModulo } from '../../../lib/helpers/math'; +import { safeModulo } from '../../lib/helpers/math'; -type TreePosition = { +type ItemPosition = { position: THREE.Vector2Like; probability: number; }; -class TreeRepartition { +/** Only works with integer coordinates */ +class RepeatableBluenoise { public readonly size: number; private readonly data: Uint8Array; private readonly prng: () => number; @@ -53,12 +54,12 @@ class TreeRepartition { } } - public getAllTrees(from: THREE.Vector2Like, to: THREE.Vector2Like): TreePosition[] { - const result: TreePosition[] = []; + public getAllItems(from: THREE.Vector2Like, to: THREE.Vector2Like): ItemPosition[] { + const result: ItemPosition[] = []; for (let iZ = from.y; iZ < to.y; iZ++) { for (let iX = from.x; iX < to.x; iX++) { - const probability = this.getTreeProbability(iX, iZ); + const probability = this.getItemProbability(iX, iZ); if (probability > 0) { result.push({ position: { x: iX, y: iZ }, @@ -71,7 +72,7 @@ class TreeRepartition { return result; } - private getTreeProbability(x: number, z: number): number { + private getItemProbability(x: number, z: number): number { x = safeModulo(x, this.size); z = safeModulo(z, this.size); @@ -88,4 +89,4 @@ class TreeRepartition { } } -export { TreeRepartition, type TreePosition }; +export { RepeatableBluenoise }; diff --git a/src/test/map/voxel-map.ts b/src/test/map/voxel-map.ts index 03f70a50..d23f87ed 100644 --- a/src/test/map/voxel-map.ts +++ b/src/test/map/voxel-map.ts @@ -13,7 +13,7 @@ import { } from '../../lib/index'; import { colorMapping } from './color-mapping'; -import { TreeRepartition } from './trees/repartition'; +import { RepeatableBluenoise } from './repeatable-bluenoise'; import { Tree } from './trees/tree'; type TreesTextureSample = { @@ -52,7 +52,7 @@ class VoxelMap implements IVoxelMap, IHeightmap { private readonly tree = new Tree(); private readonly treesDensityNoise: NoiseFunction2D; private readonly treesDensityFrequency = 0.002; - private readonly treesRepartition = new TreeRepartition('seed_trees', 150, 2 * this.tree.radiusXZ); + private readonly treesRepartition = new RepeatableBluenoise('seed_trees', 150, 2 * this.tree.radiusXZ); private readonly treesTexture: TreesTexture; private readonly colorVariationNoise: NoiseFunction2D; @@ -97,7 +97,7 @@ class VoxelMap implements IVoxelMap, IHeightmap { } const treeSearchFrom = { x: -this.tree.radiusXZ, y: -this.tree.radiusXZ }; const treeSearchTo = { x: this.treesTexture.size + this.tree.radiusXZ, y: this.treesTexture.size + this.tree.radiusXZ }; - for (const tree of this.treesRepartition.getAllTrees(treeSearchFrom, treeSearchTo)) { + for (const tree of this.treesRepartition.getAllItems(treeSearchFrom, treeSearchTo)) { const treeRootTexturePos = { x: tree.position.x + this.tree.offset.x, y: tree.position.y + this.tree.offset.z, @@ -364,7 +364,7 @@ class VoxelMap implements IVoxelMap, IHeightmap { const treeSearchFrom = { x: blockStart.x - this.tree.radiusXZ, y: blockStart.y - this.tree.radiusXZ }; const treeSearchTo = { x: blockEnd.x + this.tree.radiusXZ, y: blockEnd.y + this.tree.radiusXZ }; - for (const tree of this.treesRepartition.getAllTrees(treeSearchFrom, treeSearchTo)) { + for (const tree of this.treesRepartition.getAllItems(treeSearchFrom, treeSearchTo)) { const worldPos = { x: tree.position.x, y: 0, z: tree.position.y }; const sample = this.sampleHeightmapBaseTerrain(worldPos.x, worldPos.z); worldPos.y = sample.altitude; diff --git a/src/test/test-grass.ts b/src/test/test-grass.ts index 658ad96b..9d043420 100644 --- a/src/test/test-grass.ts +++ b/src/test/test-grass.ts @@ -4,6 +4,8 @@ import * as THREE from 'three-usage-test'; import { GrassPatchesBatch } from '../lib'; import { TestBase } from './test-base'; +import { RepeatableBluenoise } from './map/repeatable-bluenoise'; +import { logger } from '../lib/helpers/logger'; type GrassParticle = { readonly position: THREE.Vector2Like; @@ -57,6 +59,11 @@ enum EGrassMode { GRASS_3D = "3d", }; +type PositionsList = { position: THREE.Vector2Like }[]; +interface IRepartition { + getAllItems(from: THREE.Vector2Like, to: THREE.Vector2Like): PositionsList; +} + class TestGrass extends TestBase { private readonly gui: GUI; @@ -80,10 +87,49 @@ class TestGrass extends TestBase { getGrass3D(gltfLoader), ]); - return new TestGrass(grass2D, grass3D); + const density = 2; + const whitenoiseRepartition: IRepartition = { + getAllItems(from: THREE.Vector2Like, to: THREE.Vector2Like): PositionsList { + const result: PositionsList = []; + const areaSize = new THREE.Vector2().subVectors(to, from); + const totalArea = areaSize.x * areaSize.y; + const totalItemsCount = totalArea * density; + + for (let i = 0; i < totalItemsCount; i++) { + result.push({ + position: { + x: from.x + areaSize.x * Math.random(), + y: from.y + areaSize.y * Math.random(), + }, + }); + } + + return result; + }, + }; + + const repeatableBluenoise = new RepeatableBluenoise("seed", 150, 2); + const bluenoiseScaling = Math.ceil(density / 0.6); // arbitrary factor to match the same visual density as whitenoise + const bluenoiseRepartition: IRepartition = { + getAllItems(from: THREE.Vector2Like, to: THREE.Vector2Like): PositionsList { + const rawItems = repeatableBluenoise.getAllItems( + new THREE.Vector2().addScaledVector(from, bluenoiseScaling), + new THREE.Vector2().addScaledVector(to, bluenoiseScaling), + ); + return rawItems.map(item => { + return { + position: new THREE.Vector2().addScaledVector(item.position, 1 / bluenoiseScaling), + }; + }); + } + } + + const useBluenoise = true; + + return new TestGrass(grass2D, grass3D, useBluenoise ? bluenoiseRepartition : whitenoiseRepartition); } - private constructor(grass2D: ClutterDefinition, grass3D: ClutterDefinition) { + private constructor(grass2D: ClutterDefinition, grass3D: ClutterDefinition, repartition: IRepartition) { super(); this.camera.position.set(5, 5, 5); @@ -99,27 +145,26 @@ class TestGrass extends TestBase { const grassContainer = new THREE.Object3D(); this.scene.add(grassContainer); - const count = 10000; + + const allItemsPositions = repartition.getAllItems({ x: -100, y: -100 }, { x: 100, y: 100 }); + console.log(`${allItemsPositions.length} grass items`); this.grass2D = new GrassPatchesBatch({ - count, + count: allItemsPositions.length, bufferGeometry: grass2D.bufferGeometry, material: grass2D.material, }); grassContainer.add(this.grass2D.object3D); this.grass3D = new GrassPatchesBatch({ - count, + count: allItemsPositions.length, bufferGeometry: grass3D.bufferGeometry, material: grass3D.material, }); grassContainer.add(this.grass3D.object3D); const particles: GrassParticle[] = []; - for (let i = 0; i < count; i++) { + for (const itemPosition of allItemsPositions) { particles.push({ - position: { - x: 100 * (Math.random() - 0.5), - y: 100 * (Math.random() - 0.5), - }, + position: itemPosition.position, visible: false, lastChangeTimestamp: -Infinity, }); From f30de06f9ba4cf1d4189d4b3e5a1aac621a78fd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Piellard?= Date: Wed, 1 Jan 2025 17:04:38 +0100 Subject: [PATCH 12/31] test: add static rock props --- src/lib/effects/grass/grass-patches.ts | 9 +- src/test/test-grass.ts | 138 +++++++++++++++++-------- test/resources/props-rocks.glb | Bin 0 -> 5448 bytes 3 files changed, 102 insertions(+), 45 deletions(-) create mode 100644 test/resources/props-rocks.glb diff --git a/src/lib/effects/grass/grass-patches.ts b/src/lib/effects/grass/grass-patches.ts index afc4ca82..d5f01d47 100644 --- a/src/lib/effects/grass/grass-patches.ts +++ b/src/lib/effects/grass/grass-patches.ts @@ -112,9 +112,10 @@ function customizeMaterial(phongMaterial: THREE.MeshPhongMaterial, playerReactiv } type Paramerers = { - count: number; - bufferGeometry: THREE.BufferGeometry; - material: THREE.MeshPhongMaterial; + readonly count: number; + readonly reactToPlayer: boolean; + readonly bufferGeometry: THREE.BufferGeometry; + readonly material: THREE.MeshPhongMaterial; }; class GrassPatchesBatch { @@ -133,7 +134,7 @@ class GrassPatchesBatch { this.dissolveAttribute = new THREE.InstancedBufferAttribute(new Float32Array(params.count), 1); params.bufferGeometry.setAttribute('aDissolveRatio', this.dissolveAttribute); - this.material = customizeMaterial(params.material, true); + this.material = customizeMaterial(params.material, params.reactToPlayer); this.instancedMesh = new THREE.InstancedMesh(params.bufferGeometry, this.material.material, params.count); this.instancedMesh.count = params.count; } diff --git a/src/test/test-grass.ts b/src/test/test-grass.ts index 9d043420..bb95958f 100644 --- a/src/test/test-grass.ts +++ b/src/test/test-grass.ts @@ -3,9 +3,8 @@ import * as THREE from 'three-usage-test'; import { GrassPatchesBatch } from '../lib'; -import { TestBase } from './test-base'; import { RepeatableBluenoise } from './map/repeatable-bluenoise'; -import { logger } from '../lib/helpers/logger'; +import { TestBase } from './test-base'; type GrassParticle = { readonly position: THREE.Vector2Like; @@ -54,6 +53,13 @@ async function getGrass3D(gltfLoader: THREE.GLTFLoader): Promise { + const glb = await gltfLoader.loadAsync('resources/props-rocks.glb'); + const bufferGeometry = extractBufferGeometry(glb.scene); + const material = new THREE.MeshPhongMaterial({ color: 0xdddddd }); + return { bufferGeometry, material }; +} + enum EGrassMode { GRASS_2D = "2d", GRASS_3D = "3d", @@ -64,6 +70,18 @@ interface IRepartition { getAllItems(from: THREE.Vector2Like, to: THREE.Vector2Like): PositionsList; } +type Parameters = { + readonly propDefinitions: { + readonly grass2D: ClutterDefinition; + readonly grass3D: ClutterDefinition; + readonly rocks: ClutterDefinition; + }; + readonly repartitions: { + readonly bluenoise: IRepartition; + readonly whitenoise: IRepartition; + }; +}; + class TestGrass extends TestBase { private readonly gui: GUI; @@ -71,6 +89,9 @@ class TestGrass extends TestBase { private readonly grass3D: GrassPatchesBatch; private readonly grassParticles: ReadonlyArray; + private readonly rocks: GrassPatchesBatch; + private readonly rockParticles: ReadonlyArray; + private readonly fakeCamera: THREE.Object3D; private readonly parameters = { @@ -82,18 +103,19 @@ class TestGrass extends TestBase { public static async instanciate(): Promise { const gltfLoader = new THREE.GLTFLoader(); - const [grass2D, grass3D] = await Promise.all([ + const [grass2D, grass3D, rocks] = await Promise.all([ getGrass2D(gltfLoader), getGrass3D(gltfLoader), + getRocks(gltfLoader), ]); - const density = 2; + const whitenoiseDensity = 0.5; const whitenoiseRepartition: IRepartition = { getAllItems(from: THREE.Vector2Like, to: THREE.Vector2Like): PositionsList { const result: PositionsList = []; const areaSize = new THREE.Vector2().subVectors(to, from); const totalArea = areaSize.x * areaSize.y; - const totalItemsCount = totalArea * density; + const totalItemsCount = totalArea * whitenoiseDensity; for (let i = 0; i < totalItemsCount; i++) { result.push({ @@ -109,7 +131,8 @@ class TestGrass extends TestBase { }; const repeatableBluenoise = new RepeatableBluenoise("seed", 150, 2); - const bluenoiseScaling = Math.ceil(density / 0.6); // arbitrary factor to match the same visual density as whitenoise + const bluenoiseDensity = 2; + const bluenoiseScaling = Math.ceil(bluenoiseDensity / 0.6); const bluenoiseRepartition: IRepartition = { getAllItems(from: THREE.Vector2Like, to: THREE.Vector2Like): PositionsList { const rawItems = repeatableBluenoise.getAllItems( @@ -124,12 +147,16 @@ class TestGrass extends TestBase { } } - const useBluenoise = true; - - return new TestGrass(grass2D, grass3D, useBluenoise ? bluenoiseRepartition : whitenoiseRepartition); + return new TestGrass({ + propDefinitions: { grass2D, grass3D, rocks }, + repartitions: { + bluenoise: bluenoiseRepartition, + whitenoise: whitenoiseRepartition, + }, + }); } - private constructor(grass2D: ClutterDefinition, grass3D: ClutterDefinition, repartition: IRepartition) { + private constructor(params: Parameters) { super(); this.camera.position.set(5, 5, 5); @@ -146,31 +173,29 @@ class TestGrass extends TestBase { const grassContainer = new THREE.Object3D(); this.scene.add(grassContainer); - const allItemsPositions = repartition.getAllItems({ x: -100, y: -100 }, { x: 100, y: 100 }); - console.log(`${allItemsPositions.length} grass items`); + const allGrassParticlesPositions = params.repartitions.bluenoise.getAllItems({ x: -100, y: -100 }, { x: 100, y: 100 }); + this.grassParticles = allGrassParticlesPositions.map(grassParticle => { + return { + position: grassParticle.position, + visible: false, + lastChangeTimestamp: -Infinity, + } + }); + console.log(`${this.grassParticles.length} grass items`); this.grass2D = new GrassPatchesBatch({ - count: allItemsPositions.length, - bufferGeometry: grass2D.bufferGeometry, - material: grass2D.material, + count: this.grassParticles.length, + bufferGeometry: params.propDefinitions.grass2D.bufferGeometry, + material: params.propDefinitions.grass2D.material, + reactToPlayer: true, }); grassContainer.add(this.grass2D.object3D); this.grass3D = new GrassPatchesBatch({ - count: allItemsPositions.length, - bufferGeometry: grass3D.bufferGeometry, - material: grass3D.material, + count: this.grassParticles.length, + bufferGeometry: params.propDefinitions.grass3D.bufferGeometry, + material: params.propDefinitions.grass3D.material, + reactToPlayer: true, }); grassContainer.add(this.grass3D.object3D); - - const particles: GrassParticle[] = []; - for (const itemPosition of allItemsPositions) { - particles.push({ - position: itemPosition.position, - visible: false, - lastChangeTimestamp: -Infinity, - }); - } - this.grassParticles = particles; - this.grassParticles.forEach((particle: GrassParticle, index: number) => { const matrix = new THREE.Matrix4().multiplyMatrices( new THREE.Matrix4().makeTranslation(new THREE.Vector3(particle.position.x, 0, particle.position.y)), @@ -180,6 +205,30 @@ class TestGrass extends TestBase { this.grass3D.setMatrix(index, matrix); }); + const allRockParticlesPositions = params.repartitions.whitenoise.getAllItems({ x: -100, y: -100 }, { x: 100, y: 100 }); + this.rockParticles = allRockParticlesPositions.map(rockParticle => { + return { + position: rockParticle.position, + visible: false, + lastChangeTimestamp: -Infinity, + } + }); + console.log(`${this.rockParticles.length} rock items`); + this.rocks = new GrassPatchesBatch({ + count: this.rockParticles.length, + bufferGeometry: params.propDefinitions.rocks.bufferGeometry, + material: params.propDefinitions.rocks.material, + reactToPlayer: false, + }); + grassContainer.add(this.rocks.object3D); + this.rockParticles.forEach((particle: GrassParticle, index: number) => { + const matrix = new THREE.Matrix4().multiplyMatrices( + new THREE.Matrix4().makeTranslation(new THREE.Vector3(particle.position.x, 0, particle.position.y)), + new THREE.Matrix4().makeRotationY(Math.PI / 2 * Math.random()),//Math.floor(4 * Math.random())), + ); + this.rocks.setMatrix(index, matrix); + }); + this.fakeCamera = new THREE.Object3D(); this.fakeCamera.position.set(0, 0.5, 0); const boardCenterControls = new THREE.TransformControls(this.camera, this.renderer.domElement); @@ -231,7 +280,7 @@ class TestGrass extends TestBase { this.gui.add(this.parameters, 'centerOnPlayer').name('Center view on player'); this.gui.add(fakePlayer, 'visible').name('Show player'); this.gui.add(ground, 'visible').name('Show ground'); - this.gui.add(grassContainer, 'visible').name('Show grass'); + this.gui.add(grassContainer, 'visible').name('Show props'); this.gui.add(this.parameters, "grassMode", Object.values(EGrassMode)).name("Grass type").onChange(applyGrassMode); } @@ -240,25 +289,32 @@ class TestGrass extends TestBase { this.fakeCamera.getWorldPosition(this.grass3D.playerWorldPosition); this.grass2D.update(); this.grass3D.update(); + this.rocks.update(); const camera = this.parameters.centerOnPlayer ? this.fakeCamera : this.camera; const cameraPosition = camera.getWorldPosition(new THREE.Vector3()); cameraPosition.setY(0); const particlePosition = new THREE.Vector3(); - this.grassParticles.forEach((particle: GrassParticle, index: number) => { - particlePosition.set(particle.position.x, 0, particle.position.y); + const updateParticlesDissolve = (particles: ReadonlyArray, clutters: ReadonlyArray) => { + particles.forEach((particle: GrassParticle, index: number) => { + particlePosition.set(particle.position.x, 0, particle.position.y); + + const shouldBeVisible = cameraPosition.distanceTo(particlePosition) < this.parameters.viewRadius; + if (particle.visible !== shouldBeVisible) { + particle.visible = shouldBeVisible; + particle.lastChangeTimestamp = performance.now(); + } - const shouldBeVisible = cameraPosition.distanceTo(particlePosition) < this.parameters.viewRadius; - if (particle.visible !== shouldBeVisible) { - particle.visible = shouldBeVisible; - particle.lastChangeTimestamp = performance.now(); - } + const particleDissolveRatio = this.getParticleDissolveRatio(particle); + for (const clutter of clutters) { + clutter.setDissolve(index, particleDissolveRatio); + } + }); + }; - const particleDissolveRatio = this.getParticleDissolveRatio(particle); - this.grass2D.setDissolve(index, particleDissolveRatio); - this.grass3D.setDissolve(index, particleDissolveRatio); - }); + updateParticlesDissolve(this.grassParticles, [this.grass2D, this.grass3D]); + updateParticlesDissolve(this.rockParticles, [this.rocks]); } private getParticleDissolveRatio(particle: GrassParticle): number { diff --git a/test/resources/props-rocks.glb b/test/resources/props-rocks.glb new file mode 100644 index 0000000000000000000000000000000000000000..c0e73973e3747cc0437a64861d936fdbfedb7c5b GIT binary patch literal 5448 zcmbVOdvH|s89f9Q> z0!9K&N@zl%RlroDtvu@H-ckcYwY0XK7K8GKoq|%KmQkFPamN1cB^$oX^tQRpOm@!x z?m6c>-*>;vFaFx1g$V!_Jpjxf4dfISOl4A)gO$-U^EHmlj>a60WS zv(;g>ICMt6(P6aMj5deaYPUOWMzd2lj~zN~c8AOEvKdWAlhbCl$_)nC2$_e`Wwn}} z7KhDZw^*z$mq`&AofbRO7|kY^(as*aWa>EbLV@ zbF>qa9QuEB^5694|D?CvnS_(Q@j-oIRz_~dGUdhHNkC_-%(ZfT)R*fE`lIZXn@uL> zFP{|D)F_K6yH$k!@=mfj*v)s^8G-n=-AMHoJ1a(OTg_(c@bVUU32*l@*ar!3wK=V9 zk3O9YWM=2%gZHm_(>t&JvOj;6sD@X3cvImlJM~tt8a`TeTH*Yq=~%BCUfFWRd()8V z$@{#oR}CZMO+LNpad+35*F>#&+=&nPbF1EVU+ZrdwPJV6Aw`=rXQ!wY?>N3y(aM>% z;&`nuE8iWeJ5n6)ap@c4P`@L^XK(z9|J$~)Ub$bb_^WqcRJ8J(|6eTUjB7`V<2mK| zXvNX@K2|cz??@|__wb7arQY~XYQcHeXA9}cE8P2!agtVCKG7f?-ZJ2>?HeO$#bw=z ziuRB&UDArT9-X9U=Onl!tvI`Oo}yjwnM2ZwNB=Qh(LT3fnxqvk4bBv{-)HlF5t%4y z#cgB9E7~{yI#<$)J?CdA+TZT;N}91gP0{X2N|ihs@k2(BqD?HDAZf+&9)2~Y#T(yA zE!cYQK^`!^d9D0oQ4O~jPE$CUUqVvD-L)3}@ZVQ@p0BwN@cD@!nV+=NHxRrH&+pv$D8aRKv5jrwG#Y#31c?pA))2KQ7A8;+zeczxVDuc$cua zwp5^eH4gPr(^Af#vP#kx4s4b}ryD#3$8Ie#c+$V#tmJ(AlL;~L)@JGas)s!U#|Ym; z`7c}Z_@{TD6ermOf_vgj;hdqLzu2B47^GD~&$$7mKdPmC;_qSSBJ<$~eI({1%084s zaE#R&;t(vcx)Q6)b{!yfW2CM`eMnu2`cSP(zcN4GuUvPiUs6lxdZ~avSQ`}7J72np3nGt zOiccjv3w@;jx&Dp$v?O#zx9aN&ffBDskZebuUih`LXTfsZs`{<9J}SAnVG+r`HRe7=^>tt<&jt(p5>9$w6sU+ z!!iggC(m+9EQ7!@h%AFd`=wgiJLR*w0?W*^%&|+Y=GefRsBm#kJ5OiwL{q!?4%^}F zrJ1~1?=N&X#D8=&3Y{~ZBIVN_)n-Nex_@pKzmwH!wzrG4N9s>KDSuu{JHKgdRJhhO zJ4Ur?{Zc-i&q!vXR+r9@W~MWd|F+|I!KkxGyyHHq6n+u7%KtiYMf}aKYGJItB}O^3 zQs48eUA#uPe*L^iIrNX!iIur)gi(f;G&OC{`M%x~mcMP&E8;M|VtV_YrL`!Z)}`la zKJ@;jcPRByyK~hz)JIKAIioNRcQdomxCeLPUW~z5j7I_{U@{)W{kRWLU=kk2bR=R1 zI7~z`X5#^*AQKN^3R3YX9)TH;V!Lnc07xncn&XP7yI0WZFmvi$4mGT4&jG5fP>hNcI?GIwBg6-WS^bbgAV)v ZUHB Date: Wed, 1 Jan 2025 18:03:23 +0100 Subject: [PATCH 13/31] perf: handle transition GPU-side --- src/lib/effects/grass/grass-patches.ts | 121 ++++++++++++------------- src/test/test-grass.ts | 106 ++++++---------------- test/resources/grass-2d-cartoon.png | Bin 0 -> 254041 bytes test/resources/props-rocks.glb | Bin 5448 -> 4824 bytes 4 files changed, 87 insertions(+), 140 deletions(-) create mode 100644 test/resources/grass-2d-cartoon.png diff --git a/src/lib/effects/grass/grass-patches.ts b/src/lib/effects/grass/grass-patches.ts index d5f01d47..60d0d54e 100644 --- a/src/lib/effects/grass/grass-patches.ts +++ b/src/lib/effects/grass/grass-patches.ts @@ -1,15 +1,12 @@ import { applyReplacements } from '../../helpers/string'; import * as THREE from '../../libs/three-usage'; -const params = { - minDissolve: 0, -}; - type ClutterMaterial = { readonly material: THREE.MeshPhongMaterial; readonly uniforms: { uPlayerModelPosition: THREE.IUniform; - uDissolveThreshold: THREE.IUniform; + uViewRadius: THREE.IUniform; + uViewRadiusMargin: THREE.IUniform; }; }; @@ -35,12 +32,12 @@ function customizeMaterial(phongMaterial: THREE.MeshPhongMaterial, playerReactiv noiseTexture.wrapS = THREE.RepeatWrapping; noiseTexture.wrapT = THREE.RepeatWrapping; noiseTexture.magFilter = THREE.LinearFilter; - const dissolveUniform: THREE.IUniform = { value: params.minDissolve }; const customUniforms = { uNoiseTexture: { value: noiseTexture }, - uDissolveThreshold: dissolveUniform, uPlayerModelPosition: { value: new THREE.Vector3(Infinity, Infinity, Infinity) }, + uViewRadius: { value: 10 }, + uViewRadiusMargin: { value: 2 }, }; phongMaterial.onBeforeCompile = parameters => { @@ -49,60 +46,66 @@ function customizeMaterial(phongMaterial: THREE.MeshPhongMaterial, playerReactiv ...customUniforms, }; + parameters.defines = parameters.defines || {}; + const playerReactiveKey = "PLAYER_REACTIVE"; + if (playerReactive) { + parameters.defines[playerReactiveKey] = true; + } + parameters.vertexShader = applyReplacements(parameters.vertexShader, { 'void main() {': ` - + #ifdef ${playerReactiveKey} uniform vec3 uPlayerModelPosition; + #endif + + uniform float uViewRadius; + uniform float uViewRadiusMargin; - in float aDissolveRatio; out float vDissolveRatio; void main() { - vDissolveRatio = aDissolveRatio; `, - }); + // https://github.com/mrdoob/three.js/blob/dev/src/renderers/shaders/ShaderChunk/project_vertex.glsl.js + "#include ": ` + vec4 mvPosition = vec4( transformed, 1.0 ); - if (playerReactive) { - parameters.vertexShader = applyReplacements(parameters.vertexShader, { - // https://github.com/mrdoob/three.js/blob/dev/src/renderers/shaders/ShaderChunk/project_vertex.glsl.js - "#include ": ` - vec4 mvPosition = vec4( transformed, 1.0 ); - - #ifdef USE_BATCHING - mvPosition = batchingMatrix * mvPosition; - #endif - - #ifdef USE_INSTANCING - mvPosition = instanceMatrix * mvPosition; - #endif - - vec3 fromPlayer = mvPosition.xyz - uPlayerModelPosition; - float fromPlayerLength = length(fromPlayer) + 0.00001; - const float playerRadius = 0.6; - vec3 displacement = fromPlayer / fromPlayerLength * (playerRadius - fromPlayerLength) - * step(fromPlayerLength, playerRadius) * step(0.2, mvPosition.y); - mvPosition.xz += displacement.xz; - - mvPosition = modelViewMatrix * mvPosition; - - gl_Position = projectionMatrix * mvPosition; + #ifdef USE_BATCHING + mvPosition = batchingMatrix * mvPosition; + #endif + + #ifdef USE_INSTANCING + mvPosition = instanceMatrix * mvPosition; + #endif + + #ifdef ${playerReactiveKey} + vec3 fromPlayer = mvPosition.xyz - uPlayerModelPosition; + float fromPlayerLength = length(fromPlayer) + 0.00001; + const float playerRadius = 0.6; + vec3 displacement = fromPlayer / fromPlayerLength * (playerRadius - fromPlayerLength) + * step(fromPlayerLength, playerRadius) * step(0.2, mvPosition.y); + mvPosition.xz += displacement.xz; + #endif + + mvPosition = modelViewMatrix * mvPosition; + + vDissolveRatio = smoothstep(uViewRadius - uViewRadiusMargin, uViewRadius, length(mvPosition.xyz)); + + gl_Position = projectionMatrix * mvPosition; `, - }); - } + }); parameters.fragmentShader = applyReplacements(parameters.fragmentShader, { 'void main() {': ` - uniform sampler2D uNoiseTexture; - uniform float uDissolveThreshold; - - in float vDissolveRatio; - - void main() { - float noise = texture(uNoiseTexture, gl_FragCoord.xy / ${noiseTextureSize.toFixed(1)}).r; - if (noise < max(vDissolveRatio,uDissolveThreshold)) { - discard; - } - `, + uniform sampler2D uNoiseTexture; + + in float vDissolveRatio; + + void main() { + float noise = texture(uNoiseTexture, gl_FragCoord.xy / ${noiseTextureSize.toFixed(1)}).r; + if (noise < vDissolveRatio) { + discard; + } + `, }); }; return { @@ -123,17 +126,12 @@ class GrassPatchesBatch { return this.instancedMesh; } - public minDissolve: number = 0; public readonly playerWorldPosition = new THREE.Vector3(); private readonly instancedMesh: THREE.InstancedMesh; private readonly material: ClutterMaterial; - private readonly dissolveAttribute: THREE.InstancedBufferAttribute; public constructor(params: Paramerers) { - this.dissolveAttribute = new THREE.InstancedBufferAttribute(new Float32Array(params.count), 1); - params.bufferGeometry.setAttribute('aDissolveRatio', this.dissolveAttribute); - this.material = customizeMaterial(params.material, params.reactToPlayer); this.instancedMesh = new THREE.InstancedMesh(params.bufferGeometry, this.material.material, params.count); this.instancedMesh.count = params.count; @@ -143,18 +141,19 @@ class GrassPatchesBatch { this.material.uniforms.uPlayerModelPosition.value.copy(this.playerWorldPosition).applyMatrix4( this.object3D.matrixWorld.clone().invert() ); - - this.material.uniforms.uDissolveThreshold.value = this.minDissolve; - } - - public setDissolve(index: number, dissolveRatio: number): void { - this.dissolveAttribute.array[index] = dissolveRatio; - this.dissolveAttribute.needsUpdate = true; } public setMatrix(index: number, matrix: THREE.Matrix4): void { this.instancedMesh.setMatrixAt(index, matrix); } + + public setViewDistance(distance: number): void { + this.material.uniforms.uViewRadius.value = distance; + } + public setViewDistanceMargin(margin: number): void { + this.material.uniforms.uViewRadiusMargin.value = margin; + } } -export { GrassPatchesBatch, params }; +export { GrassPatchesBatch }; + diff --git a/src/test/test-grass.ts b/src/test/test-grass.ts index bb95958f..0640e9af 100644 --- a/src/test/test-grass.ts +++ b/src/test/test-grass.ts @@ -6,12 +6,6 @@ import { GrassPatchesBatch } from '../lib'; import { RepeatableBluenoise } from './map/repeatable-bluenoise'; import { TestBase } from './test-base'; -type GrassParticle = { - readonly position: THREE.Vector2Like; - visible: boolean; - lastChangeTimestamp: number; -}; - type ClutterDefinition = { readonly bufferGeometry: THREE.BufferGeometry; readonly material: THREE.MeshPhongMaterial; @@ -41,7 +35,7 @@ async function getGrass2D(gltfLoader: THREE.GLTFLoader): Promise; - private readonly rocks: GrassPatchesBatch; - private readonly rockParticles: ReadonlyArray; private readonly fakeCamera: THREE.Object3D; private readonly parameters = { viewRadius: 20, - transitionTime: 0.25, - centerOnPlayer: true, + viewRadiusMargin: 2, grassMode: EGrassMode.GRASS_2D, }; @@ -174,29 +164,22 @@ class TestGrass extends TestBase { this.scene.add(grassContainer); const allGrassParticlesPositions = params.repartitions.bluenoise.getAllItems({ x: -100, y: -100 }, { x: 100, y: 100 }); - this.grassParticles = allGrassParticlesPositions.map(grassParticle => { - return { - position: grassParticle.position, - visible: false, - lastChangeTimestamp: -Infinity, - } - }); - console.log(`${this.grassParticles.length} grass items`); + console.log(`${allGrassParticlesPositions.length} grass items`); this.grass2D = new GrassPatchesBatch({ - count: this.grassParticles.length, + count: allGrassParticlesPositions.length, bufferGeometry: params.propDefinitions.grass2D.bufferGeometry, material: params.propDefinitions.grass2D.material, reactToPlayer: true, }); grassContainer.add(this.grass2D.object3D); this.grass3D = new GrassPatchesBatch({ - count: this.grassParticles.length, + count: allGrassParticlesPositions.length, bufferGeometry: params.propDefinitions.grass3D.bufferGeometry, material: params.propDefinitions.grass3D.material, reactToPlayer: true, }); grassContainer.add(this.grass3D.object3D); - this.grassParticles.forEach((particle: GrassParticle, index: number) => { + allGrassParticlesPositions.forEach((particle: { position: THREE.Vector2Like }, index: number) => { const matrix = new THREE.Matrix4().multiplyMatrices( new THREE.Matrix4().makeTranslation(new THREE.Vector3(particle.position.x, 0, particle.position.y)), new THREE.Matrix4().makeRotationY(Math.PI / 2 * Math.random()),//Math.floor(4 * Math.random())), @@ -206,25 +189,18 @@ class TestGrass extends TestBase { }); const allRockParticlesPositions = params.repartitions.whitenoise.getAllItems({ x: -100, y: -100 }, { x: 100, y: 100 }); - this.rockParticles = allRockParticlesPositions.map(rockParticle => { - return { - position: rockParticle.position, - visible: false, - lastChangeTimestamp: -Infinity, - } - }); - console.log(`${this.rockParticles.length} rock items`); + console.log(`${allRockParticlesPositions.length} rock items`); this.rocks = new GrassPatchesBatch({ - count: this.rockParticles.length, + count: allRockParticlesPositions.length, bufferGeometry: params.propDefinitions.rocks.bufferGeometry, material: params.propDefinitions.rocks.material, reactToPlayer: false, }); grassContainer.add(this.rocks.object3D); - this.rockParticles.forEach((particle: GrassParticle, index: number) => { + allRockParticlesPositions.forEach((particle: { position: THREE.Vector2Like }, index: number) => { const matrix = new THREE.Matrix4().multiplyMatrices( new THREE.Matrix4().makeTranslation(new THREE.Vector3(particle.position.x, 0, particle.position.y)), - new THREE.Matrix4().makeRotationY(Math.PI / 2 * Math.random()),//Math.floor(4 * Math.random())), + new THREE.Matrix4().makeRotationY(Math.PI / 2 * Math.random()), ); this.rocks.setMatrix(index, matrix); }); @@ -272,12 +248,24 @@ class TestGrass extends TestBase { }; applyGrassMode(); + const applyViewDistance = () => { + this.grass2D.setViewDistance(this.parameters.viewRadius); + this.grass3D.setViewDistance(this.parameters.viewRadius); + this.rocks.setViewDistance(this.parameters.viewRadius); + }; + applyViewDistance(); + + const applyViewDistanceMargin = () => { + this.grass2D.setViewDistanceMargin(this.parameters.viewRadiusMargin); + this.grass3D.setViewDistanceMargin(this.parameters.viewRadiusMargin); + this.rocks.setViewDistanceMargin(this.parameters.viewRadiusMargin); + }; + applyViewDistanceMargin(); + this.gui = new GUI(); this.gui.show(); - this.gui.add(this.grass2D, 'minDissolve', 0, 1, 0.01).name('Min dissolve'); - this.gui.add(this.parameters, 'transitionTime', 0, 2, 0.01).name('Transition time'); - this.gui.add(this.parameters, 'viewRadius', 0, 200, 1).name('View distance'); - this.gui.add(this.parameters, 'centerOnPlayer').name('Center view on player'); + this.gui.add(this.parameters, 'viewRadius', 0, 200, 1).name('View distance').onChange(applyViewDistance); + this.gui.add(this.parameters, 'viewRadiusMargin', 0, 50, 0.1).name('View distance margin').onChange(applyViewDistanceMargin); this.gui.add(fakePlayer, 'visible').name('Show player'); this.gui.add(ground, 'visible').name('Show ground'); this.gui.add(grassContainer, 'visible').name('Show props'); @@ -287,50 +275,10 @@ class TestGrass extends TestBase { protected override update(): void { this.fakeCamera.getWorldPosition(this.grass2D.playerWorldPosition); this.fakeCamera.getWorldPosition(this.grass3D.playerWorldPosition); + this.grass2D.update(); this.grass3D.update(); this.rocks.update(); - - const camera = this.parameters.centerOnPlayer ? this.fakeCamera : this.camera; - const cameraPosition = camera.getWorldPosition(new THREE.Vector3()); - cameraPosition.setY(0); - - const particlePosition = new THREE.Vector3(); - const updateParticlesDissolve = (particles: ReadonlyArray, clutters: ReadonlyArray) => { - particles.forEach((particle: GrassParticle, index: number) => { - particlePosition.set(particle.position.x, 0, particle.position.y); - - const shouldBeVisible = cameraPosition.distanceTo(particlePosition) < this.parameters.viewRadius; - if (particle.visible !== shouldBeVisible) { - particle.visible = shouldBeVisible; - particle.lastChangeTimestamp = performance.now(); - } - - const particleDissolveRatio = this.getParticleDissolveRatio(particle); - for (const clutter of clutters) { - clutter.setDissolve(index, particleDissolveRatio); - } - }); - }; - - updateParticlesDissolve(this.grassParticles, [this.grass2D, this.grass3D]); - updateParticlesDissolve(this.rockParticles, [this.rocks]); - } - - private getParticleDissolveRatio(particle: GrassParticle): number { - if (this.parameters.transitionTime <= 0) { - return 1 - +particle.visible; - } - - const transitionStep = (0.001 * (performance.now() - particle.lastChangeTimestamp)) / this.parameters.transitionTime; - - if (transitionStep <= 0) { - return particle.visible ? 1 : 0; - } else if (transitionStep >= 1) { - return particle.visible ? 0 : 1; - } else { - return particle.visible ? 1 - transitionStep : transitionStep; - } } } diff --git a/test/resources/grass-2d-cartoon.png b/test/resources/grass-2d-cartoon.png new file mode 100644 index 0000000000000000000000000000000000000000..6f1949493b4ef3666725861a3b5706628dbdba95 GIT binary patch literal 254041 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 JPDHLkV1nQ=;z0la literal 0 HcmV?d00001 diff --git a/test/resources/props-rocks.glb b/test/resources/props-rocks.glb index c0e73973e3747cc0437a64861d936fdbfedb7c5b..04fb06e4a1e59ae2f3a08e6a274953a5e8d4bb57 100644 GIT binary patch delta 535 zcmYk0>vjuK0ENFyN{AAwN?mJc(WG(a%$%7sDMeeQF0D&ls)CfHLR*&xDI(OZ?sVLq z!dg~e!Xxwz{PhS%fBCc5UTg3Dt^KQYt~ow3I?!VPb9F!CU6GuUDRV4WHZwya?I~Ld zM@dIno>0D>5^3QHTM6Y!$91(5Qm5KPT1x3_^h{0N-)RLfvCYEBdm6dEG#cFn7v7VhMHX-rY!e)G0X{H^)b~4zc(d?m%9`>=j z$|?48fTQfxl55e6O40>5|4PnL!R-H$5gnFe!>HuGZpF-WvWc_luvx)9iREi VN8a*^_q^c?Goj8f%?Dl+@E3cLXF~u0 delta 1157 zcmb`ES4@*p6o5Z|K(sDULB)kd98loz-+vW_YF#)$MO3CT1VjM`pr~No8%2v-aVsu} zA!;&E({v)$qBC2V8YG#`lJl%!jqrfU zShTgKWKn5pbxDme$y@p;#0^u$KMS%X$FUiweK0kY# z9^mrFIq!PCPR7$mo&NZ4@pG^9s;<4$ALliFNRH}Fv`+TH0%{>B{zfl(ojCr7hw zp0Spf30?lUPg{=HX+HJc<&Q(hWqO_3;BPK}e7if#=9y$@aj)waJA4x_yMNq$c6NE< z4^xRPz2kk#mDbmtsga3Y%{@lj{jZN*p4xAx-u^PV@mi2=K}ELB*Z1f6_Nljz|NCPf zsl#E24sqs$863XdeT4rw%|C~6cvla=K(KqaK>83wKSJnBe*^{)Mg&6`#6$)&f>DGr z8Y2UVB8G6LU}rc(iDe9tNK9ZX8j~20m0?WB#7Ghl8HWX(XcUr}%v5F&M+!4Z#1YSI zRLsm}8ZvXRk;E((u!s!Om`5QCSxPpktRRp16mvVGq@;ThDH4*@%N}Y-T5iImtnqaB`GG?5B|f zoZy%{9%C;@*vD}$ah-Eq;Tjh?%PG!thO4x=V++k(;53)HNgH>#!ENr+$^-6kpLTBX E6Ts?(KL7v# From f0a86373bd097d7359a096d741e8279e8489504f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Piellard?= Date: Wed, 1 Jan 2025 18:09:18 +0100 Subject: [PATCH 14/31] refactor: renamings --- .../grass-patches.ts => props/props-batch.ts} | 10 ++--- src/lib/index.ts | 9 +++-- src/test/test-grass.ts | 40 +++++++++---------- 3 files changed, 30 insertions(+), 29 deletions(-) rename src/lib/effects/{grass/grass-patches.ts => props/props-batch.ts} (96%) diff --git a/src/lib/effects/grass/grass-patches.ts b/src/lib/effects/props/props-batch.ts similarity index 96% rename from src/lib/effects/grass/grass-patches.ts rename to src/lib/effects/props/props-batch.ts index 60d0d54e..4c04d989 100644 --- a/src/lib/effects/grass/grass-patches.ts +++ b/src/lib/effects/props/props-batch.ts @@ -1,7 +1,7 @@ import { applyReplacements } from '../../helpers/string'; import * as THREE from '../../libs/three-usage'; -type ClutterMaterial = { +type PropsMaterial = { readonly material: THREE.MeshPhongMaterial; readonly uniforms: { uPlayerModelPosition: THREE.IUniform; @@ -24,7 +24,7 @@ function buildNoiseTexture(resolution: number): THREE.DataTexture { return texture; } -function customizeMaterial(phongMaterial: THREE.MeshPhongMaterial, playerReactive: boolean): ClutterMaterial { +function customizeMaterial(phongMaterial: THREE.MeshPhongMaterial, playerReactive: boolean): PropsMaterial { phongMaterial.customProgramCacheKey = () => `prop_phong_material`; const noiseTextureSize = 64; @@ -121,7 +121,7 @@ type Paramerers = { readonly material: THREE.MeshPhongMaterial; }; -class GrassPatchesBatch { +class PropsBatch { public get object3D() { return this.instancedMesh; } @@ -129,7 +129,7 @@ class GrassPatchesBatch { public readonly playerWorldPosition = new THREE.Vector3(); private readonly instancedMesh: THREE.InstancedMesh; - private readonly material: ClutterMaterial; + private readonly material: PropsMaterial; public constructor(params: Paramerers) { this.material = customizeMaterial(params.material, params.reactToPlayer); @@ -155,5 +155,5 @@ class GrassPatchesBatch { } } -export { GrassPatchesBatch }; +export { PropsBatch }; diff --git a/src/lib/index.ts b/src/lib/index.ts index 48cb07ee..d8ec946c 100644 --- a/src/lib/index.ts +++ b/src/lib/index.ts @@ -13,11 +13,11 @@ export { type IVoxelMap, type IVoxelMaterial, type VoxelsChunkOrdering, - type VoxelsChunkSize, + type VoxelsChunkSize } from './terrain/voxelmap/i-voxelmap'; export { VoxelmapViewerAutonomous, - type VoxelmapViewerAutonomousOptions, + type VoxelmapViewerAutonomousOptions } from './terrain/voxelmap/viewer/autonomous/voxelmap-viewer-autonomous'; export { EComputationMethod, @@ -25,7 +25,7 @@ export { type ComputationOptions, type ComputationStatus, type VoxelmapViewerOptions, - type VoxelsChunkData, + type VoxelsChunkData } from './terrain/voxelmap/viewer/simple/voxelmap-viewer'; export { VoxelmapVisibilityComputer } from './terrain/voxelmap/voxelmap-visibility-computer'; export { EVoxelsDisplayMode } from './terrain/voxelmap/voxelsRenderable/voxels-material'; @@ -44,4 +44,5 @@ export { GpuInstancedBillboard } from './effects/weather/weather-particles-base' export { CustomizableTexture } from './helpers/customizable-texture'; -export { GrassPatchesBatch } from './effects/grass/grass-patches'; +export { PropsBatch } from './effects/props/props-batch'; + diff --git a/src/test/test-grass.ts b/src/test/test-grass.ts index 0640e9af..6337cc26 100644 --- a/src/test/test-grass.ts +++ b/src/test/test-grass.ts @@ -1,12 +1,12 @@ import GUI from 'lil-gui'; import * as THREE from 'three-usage-test'; -import { GrassPatchesBatch } from '../lib'; +import { PropsBatch } from '../lib'; import { RepeatableBluenoise } from './map/repeatable-bluenoise'; import { TestBase } from './test-base'; -type ClutterDefinition = { +type PropsDefinition = { readonly bufferGeometry: THREE.BufferGeometry; readonly material: THREE.MeshPhongMaterial; }; @@ -25,7 +25,7 @@ function extractBufferGeometry(object: THREE.Object3D): THREE.BufferGeometry { return bufferGeometry; } -async function getGrass2D(gltfLoader: THREE.GLTFLoader): Promise { +async function getGrass2D(gltfLoader: THREE.GLTFLoader): Promise { const glb = await gltfLoader.loadAsync('resources/grass-2d.glb'); const bufferGeometry = extractBufferGeometry(glb.scene); @@ -40,14 +40,14 @@ async function getGrass2D(gltfLoader: THREE.GLTFLoader): Promise { +async function getGrass3D(gltfLoader: THREE.GLTFLoader): Promise { const glb = await gltfLoader.loadAsync('resources/grass-3d.glb'); const bufferGeometry = extractBufferGeometry(glb.scene); const material = new THREE.MeshPhongMaterial({ color: 0xffffff }); return { bufferGeometry, material }; } -async function getRocks(gltfLoader: THREE.GLTFLoader): Promise { +async function getRocks(gltfLoader: THREE.GLTFLoader): Promise { const glb = await gltfLoader.loadAsync('resources/props-rocks.glb'); const bufferGeometry = extractBufferGeometry(glb.scene); const material = new THREE.MeshPhongMaterial({ color: 0xdddddd }); @@ -66,9 +66,9 @@ interface IRepartition { type Parameters = { readonly propDefinitions: { - readonly grass2D: ClutterDefinition; - readonly grass3D: ClutterDefinition; - readonly rocks: ClutterDefinition; + readonly grass2D: PropsDefinition; + readonly grass3D: PropsDefinition; + readonly rocks: PropsDefinition; }; readonly repartitions: { readonly bluenoise: IRepartition; @@ -79,9 +79,9 @@ type Parameters = { class TestGrass extends TestBase { private readonly gui: GUI; - private readonly grass2D: GrassPatchesBatch; - private readonly grass3D: GrassPatchesBatch; - private readonly rocks: GrassPatchesBatch; + private readonly grass2D: PropsBatch; + private readonly grass3D: PropsBatch; + private readonly rocks: PropsBatch; private readonly fakeCamera: THREE.Object3D; @@ -160,25 +160,25 @@ class TestGrass extends TestBase { const ambientLight = new THREE.AmbientLight(0xffffff); this.scene.add(ambientLight); - const grassContainer = new THREE.Object3D(); - this.scene.add(grassContainer); + const propsContainer = new THREE.Object3D(); + this.scene.add(propsContainer); const allGrassParticlesPositions = params.repartitions.bluenoise.getAllItems({ x: -100, y: -100 }, { x: 100, y: 100 }); console.log(`${allGrassParticlesPositions.length} grass items`); - this.grass2D = new GrassPatchesBatch({ + this.grass2D = new PropsBatch({ count: allGrassParticlesPositions.length, bufferGeometry: params.propDefinitions.grass2D.bufferGeometry, material: params.propDefinitions.grass2D.material, reactToPlayer: true, }); - grassContainer.add(this.grass2D.object3D); - this.grass3D = new GrassPatchesBatch({ + propsContainer.add(this.grass2D.object3D); + this.grass3D = new PropsBatch({ count: allGrassParticlesPositions.length, bufferGeometry: params.propDefinitions.grass3D.bufferGeometry, material: params.propDefinitions.grass3D.material, reactToPlayer: true, }); - grassContainer.add(this.grass3D.object3D); + propsContainer.add(this.grass3D.object3D); allGrassParticlesPositions.forEach((particle: { position: THREE.Vector2Like }, index: number) => { const matrix = new THREE.Matrix4().multiplyMatrices( new THREE.Matrix4().makeTranslation(new THREE.Vector3(particle.position.x, 0, particle.position.y)), @@ -190,13 +190,13 @@ class TestGrass extends TestBase { const allRockParticlesPositions = params.repartitions.whitenoise.getAllItems({ x: -100, y: -100 }, { x: 100, y: 100 }); console.log(`${allRockParticlesPositions.length} rock items`); - this.rocks = new GrassPatchesBatch({ + this.rocks = new PropsBatch({ count: allRockParticlesPositions.length, bufferGeometry: params.propDefinitions.rocks.bufferGeometry, material: params.propDefinitions.rocks.material, reactToPlayer: false, }); - grassContainer.add(this.rocks.object3D); + propsContainer.add(this.rocks.object3D); allRockParticlesPositions.forEach((particle: { position: THREE.Vector2Like }, index: number) => { const matrix = new THREE.Matrix4().multiplyMatrices( new THREE.Matrix4().makeTranslation(new THREE.Vector3(particle.position.x, 0, particle.position.y)), @@ -268,7 +268,7 @@ class TestGrass extends TestBase { this.gui.add(this.parameters, 'viewRadiusMargin', 0, 50, 0.1).name('View distance margin').onChange(applyViewDistanceMargin); this.gui.add(fakePlayer, 'visible').name('Show player'); this.gui.add(ground, 'visible').name('Show ground'); - this.gui.add(grassContainer, 'visible').name('Show props'); + this.gui.add(propsContainer, 'visible').name('Show props'); this.gui.add(this.parameters, "grassMode", Object.values(EGrassMode)).name("Grass type").onChange(applyGrassMode); } From 78c16c7b3525cb5dad85aaf8a840a6576ad26718 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Piellard?= Date: Wed, 1 Jan 2025 21:55:53 +0100 Subject: [PATCH 15/31] feat: organize props per group --- src/lib/effects/props/props-batch.ts | 106 ++++++++++++++++++++++++--- src/lib/index.ts | 7 +- src/test/test-grass.ts | 72 +++++++++--------- 3 files changed, 132 insertions(+), 53 deletions(-) diff --git a/src/lib/effects/props/props-batch.ts b/src/lib/effects/props/props-batch.ts index 4c04d989..15188937 100644 --- a/src/lib/effects/props/props-batch.ts +++ b/src/lib/effects/props/props-batch.ts @@ -1,6 +1,14 @@ +import { logger } from '../../helpers/logger'; import { applyReplacements } from '../../helpers/string'; import * as THREE from '../../libs/three-usage'; +function copyMap(source: ReadonlyMap, destination: Map): void { + destination.clear(); + for (const [key, value] of source.entries()) { + destination.set(key, value); + } +} + type PropsMaterial = { readonly material: THREE.MeshPhongMaterial; readonly uniforms: { @@ -47,7 +55,7 @@ function customizeMaterial(phongMaterial: THREE.MeshPhongMaterial, playerReactiv }; parameters.defines = parameters.defines || {}; - const playerReactiveKey = "PLAYER_REACTIVE"; + const playerReactiveKey = 'PLAYER_REACTIVE'; if (playerReactive) { parameters.defines[playerReactiveKey] = true; } @@ -66,7 +74,7 @@ function customizeMaterial(phongMaterial: THREE.MeshPhongMaterial, playerReactiv void main() { `, // https://github.com/mrdoob/three.js/blob/dev/src/renderers/shaders/ShaderChunk/project_vertex.glsl.js - "#include ": ` + '#include ': ` vec4 mvPosition = vec4( transformed, 1.0 ); #ifdef USE_BATCHING @@ -115,45 +123,119 @@ function customizeMaterial(phongMaterial: THREE.MeshPhongMaterial, playerReactiv } type Paramerers = { - readonly count: number; + readonly maxInstancesCount: number; readonly reactToPlayer: boolean; readonly bufferGeometry: THREE.BufferGeometry; readonly material: THREE.MeshPhongMaterial; }; +type GroupDefinition = { + readonly startIndex: number; + readonly count: number; +}; + class PropsBatch { - public get object3D() { + public get container(): THREE.Object3D { return this.instancedMesh; } public readonly playerWorldPosition = new THREE.Vector3(); + private readonly maxInstancesCount: number; private readonly instancedMesh: THREE.InstancedMesh; + private readonly material: PropsMaterial; + private readonly groupsDefinitions: Map; public constructor(params: Paramerers) { + this.maxInstancesCount = params.maxInstancesCount; + this.material = customizeMaterial(params.material, params.reactToPlayer); - this.instancedMesh = new THREE.InstancedMesh(params.bufferGeometry, this.material.material, params.count); - this.instancedMesh.count = params.count; + this.groupsDefinitions = new Map(); + + this.instancedMesh = new THREE.InstancedMesh(params.bufferGeometry, this.material.material, this.maxInstancesCount); + this.instancedMesh.count = 0; } public update(): void { - this.material.uniforms.uPlayerModelPosition.value.copy(this.playerWorldPosition).applyMatrix4( - this.object3D.matrixWorld.clone().invert() - ); + this.material.uniforms.uPlayerModelPosition.value + .copy(this.playerWorldPosition) + .applyMatrix4(this.instancedMesh.matrixWorld.clone().invert()); } - public setMatrix(index: number, matrix: THREE.Matrix4): void { - this.instancedMesh.setMatrixAt(index, matrix); + public setInstancesGroup(groupName: string, matricesList: ReadonlyArray): void { + if (this.groupsDefinitions.has(groupName)) { + this.groupsDefinitions.delete(groupName); + this.reorderMatricesBuffer(); + } + + if (matricesList.length === 0) { + return; + } + + const spareInstancesLeft = this.spareInstancesLeft; + if (matricesList.length > spareInstancesLeft) { + throw new Error( + `Props batch don't have enough space to store "${matricesList.length}" more instances ("${spareInstancesLeft}" left)` + ); + } + + const newGroup: GroupDefinition = { + startIndex: this.instancedMesh.count, + count: matricesList.length, + }; + this.groupsDefinitions.set(groupName, newGroup); + matricesList.forEach((matrix: THREE.Matrix4, index: number) => { + this.instancedMesh.setMatrixAt(newGroup.startIndex + index, matrix); + }); + this.instancedMesh.count += matricesList.length; + } + + public removeInstancesGroup(groupName: string): void { + if (this.groupsDefinitions.has(groupName)) { + this.groupsDefinitions.delete(groupName); + this.reorderMatricesBuffer(); + } else { + logger.warn(`Unknown props batch group "${groupName}".`); + } } public setViewDistance(distance: number): void { this.material.uniforms.uViewRadius.value = distance; } + public setViewDistanceMargin(margin: number): void { this.material.uniforms.uViewRadiusMargin.value = margin; } + + public get spareInstancesLeft(): number { + return this.maxInstancesCount - this.instancedMesh.count; + } + + private reorderMatricesBuffer(): void { + const reorderedMatrices = new Float32Array(this.instancedMesh.instanceMatrix.array.length); + + let instancesCount = 0; + const newGroupDefinitions = new Map(); + for (const [groupName, groupDefinition] of this.groupsDefinitions.entries()) { + const newGroupDefinition: GroupDefinition = { + startIndex: instancesCount, + count: groupDefinition.count, + }; + newGroupDefinitions.set(groupName, newGroupDefinition); + instancesCount += groupDefinition.count; + + for (let iM = 0; iM < groupDefinition.count; iM++) { + const oldMatrixStart = 16 * (groupDefinition.startIndex + iM); + const matrix = this.instancedMesh.instanceMatrix.array.subarray(oldMatrixStart, oldMatrixStart + 16); + reorderedMatrices.set(matrix, 16 * (newGroupDefinition.startIndex + iM)); + } + } + copyMap(newGroupDefinitions, this.groupsDefinitions); + + this.instancedMesh.instanceMatrix.array.set(reorderedMatrices.subarray(0, 16 * instancesCount), 0); + this.instancedMesh.count = instancesCount; + } } export { PropsBatch }; - diff --git a/src/lib/index.ts b/src/lib/index.ts index d8ec946c..a7a8a935 100644 --- a/src/lib/index.ts +++ b/src/lib/index.ts @@ -13,11 +13,11 @@ export { type IVoxelMap, type IVoxelMaterial, type VoxelsChunkOrdering, - type VoxelsChunkSize + type VoxelsChunkSize, } from './terrain/voxelmap/i-voxelmap'; export { VoxelmapViewerAutonomous, - type VoxelmapViewerAutonomousOptions + type VoxelmapViewerAutonomousOptions, } from './terrain/voxelmap/viewer/autonomous/voxelmap-viewer-autonomous'; export { EComputationMethod, @@ -25,7 +25,7 @@ export { type ComputationOptions, type ComputationStatus, type VoxelmapViewerOptions, - type VoxelsChunkData + type VoxelsChunkData, } from './terrain/voxelmap/viewer/simple/voxelmap-viewer'; export { VoxelmapVisibilityComputer } from './terrain/voxelmap/voxelmap-visibility-computer'; export { EVoxelsDisplayMode } from './terrain/voxelmap/voxelsRenderable/voxels-material'; @@ -45,4 +45,3 @@ export { GpuInstancedBillboard } from './effects/weather/weather-particles-base' export { CustomizableTexture } from './helpers/customizable-texture'; export { PropsBatch } from './effects/props/props-batch'; - diff --git a/src/test/test-grass.ts b/src/test/test-grass.ts index 6337cc26..13586e75 100644 --- a/src/test/test-grass.ts +++ b/src/test/test-grass.ts @@ -29,7 +29,7 @@ async function getGrass2D(gltfLoader: THREE.GLTFLoader): Promise } enum EGrassMode { - GRASS_2D = "2d", - GRASS_3D = "3d", -}; + GRASS_2D = '2d', + GRASS_3D = '3d', +} type PositionsList = { position: THREE.Vector2Like }[]; interface IRepartition { @@ -93,11 +93,7 @@ class TestGrass extends TestBase { public static async instanciate(): Promise { const gltfLoader = new THREE.GLTFLoader(); - const [grass2D, grass3D, rocks] = await Promise.all([ - getGrass2D(gltfLoader), - getGrass3D(gltfLoader), - getRocks(gltfLoader), - ]); + const [grass2D, grass3D, rocks] = await Promise.all([getGrass2D(gltfLoader), getGrass3D(gltfLoader), getRocks(gltfLoader)]); const whitenoiseDensity = 0.5; const whitenoiseRepartition: IRepartition = { @@ -120,22 +116,22 @@ class TestGrass extends TestBase { }, }; - const repeatableBluenoise = new RepeatableBluenoise("seed", 150, 2); + const repeatableBluenoise = new RepeatableBluenoise('seed', 150, 2); const bluenoiseDensity = 2; const bluenoiseScaling = Math.ceil(bluenoiseDensity / 0.6); const bluenoiseRepartition: IRepartition = { getAllItems(from: THREE.Vector2Like, to: THREE.Vector2Like): PositionsList { const rawItems = repeatableBluenoise.getAllItems( new THREE.Vector2().addScaledVector(from, bluenoiseScaling), - new THREE.Vector2().addScaledVector(to, bluenoiseScaling), + new THREE.Vector2().addScaledVector(to, bluenoiseScaling) ); return rawItems.map(item => { return { position: new THREE.Vector2().addScaledVector(item.position, 1 / bluenoiseScaling), }; }); - } - } + }, + }; return new TestGrass({ propDefinitions: { grass2D, grass3D, rocks }, @@ -166,44 +162,44 @@ class TestGrass extends TestBase { const allGrassParticlesPositions = params.repartitions.bluenoise.getAllItems({ x: -100, y: -100 }, { x: 100, y: 100 }); console.log(`${allGrassParticlesPositions.length} grass items`); this.grass2D = new PropsBatch({ - count: allGrassParticlesPositions.length, + maxInstancesCount: allGrassParticlesPositions.length, bufferGeometry: params.propDefinitions.grass2D.bufferGeometry, material: params.propDefinitions.grass2D.material, reactToPlayer: true, }); - propsContainer.add(this.grass2D.object3D); + propsContainer.add(this.grass2D.container); this.grass3D = new PropsBatch({ - count: allGrassParticlesPositions.length, + maxInstancesCount: allGrassParticlesPositions.length, bufferGeometry: params.propDefinitions.grass3D.bufferGeometry, material: params.propDefinitions.grass3D.material, reactToPlayer: true, }); - propsContainer.add(this.grass3D.object3D); - allGrassParticlesPositions.forEach((particle: { position: THREE.Vector2Like }, index: number) => { - const matrix = new THREE.Matrix4().multiplyMatrices( + propsContainer.add(this.grass3D.container); + const grassParticlesMatrices = allGrassParticlesPositions.map(particle => + new THREE.Matrix4().multiplyMatrices( new THREE.Matrix4().makeTranslation(new THREE.Vector3(particle.position.x, 0, particle.position.y)), - new THREE.Matrix4().makeRotationY(Math.PI / 2 * Math.random()),//Math.floor(4 * Math.random())), - ); - this.grass2D.setMatrix(index, matrix); - this.grass3D.setMatrix(index, matrix); - }); + new THREE.Matrix4().makeRotationY((Math.PI / 2) * Math.random()) // Math.floor(4 * Math.random())), + ) + ); + this.grass2D.setInstancesGroup('haha', grassParticlesMatrices); + this.grass3D.setInstancesGroup('haha', grassParticlesMatrices); const allRockParticlesPositions = params.repartitions.whitenoise.getAllItems({ x: -100, y: -100 }, { x: 100, y: 100 }); console.log(`${allRockParticlesPositions.length} rock items`); this.rocks = new PropsBatch({ - count: allRockParticlesPositions.length, + maxInstancesCount: allRockParticlesPositions.length, bufferGeometry: params.propDefinitions.rocks.bufferGeometry, material: params.propDefinitions.rocks.material, reactToPlayer: false, }); - propsContainer.add(this.rocks.object3D); - allRockParticlesPositions.forEach((particle: { position: THREE.Vector2Like }, index: number) => { - const matrix = new THREE.Matrix4().multiplyMatrices( + propsContainer.add(this.rocks.container); + const rockParticlesMatrices = allRockParticlesPositions.map(particle => + new THREE.Matrix4().multiplyMatrices( new THREE.Matrix4().makeTranslation(new THREE.Vector3(particle.position.x, 0, particle.position.y)), - new THREE.Matrix4().makeRotationY(Math.PI / 2 * Math.random()), - ); - this.rocks.setMatrix(index, matrix); - }); + new THREE.Matrix4().makeRotationY((Math.PI / 2) * Math.random()) + ) + ); + this.rocks.setInstancesGroup('haha', rockParticlesMatrices); this.fakeCamera = new THREE.Object3D(); this.fakeCamera.position.set(0, 0.5, 0); @@ -212,7 +208,10 @@ class TestGrass extends TestBase { boardCenterControls.addEventListener('dragging-changed', event => { this.cameraControl.enabled = !event.value; }); - const fakePlayer = new THREE.Mesh(new THREE.SphereGeometry(0.5, 12, 12), new THREE.MeshPhongMaterial({ color: 0xffffff, wireframe: false })); + const fakePlayer = new THREE.Mesh( + new THREE.SphereGeometry(0.5, 12, 12), + new THREE.MeshPhongMaterial({ color: 0xffffff, wireframe: false }) + ); this.fakeCamera.add(fakePlayer); boardCenterControls.attach(this.fakeCamera); this.scene.add(this.fakeCamera); @@ -243,8 +242,8 @@ class TestGrass extends TestBase { this.scene.add(ground); const applyGrassMode = () => { - this.grass2D.object3D.visible = this.parameters.grassMode === EGrassMode.GRASS_2D; - this.grass3D.object3D.visible = this.parameters.grassMode === EGrassMode.GRASS_3D; + this.grass2D.container.visible = this.parameters.grassMode === EGrassMode.GRASS_2D; + this.grass3D.container.visible = this.parameters.grassMode === EGrassMode.GRASS_3D; }; applyGrassMode(); @@ -269,7 +268,7 @@ class TestGrass extends TestBase { this.gui.add(fakePlayer, 'visible').name('Show player'); this.gui.add(ground, 'visible').name('Show ground'); this.gui.add(propsContainer, 'visible').name('Show props'); - this.gui.add(this.parameters, "grassMode", Object.values(EGrassMode)).name("Grass type").onChange(applyGrassMode); + this.gui.add(this.parameters, 'grassMode', Object.values(EGrassMode)).name('Grass type').onChange(applyGrassMode); } protected override update(): void { @@ -283,4 +282,3 @@ class TestGrass extends TestBase { } export { TestGrass }; - From 88be846bcd8050a848139e7eab0c1643fcc2b719 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Piellard?= Date: Wed, 1 Jan 2025 21:57:53 +0100 Subject: [PATCH 16/31] refactor: move code --- src/lib/effects/props/props-batch.ts | 8 +------- src/lib/helpers/misc.ts | 11 +++++++++++ 2 files changed, 12 insertions(+), 7 deletions(-) create mode 100644 src/lib/helpers/misc.ts diff --git a/src/lib/effects/props/props-batch.ts b/src/lib/effects/props/props-batch.ts index 15188937..6c385610 100644 --- a/src/lib/effects/props/props-batch.ts +++ b/src/lib/effects/props/props-batch.ts @@ -1,14 +1,8 @@ import { logger } from '../../helpers/logger'; +import { copyMap } from "../../helpers/misc"; import { applyReplacements } from '../../helpers/string'; import * as THREE from '../../libs/three-usage'; -function copyMap(source: ReadonlyMap, destination: Map): void { - destination.clear(); - for (const [key, value] of source.entries()) { - destination.set(key, value); - } -} - type PropsMaterial = { readonly material: THREE.MeshPhongMaterial; readonly uniforms: { diff --git a/src/lib/helpers/misc.ts b/src/lib/helpers/misc.ts new file mode 100644 index 00000000..caea8af1 --- /dev/null +++ b/src/lib/helpers/misc.ts @@ -0,0 +1,11 @@ +function copyMap(source: ReadonlyMap, destination: Map): void { + destination.clear(); + for (const [key, value] of source.entries()) { + destination.set(key, value); + } +} + +export { + copyMap +}; + From 041294850b9317b760ddc11d167603a4dc253284 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Piellard?= Date: Wed, 1 Jan 2025 22:34:00 +0100 Subject: [PATCH 17/31] fix: fix precision issues with player-grass interactions Compute them in view space to avoid floating precision issues --- src/lib/effects/props/props-batch.ts | 36 +++++++++++++++------------- src/lib/helpers/misc.ts | 5 +--- src/test/test-grass.ts | 9 ++++--- 3 files changed, 24 insertions(+), 26 deletions(-) diff --git a/src/lib/effects/props/props-batch.ts b/src/lib/effects/props/props-batch.ts index 6c385610..519efcaf 100644 --- a/src/lib/effects/props/props-batch.ts +++ b/src/lib/effects/props/props-batch.ts @@ -1,12 +1,12 @@ import { logger } from '../../helpers/logger'; -import { copyMap } from "../../helpers/misc"; +import { copyMap } from '../../helpers/misc'; import { applyReplacements } from '../../helpers/string'; import * as THREE from '../../libs/three-usage'; type PropsMaterial = { readonly material: THREE.MeshPhongMaterial; readonly uniforms: { - uPlayerModelPosition: THREE.IUniform; + uPlayerViewPosition: THREE.IUniform; uViewRadius: THREE.IUniform; uViewRadiusMargin: THREE.IUniform; }; @@ -37,7 +37,7 @@ function customizeMaterial(phongMaterial: THREE.MeshPhongMaterial, playerReactiv const customUniforms = { uNoiseTexture: { value: noiseTexture }, - uPlayerModelPosition: { value: new THREE.Vector3(Infinity, Infinity, Infinity) }, + uPlayerViewPosition: { value: new THREE.Vector3(Infinity, Infinity, Infinity) }, uViewRadius: { value: 10 }, uViewRadiusMargin: { value: 2 }, }; @@ -57,7 +57,7 @@ function customizeMaterial(phongMaterial: THREE.MeshPhongMaterial, playerReactiv parameters.vertexShader = applyReplacements(parameters.vertexShader, { 'void main() {': ` #ifdef ${playerReactiveKey} - uniform vec3 uPlayerModelPosition; + uniform vec3 uPlayerViewPosition; #endif uniform float uViewRadius; @@ -79,17 +79,24 @@ function customizeMaterial(phongMaterial: THREE.MeshPhongMaterial, playerReactiv mvPosition = instanceMatrix * mvPosition; #endif + float canBeDisplaced = step(0.2, mvPosition.y); + + mvPosition = modelViewMatrix * mvPosition; + + vec4 viewX = viewMatrix * vec4(1, 0, 0, 0); + vec4 viewZ = viewMatrix * vec4(0, 0, 1, 0); + #ifdef ${playerReactiveKey} - vec3 fromPlayer = mvPosition.xyz - uPlayerModelPosition; + vec3 fromPlayer = mvPosition.xyz - uPlayerViewPosition; float fromPlayerLength = length(fromPlayer) + 0.00001; const float playerRadius = 0.6; - vec3 displacement = fromPlayer / fromPlayerLength * (playerRadius - fromPlayerLength) - * step(fromPlayerLength, playerRadius) * step(0.2, mvPosition.y); - mvPosition.xz += displacement.xz; + vec3 displacementViewspace = fromPlayer / fromPlayerLength * (playerRadius - fromPlayerLength) + * step(fromPlayerLength, playerRadius) * canBeDisplaced; + mvPosition.xyz += + viewX.xyz * dot(displacementViewspace, viewX.xyz) + + viewZ.xyz * dot(displacementViewspace, viewZ.xyz); #endif - mvPosition = modelViewMatrix * mvPosition; - vDissolveRatio = smoothstep(uViewRadius - uViewRadiusMargin, uViewRadius, length(mvPosition.xyz)); gl_Position = projectionMatrix * mvPosition; @@ -133,7 +140,7 @@ class PropsBatch { return this.instancedMesh; } - public readonly playerWorldPosition = new THREE.Vector3(); + public readonly playerViewPosition = new THREE.Vector3(); private readonly maxInstancesCount: number; private readonly instancedMesh: THREE.InstancedMesh; @@ -145,18 +152,13 @@ class PropsBatch { this.maxInstancesCount = params.maxInstancesCount; this.material = customizeMaterial(params.material, params.reactToPlayer); + this.playerViewPosition = this.material.uniforms.uPlayerViewPosition.value; this.groupsDefinitions = new Map(); this.instancedMesh = new THREE.InstancedMesh(params.bufferGeometry, this.material.material, this.maxInstancesCount); this.instancedMesh.count = 0; } - public update(): void { - this.material.uniforms.uPlayerModelPosition.value - .copy(this.playerWorldPosition) - .applyMatrix4(this.instancedMesh.matrixWorld.clone().invert()); - } - public setInstancesGroup(groupName: string, matricesList: ReadonlyArray): void { if (this.groupsDefinitions.has(groupName)) { this.groupsDefinitions.delete(groupName); diff --git a/src/lib/helpers/misc.ts b/src/lib/helpers/misc.ts index caea8af1..3d94e319 100644 --- a/src/lib/helpers/misc.ts +++ b/src/lib/helpers/misc.ts @@ -5,7 +5,4 @@ function copyMap(source: ReadonlyMap, destination: Map): void } } -export { - copyMap -}; - +export { copyMap }; diff --git a/src/test/test-grass.ts b/src/test/test-grass.ts index 13586e75..55cb86b6 100644 --- a/src/test/test-grass.ts +++ b/src/test/test-grass.ts @@ -272,12 +272,11 @@ class TestGrass extends TestBase { } protected override update(): void { - this.fakeCamera.getWorldPosition(this.grass2D.playerWorldPosition); - this.fakeCamera.getWorldPosition(this.grass3D.playerWorldPosition); + const playerViewPosition = this.fakeCamera.getWorldPosition(new THREE.Vector3()).applyMatrix4(this.camera.matrixWorldInverse); - this.grass2D.update(); - this.grass3D.update(); - this.rocks.update(); + this.grass2D.playerViewPosition.copy(playerViewPosition); + this.grass3D.playerViewPosition.copy(playerViewPosition); + this.rocks.playerViewPosition.copy(playerViewPosition); } } From 3a3a51eb9b6c4187c3b839c75dacfe608fe3e6ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Piellard?= Date: Thu, 2 Jan 2025 18:45:30 +0100 Subject: [PATCH 18/31] feat: support infinite number of props --- src/lib/effects/props/props-batch.ts | 6 +- src/lib/effects/props/props-handler.ts | 157 +++++++++++++++++++++++++ src/lib/index.ts | 2 +- src/test/test-grass.ts | 29 ++--- 4 files changed, 176 insertions(+), 18 deletions(-) create mode 100644 src/lib/effects/props/props-handler.ts diff --git a/src/lib/effects/props/props-batch.ts b/src/lib/effects/props/props-batch.ts index 519efcaf..fa1f3072 100644 --- a/src/lib/effects/props/props-batch.ts +++ b/src/lib/effects/props/props-batch.ts @@ -187,7 +187,7 @@ class PropsBatch { this.instancedMesh.count += matricesList.length; } - public removeInstancesGroup(groupName: string): void { + public deleteInstancesGroup(groupName: string): void { if (this.groupsDefinitions.has(groupName)) { this.groupsDefinitions.delete(groupName); this.reorderMatricesBuffer(); @@ -208,6 +208,10 @@ class PropsBatch { return this.maxInstancesCount - this.instancedMesh.count; } + public dispose(): void { + this.instancedMesh.geometry.dispose(); + } + private reorderMatricesBuffer(): void { const reorderedMatrices = new Float32Array(this.instancedMesh.instanceMatrix.array.length); diff --git a/src/lib/effects/props/props-handler.ts b/src/lib/effects/props/props-handler.ts new file mode 100644 index 00000000..6baefae8 --- /dev/null +++ b/src/lib/effects/props/props-handler.ts @@ -0,0 +1,157 @@ +import * as THREE from '../../libs/three-usage'; + +import { PropsBatch } from './props-batch'; + +type Parameters = { + readonly batchSize?: number; + readonly minGroupPartSize?: number; + readonly reactToPlayer?: boolean; + readonly bufferGeometry: THREE.BufferGeometry; + readonly material: THREE.MeshPhongMaterial; +}; + +class PropsHandler { + public readonly container: THREE.Object3D; + + private readonly batchSize: number; + private readonly minGroupPartSize: number; + + private readonly reactToPlayer: boolean; + private readonly bufferGeometry: THREE.BufferGeometry; + private readonly material: THREE.MeshPhongMaterial; + + private viewDistance: number = 20; + private viewDistanceMargin: number = 2; + private playerViewPosition: THREE.Vector3Like = new THREE.Vector3(Infinity, Infinity, Infinity); + + private readonly batchesPerGroup: Map>; + private batches: PropsBatch[]; + + public constructor(params: Parameters) { + this.container = new THREE.Group(); + this.container.name = 'props-handler'; + + this.batchSize = params.batchSize ?? 20000; + this.minGroupPartSize = params.minGroupPartSize ?? 1000; + + this.reactToPlayer = params.reactToPlayer ?? false; + this.bufferGeometry = params.bufferGeometry; + this.material = params.material; + + if (this.batchSize === 0 || this.minGroupPartSize >= this.batchSize) { + throw new Error(`Invalid parameters: minGroupPartSize="${this.minGroupPartSize}", batchSize="${this.batchSize}"`); + } + + this.batchesPerGroup = new Map(); + this.batches = []; + } + + public setGroup(groupName: string, matricesList: ReadonlyArray): void { + if (this.hasGroup(groupName)) { + this.deleteGroup(groupName); + } + + let remainingMatricesList = matricesList.slice(0); + + const addInstancesToBatch = (batch: PropsBatch, instancesCount: number): void => { + if (instancesCount <= 0) { + return; + } + + const batchMatrices = remainingMatricesList.slice(0, instancesCount); + remainingMatricesList = remainingMatricesList.slice(instancesCount); + + batch.setInstancesGroup(groupName, batchMatrices); + + let batches = this.batchesPerGroup.get(groupName); + if (!batches) { + batches = new Set(); + this.batchesPerGroup.set(groupName, batches); + } + batches.add(batch); + }; + + // First, try to fit this group in existing batches + for (const existingBatch of this.batches) { + let instancesCountForThisBatch = 0; + const freeInstancesCountInBatch = existingBatch.spareInstancesLeft; + if (remainingMatricesList.length < freeInstancesCountInBatch) { + // all the remaining instances fit in this batch + instancesCountForThisBatch = remainingMatricesList.length; + } else { + // not all remaining instances fit in this batch + if (freeInstancesCountInBatch >= this.minGroupPartSize) { + // but we can fit a section of the remaining matrices in this batch + instancesCountForThisBatch = freeInstancesCountInBatch; + } + } + + addInstancesToBatch(existingBatch, instancesCountForThisBatch); + } + + // If there are more matrices to fit, create new batches + while (remainingMatricesList.length > 0) { + const newBatch = new PropsBatch({ + maxInstancesCount: this.batchSize, + reactToPlayer: this.reactToPlayer, + bufferGeometry: this.bufferGeometry, + material: this.material, + }); + newBatch.setViewDistance(this.viewDistance); + newBatch.setViewDistanceMargin(this.viewDistanceMargin); + newBatch.playerViewPosition.copy(this.playerViewPosition); + this.container.add(newBatch.container); + + this.batches.push(newBatch); + const instancesCountForNewBatch = Math.min(newBatch.spareInstancesLeft, remainingMatricesList.length); + addInstancesToBatch(newBatch, instancesCountForNewBatch); + } + } + + public deleteGroup(groupName: string): void { + const batchesForThisGroup = this.batchesPerGroup.get(groupName); + if (!batchesForThisGroup) { + throw new Error(`Unknown props group "${groupName}".`); + } + for (const batch of batchesForThisGroup) { + batch.deleteInstancesGroup(groupName); + } + this.batchesPerGroup.delete(groupName); + } + + public hasGroup(groupName: string): boolean { + return this.batchesPerGroup.has(groupName); + } + + public dispose(): void { + this.batchesPerGroup.clear(); + for (const batch of this.batches) { + batch.dispose(); + } + this.batches = []; + this.container.clear(); + } + + public setViewDistance(distance: number): void { + this.viewDistance = distance; + for (const batch of this.batches) { + batch.setViewDistance(this.viewDistance); + } + } + + public setViewDistanceMargin(margin: number): void { + this.viewDistanceMargin = margin; + for (const batch of this.batches) { + batch.setViewDistanceMargin(this.viewDistanceMargin); + } + } + + public setPlayerViewPosition(playerViewPosition: THREE.Vector3Like): void { + this.playerViewPosition = new THREE.Vector3().copy(playerViewPosition); + for (const batch of this.batches) { + batch.playerViewPosition.copy(this.playerViewPosition); + } + } +} + +export { PropsHandler }; diff --git a/src/lib/index.ts b/src/lib/index.ts index a7a8a935..cbcc56cd 100644 --- a/src/lib/index.ts +++ b/src/lib/index.ts @@ -44,4 +44,4 @@ export { GpuInstancedBillboard } from './effects/weather/weather-particles-base' export { CustomizableTexture } from './helpers/customizable-texture'; -export { PropsBatch } from './effects/props/props-batch'; +export { PropsHandler } from './effects/props/props-handler'; diff --git a/src/test/test-grass.ts b/src/test/test-grass.ts index 55cb86b6..0fd41c92 100644 --- a/src/test/test-grass.ts +++ b/src/test/test-grass.ts @@ -1,7 +1,7 @@ import GUI from 'lil-gui'; import * as THREE from 'three-usage-test'; -import { PropsBatch } from '../lib'; +import { PropsHandler } from '../lib'; import { RepeatableBluenoise } from './map/repeatable-bluenoise'; import { TestBase } from './test-base'; @@ -79,9 +79,9 @@ type Parameters = { class TestGrass extends TestBase { private readonly gui: GUI; - private readonly grass2D: PropsBatch; - private readonly grass3D: PropsBatch; - private readonly rocks: PropsBatch; + private readonly grass2D: PropsHandler; + private readonly grass3D: PropsHandler; + private readonly rocks: PropsHandler; private readonly fakeCamera: THREE.Object3D; @@ -161,15 +161,13 @@ class TestGrass extends TestBase { const allGrassParticlesPositions = params.repartitions.bluenoise.getAllItems({ x: -100, y: -100 }, { x: 100, y: 100 }); console.log(`${allGrassParticlesPositions.length} grass items`); - this.grass2D = new PropsBatch({ - maxInstancesCount: allGrassParticlesPositions.length, + this.grass2D = new PropsHandler({ bufferGeometry: params.propDefinitions.grass2D.bufferGeometry, material: params.propDefinitions.grass2D.material, reactToPlayer: true, }); propsContainer.add(this.grass2D.container); - this.grass3D = new PropsBatch({ - maxInstancesCount: allGrassParticlesPositions.length, + this.grass3D = new PropsHandler({ bufferGeometry: params.propDefinitions.grass3D.bufferGeometry, material: params.propDefinitions.grass3D.material, reactToPlayer: true, @@ -181,13 +179,12 @@ class TestGrass extends TestBase { new THREE.Matrix4().makeRotationY((Math.PI / 2) * Math.random()) // Math.floor(4 * Math.random())), ) ); - this.grass2D.setInstancesGroup('haha', grassParticlesMatrices); - this.grass3D.setInstancesGroup('haha', grassParticlesMatrices); + this.grass2D.setGroup('haha', grassParticlesMatrices); + this.grass3D.setGroup('haha', grassParticlesMatrices); const allRockParticlesPositions = params.repartitions.whitenoise.getAllItems({ x: -100, y: -100 }, { x: 100, y: 100 }); console.log(`${allRockParticlesPositions.length} rock items`); - this.rocks = new PropsBatch({ - maxInstancesCount: allRockParticlesPositions.length, + this.rocks = new PropsHandler({ bufferGeometry: params.propDefinitions.rocks.bufferGeometry, material: params.propDefinitions.rocks.material, reactToPlayer: false, @@ -199,7 +196,7 @@ class TestGrass extends TestBase { new THREE.Matrix4().makeRotationY((Math.PI / 2) * Math.random()) ) ); - this.rocks.setInstancesGroup('haha', rockParticlesMatrices); + this.rocks.setGroup('haha', rockParticlesMatrices); this.fakeCamera = new THREE.Object3D(); this.fakeCamera.position.set(0, 0.5, 0); @@ -274,9 +271,9 @@ class TestGrass extends TestBase { protected override update(): void { const playerViewPosition = this.fakeCamera.getWorldPosition(new THREE.Vector3()).applyMatrix4(this.camera.matrixWorldInverse); - this.grass2D.playerViewPosition.copy(playerViewPosition); - this.grass3D.playerViewPosition.copy(playerViewPosition); - this.rocks.playerViewPosition.copy(playerViewPosition); + this.grass2D.setPlayerViewPosition(playerViewPosition); + this.grass3D.setPlayerViewPosition(playerViewPosition); + this.rocks.setPlayerViewPosition(playerViewPosition); } } From dbf17a1c49c9347019d706689fd63778b759f8bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Piellard?= Date: Thu, 2 Jan 2025 18:49:59 +0100 Subject: [PATCH 19/31] fix: correctly handle empty groups --- src/lib/effects/props/props-handler.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/effects/props/props-handler.ts b/src/lib/effects/props/props-handler.ts index 6baefae8..902cd017 100644 --- a/src/lib/effects/props/props-handler.ts +++ b/src/lib/effects/props/props-handler.ts @@ -50,6 +50,7 @@ class PropsHandler { if (this.hasGroup(groupName)) { this.deleteGroup(groupName); } + this.batchesPerGroup.set(groupName, new Set()); let remainingMatricesList = matricesList.slice(0); @@ -65,8 +66,7 @@ class PropsHandler { let batches = this.batchesPerGroup.get(groupName); if (!batches) { - batches = new Set(); - this.batchesPerGroup.set(groupName, batches); + throw new Error("should not happen"); } batches.add(batch); }; From c44e1ea8e13fdee272ebf49f2d411a27c57ddcde Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Piellard?= Date: Thu, 2 Jan 2025 19:07:15 +0100 Subject: [PATCH 20/31] feat: add memory garbage collecting --- src/lib/effects/props/props-handler.ts | 25 +++++++++++++++++++++++++ src/test/main.ts | 2 ++ 2 files changed, 27 insertions(+) diff --git a/src/lib/effects/props/props-handler.ts b/src/lib/effects/props/props-handler.ts index 902cd017..0d97c57e 100644 --- a/src/lib/effects/props/props-handler.ts +++ b/src/lib/effects/props/props-handler.ts @@ -1,3 +1,4 @@ +import { logger } from '../../helpers/logger'; import * as THREE from '../../libs/three-usage'; import { PropsBatch } from './props-batch'; @@ -132,6 +133,30 @@ class PropsHandler { this.container.clear(); } + public garbageCollect(): void { + const usedBatches = new Set(); + for (const usedBatchesForGroup of this.batchesPerGroup.values()) { + usedBatchesForGroup.forEach(usedBatchForGroup => usedBatches.add(usedBatchForGroup)); + } + + let garbageCollectedBatchesCount = 0; + const usedBatchesList: PropsBatch[] = []; + for (const batch of this.batches) { + if (usedBatches.has(batch)) { + usedBatchesList.push(batch); + } else { + if (batch.spareInstancesLeft !== this.batchSize) { + throw new Error(`No group registered for batch, yet the batch is not empty.`); + } + batch.dispose(); + this.container.remove(batch.container); + garbageCollectedBatchesCount++; + } + } + this.batches = usedBatchesList; + logger.debug(`PropsHandler: garbage collected ${garbageCollectedBatchesCount} batches.`); + } + public setViewDistance(distance: number): void { this.viewDistance = distance; for (const batch of this.batches) { diff --git a/src/test/main.ts b/src/test/main.ts index a774b370..5a115eea 100644 --- a/src/test/main.ts +++ b/src/test/main.ts @@ -1,3 +1,4 @@ +import { logger } from '../lib/helpers/logger'; import { ELogLevel, setVerbosity } from '../lib/index'; import { VoxelMap } from './map/voxel-map'; @@ -48,6 +49,7 @@ async function buildTestScene(test: ETest): Promise { } else if (test === ETest.BOARD) { return new TestBoard(createVoxelMap()); } else if (test === ETest.GRASS) { + logger.verbosity = ELogLevel.DEBUG; return TestGrass.instanciate(); } else { throw new Error(`Unknown test "${test}".`); From 031cae5cd9aa3d52f08438adaa95cfcb46ab68e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Piellard?= Date: Thu, 2 Jan 2025 19:17:08 +0100 Subject: [PATCH 21/31] feat: memory statistics for PropsHandler --- src/lib/effects/props/props-handler.ts | 27 +++++++++++++++++++++++--- src/lib/index.ts | 2 +- src/test/test-grass.ts | 2 -- 3 files changed, 25 insertions(+), 6 deletions(-) diff --git a/src/lib/effects/props/props-handler.ts b/src/lib/effects/props/props-handler.ts index 0d97c57e..80f6cda7 100644 --- a/src/lib/effects/props/props-handler.ts +++ b/src/lib/effects/props/props-handler.ts @@ -3,6 +3,13 @@ import * as THREE from '../../libs/three-usage'; import { PropsBatch } from './props-batch'; +type PropsHandlerStatistics = { + batchesCount: number; + batchesSize: number; + totalInstancesCapacity: number; + totalInstancesUsed: number; +}; + type Parameters = { readonly batchSize?: number; readonly minGroupPartSize?: number; @@ -65,9 +72,9 @@ class PropsHandler { batch.setInstancesGroup(groupName, batchMatrices); - let batches = this.batchesPerGroup.get(groupName); + const batches = this.batchesPerGroup.get(groupName); if (!batches) { - throw new Error("should not happen"); + throw new Error('should not happen'); } batches.add(batch); }; @@ -157,6 +164,20 @@ class PropsHandler { logger.debug(`PropsHandler: garbage collected ${garbageCollectedBatchesCount} batches.`); } + public getStatistics(): PropsHandlerStatistics { + let totalInstancesUsed = 0; + for (const batch of this.batches) { + totalInstancesUsed += this.batchSize - batch.spareInstancesLeft; + } + + return { + batchesCount: this.batches.length, + batchesSize: this.batchSize, + totalInstancesCapacity: this.batches.length * this.batchSize, + totalInstancesUsed, + }; + } + public setViewDistance(distance: number): void { this.viewDistance = distance; for (const batch of this.batches) { @@ -179,4 +200,4 @@ class PropsHandler { } } -export { PropsHandler }; +export { PropsHandler, type PropsHandlerStatistics }; diff --git a/src/lib/index.ts b/src/lib/index.ts index cbcc56cd..3d2daea2 100644 --- a/src/lib/index.ts +++ b/src/lib/index.ts @@ -44,4 +44,4 @@ export { GpuInstancedBillboard } from './effects/weather/weather-particles-base' export { CustomizableTexture } from './helpers/customizable-texture'; -export { PropsHandler } from './effects/props/props-handler'; +export { PropsHandler, type PropsHandlerStatistics } from './effects/props/props-handler'; diff --git a/src/test/test-grass.ts b/src/test/test-grass.ts index 0fd41c92..42f9f47a 100644 --- a/src/test/test-grass.ts +++ b/src/test/test-grass.ts @@ -160,7 +160,6 @@ class TestGrass extends TestBase { this.scene.add(propsContainer); const allGrassParticlesPositions = params.repartitions.bluenoise.getAllItems({ x: -100, y: -100 }, { x: 100, y: 100 }); - console.log(`${allGrassParticlesPositions.length} grass items`); this.grass2D = new PropsHandler({ bufferGeometry: params.propDefinitions.grass2D.bufferGeometry, material: params.propDefinitions.grass2D.material, @@ -183,7 +182,6 @@ class TestGrass extends TestBase { this.grass3D.setGroup('haha', grassParticlesMatrices); const allRockParticlesPositions = params.repartitions.whitenoise.getAllItems({ x: -100, y: -100 }, { x: 100, y: 100 }); - console.log(`${allRockParticlesPositions.length} rock items`); this.rocks = new PropsHandler({ bufferGeometry: params.propDefinitions.rocks.bufferGeometry, material: params.propDefinitions.rocks.material, From 4db8495ed7fbe218f991f7d1c4e7bdf4481f31b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Piellard?= Date: Thu, 2 Jan 2025 19:21:00 +0100 Subject: [PATCH 22/31] feat: memory statistics for PropsBatch --- src/lib/effects/props/props-batch.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/lib/effects/props/props-batch.ts b/src/lib/effects/props/props-batch.ts index fa1f3072..8a3327b2 100644 --- a/src/lib/effects/props/props-batch.ts +++ b/src/lib/effects/props/props-batch.ts @@ -123,6 +123,12 @@ function customizeMaterial(phongMaterial: THREE.MeshPhongMaterial, playerReactiv }; } +type PropsBatchStatistics = { + instancesCapacity: number; + instancesUsed: number; + groupsCount: number; +}; + type Paramerers = { readonly maxInstancesCount: number; readonly reactToPlayer: boolean; @@ -212,6 +218,14 @@ class PropsBatch { this.instancedMesh.geometry.dispose(); } + public getStatistics(): PropsBatchStatistics { + return { + instancesCapacity: this.maxInstancesCount, + instancesUsed: this.instancedMesh.count, + groupsCount: this.groupsDefinitions.size, + }; + } + private reorderMatricesBuffer(): void { const reorderedMatrices = new Float32Array(this.instancedMesh.instanceMatrix.array.length); From bc1f6adb35288860ec1dd1e15008111dc3106456 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Piellard?= Date: Thu, 2 Jan 2025 19:44:34 +0100 Subject: [PATCH 23/31] test: dynamic & infinite grass generation --- src/test/test-grass.ts | 103 +++++++++++++++++++++++++---------------- 1 file changed, 62 insertions(+), 41 deletions(-) diff --git a/src/test/test-grass.ts b/src/test/test-grass.ts index 42f9f47a..b451d259 100644 --- a/src/test/test-grass.ts +++ b/src/test/test-grass.ts @@ -59,9 +59,8 @@ enum EGrassMode { GRASS_3D = '3d', } -type PositionsList = { position: THREE.Vector2Like }[]; interface IRepartition { - getAllItems(from: THREE.Vector2Like, to: THREE.Vector2Like): PositionsList; + getAllItems(from: THREE.Vector2Like, to: THREE.Vector2Like): THREE.Vector2Like[]; } type Parameters = { @@ -83,7 +82,10 @@ class TestGrass extends TestBase { private readonly grass3D: PropsHandler; private readonly rocks: PropsHandler; - private readonly fakeCamera: THREE.Object3D; + readonly grassRepartition: IRepartition; + readonly rocksRepartition: IRepartition; + + private readonly fakePlayer: THREE.Object3D; private readonly parameters = { viewRadius: 20, @@ -97,18 +99,16 @@ class TestGrass extends TestBase { const whitenoiseDensity = 0.5; const whitenoiseRepartition: IRepartition = { - getAllItems(from: THREE.Vector2Like, to: THREE.Vector2Like): PositionsList { - const result: PositionsList = []; + getAllItems(from: THREE.Vector2Like, to: THREE.Vector2Like): THREE.Vector2Like[] { + const result: THREE.Vector2Like[] = []; const areaSize = new THREE.Vector2().subVectors(to, from); const totalArea = areaSize.x * areaSize.y; const totalItemsCount = totalArea * whitenoiseDensity; for (let i = 0; i < totalItemsCount; i++) { result.push({ - position: { - x: from.x + areaSize.x * Math.random(), - y: from.y + areaSize.y * Math.random(), - }, + x: from.x + areaSize.x * Math.random(), + y: from.y + areaSize.y * Math.random(), }); } @@ -120,16 +120,12 @@ class TestGrass extends TestBase { const bluenoiseDensity = 2; const bluenoiseScaling = Math.ceil(bluenoiseDensity / 0.6); const bluenoiseRepartition: IRepartition = { - getAllItems(from: THREE.Vector2Like, to: THREE.Vector2Like): PositionsList { + getAllItems(from: THREE.Vector2Like, to: THREE.Vector2Like): THREE.Vector2Like[] { const rawItems = repeatableBluenoise.getAllItems( new THREE.Vector2().addScaledVector(from, bluenoiseScaling), new THREE.Vector2().addScaledVector(to, bluenoiseScaling) ); - return rawItems.map(item => { - return { - position: new THREE.Vector2().addScaledVector(item.position, 1 / bluenoiseScaling), - }; - }); + return rawItems.map(item => new THREE.Vector2().addScaledVector(item.position, 1 / bluenoiseScaling)); }, }; @@ -159,7 +155,9 @@ class TestGrass extends TestBase { const propsContainer = new THREE.Object3D(); this.scene.add(propsContainer); - const allGrassParticlesPositions = params.repartitions.bluenoise.getAllItems({ x: -100, y: -100 }, { x: 100, y: 100 }); + this.grassRepartition = params.repartitions.bluenoise; + this.rocksRepartition = params.repartitions.whitenoise; + this.grass2D = new PropsHandler({ bufferGeometry: params.propDefinitions.grass2D.bufferGeometry, material: params.propDefinitions.grass2D.material, @@ -172,44 +170,28 @@ class TestGrass extends TestBase { reactToPlayer: true, }); propsContainer.add(this.grass3D.container); - const grassParticlesMatrices = allGrassParticlesPositions.map(particle => - new THREE.Matrix4().multiplyMatrices( - new THREE.Matrix4().makeTranslation(new THREE.Vector3(particle.position.x, 0, particle.position.y)), - new THREE.Matrix4().makeRotationY((Math.PI / 2) * Math.random()) // Math.floor(4 * Math.random())), - ) - ); - this.grass2D.setGroup('haha', grassParticlesMatrices); - this.grass3D.setGroup('haha', grassParticlesMatrices); - const allRockParticlesPositions = params.repartitions.whitenoise.getAllItems({ x: -100, y: -100 }, { x: 100, y: 100 }); this.rocks = new PropsHandler({ bufferGeometry: params.propDefinitions.rocks.bufferGeometry, material: params.propDefinitions.rocks.material, reactToPlayer: false, }); propsContainer.add(this.rocks.container); - const rockParticlesMatrices = allRockParticlesPositions.map(particle => - new THREE.Matrix4().multiplyMatrices( - new THREE.Matrix4().makeTranslation(new THREE.Vector3(particle.position.x, 0, particle.position.y)), - new THREE.Matrix4().makeRotationY((Math.PI / 2) * Math.random()) - ) - ); - this.rocks.setGroup('haha', rockParticlesMatrices); - this.fakeCamera = new THREE.Object3D(); - this.fakeCamera.position.set(0, 0.5, 0); + this.fakePlayer = new THREE.Object3D(); + this.fakePlayer.position.set(0, 0.5, 0); const boardCenterControls = new THREE.TransformControls(this.camera, this.renderer.domElement); boardCenterControls.showY = false; boardCenterControls.addEventListener('dragging-changed', event => { this.cameraControl.enabled = !event.value; }); - const fakePlayer = new THREE.Mesh( + const fakePlayerMesh = new THREE.Mesh( new THREE.SphereGeometry(0.5, 12, 12), new THREE.MeshPhongMaterial({ color: 0xffffff, wireframe: false }) ); - this.fakeCamera.add(fakePlayer); - boardCenterControls.attach(this.fakeCamera); - this.scene.add(this.fakeCamera); + this.fakePlayer.add(fakePlayerMesh); + boardCenterControls.attach(this.fakePlayer); + this.scene.add(this.fakePlayer); this.scene.add(boardCenterControls.getHelper()); const groundSize = 1000; @@ -258,16 +240,55 @@ class TestGrass extends TestBase { this.gui = new GUI(); this.gui.show(); - this.gui.add(this.parameters, 'viewRadius', 0, 200, 1).name('View distance').onChange(applyViewDistance); + this.gui.add(this.parameters, 'viewRadius', 0, 1000, 1).name('View distance').onChange(applyViewDistance); this.gui.add(this.parameters, 'viewRadiusMargin', 0, 50, 0.1).name('View distance margin').onChange(applyViewDistanceMargin); - this.gui.add(fakePlayer, 'visible').name('Show player'); + this.gui.add(fakePlayerMesh, 'visible').name('Show player'); this.gui.add(ground, 'visible').name('Show ground'); this.gui.add(propsContainer, 'visible').name('Show props'); this.gui.add(this.parameters, 'grassMode', Object.values(EGrassMode)).name('Grass type').onChange(applyGrassMode); } protected override update(): void { - const playerViewPosition = this.fakeCamera.getWorldPosition(new THREE.Vector3()).applyMatrix4(this.camera.matrixWorldInverse); + const cameraWorldPosition3d = this.camera.getWorldPosition(new THREE.Vector3()); + + const patchSize = 64; + const fromPatch = new THREE.Vector2(cameraWorldPosition3d.x, cameraWorldPosition3d.z).subScalar(this.parameters.viewRadius).divideScalar(patchSize).floor(); + const toPatch = new THREE.Vector2(cameraWorldPosition3d.x, cameraWorldPosition3d.z).addScalar(this.parameters.viewRadius).divideScalar(patchSize).floor(); + + for (let iX = fromPatch.x; iX <= toPatch.x; iX++) { + for (let iY = fromPatch.y; iY <= toPatch.y; iY++) { + if ((iX + iY) % 2 === 0) { + continue; + } + + const patchId = `${iX}_${iY}`; + if (!this.grass2D.hasGroup(patchId)) { + const patchStart = new THREE.Vector2(iX, iY).multiplyScalar(patchSize); + const patchEnd = new THREE.Vector2(iX, iY).addScalar(1).multiplyScalar(patchSize); + + const grassParticlesPositions = this.grassRepartition.getAllItems(patchStart, patchEnd); + const grassParticlesMatrices = grassParticlesPositions.map(position => + new THREE.Matrix4().multiplyMatrices( + new THREE.Matrix4().makeTranslation(new THREE.Vector3(position.x, 0, position.y)), + new THREE.Matrix4().makeRotationY((Math.PI / 2) * Math.random()) // Math.floor(4 * Math.random())), + ) + ); + this.grass2D.setGroup(patchId, grassParticlesMatrices); + this.grass3D.setGroup(patchId, grassParticlesMatrices); + + const rockParticlesPositions = this.rocksRepartition.getAllItems(patchStart, patchEnd); + const rockParticlesMatrices = rockParticlesPositions.map(position => + new THREE.Matrix4().multiplyMatrices( + new THREE.Matrix4().makeTranslation(new THREE.Vector3(position.x, 0, position.y)), + new THREE.Matrix4().makeRotationY((Math.PI / 2) * Math.random()) + ) + ); + this.rocks.setGroup(patchId, rockParticlesMatrices); + } + } + } + + const playerViewPosition = this.fakePlayer.getWorldPosition(new THREE.Vector3()).applyMatrix4(this.camera.matrixWorldInverse); this.grass2D.setPlayerViewPosition(playerViewPosition); this.grass3D.setPlayerViewPosition(playerViewPosition); From 33f303ab70774c89020a0f9cd495832002b8a381 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Piellard?= Date: Thu, 2 Jan 2025 21:24:30 +0100 Subject: [PATCH 24/31] fix: fix bug where some props were not displayed sometimes --- src/lib/effects/props/props-batch.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib/effects/props/props-batch.ts b/src/lib/effects/props/props-batch.ts index 8a3327b2..0fe1547c 100644 --- a/src/lib/effects/props/props-batch.ts +++ b/src/lib/effects/props/props-batch.ts @@ -190,6 +190,7 @@ class PropsBatch { matricesList.forEach((matrix: THREE.Matrix4, index: number) => { this.instancedMesh.setMatrixAt(newGroup.startIndex + index, matrix); }); + this.instancedMesh.instanceMatrix.needsUpdate = true; this.instancedMesh.count += matricesList.length; } From d3dc151605676e1e761080f22a1def7b4c87329a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Piellard?= Date: Thu, 2 Jan 2025 21:25:25 +0100 Subject: [PATCH 25/31] refactor: simplification --- src/lib/effects/props/props-batch.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/lib/effects/props/props-batch.ts b/src/lib/effects/props/props-batch.ts index 0fe1547c..9019a5e3 100644 --- a/src/lib/effects/props/props-batch.ts +++ b/src/lib/effects/props/props-batch.ts @@ -175,10 +175,9 @@ class PropsBatch { return; } - const spareInstancesLeft = this.spareInstancesLeft; - if (matricesList.length > spareInstancesLeft) { + if (matricesList.length > this.spareInstancesLeft) { throw new Error( - `Props batch don't have enough space to store "${matricesList.length}" more instances ("${spareInstancesLeft}" left)` + `Props batch don't have enough space to store "${matricesList.length}" more instances ("${this.spareInstancesLeft}" left)` ); } From 5c4b5bbaea592209050def0e44745d318a413407 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Piellard?= Date: Thu, 2 Jan 2025 21:32:29 +0100 Subject: [PATCH 26/31] fix: fix bad frustum culling from threejs --- src/lib/effects/props/props-batch.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lib/effects/props/props-batch.ts b/src/lib/effects/props/props-batch.ts index 9019a5e3..fe33ccc2 100644 --- a/src/lib/effects/props/props-batch.ts +++ b/src/lib/effects/props/props-batch.ts @@ -190,6 +190,8 @@ class PropsBatch { this.instancedMesh.setMatrixAt(newGroup.startIndex + index, matrix); }); this.instancedMesh.instanceMatrix.needsUpdate = true; + this.instancedMesh.computeBoundingBox(); + this.instancedMesh.computeBoundingSphere(); this.instancedMesh.count += matricesList.length; } From 799355f43961d1a046a06c76d4eb6f54b62e1017 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Piellard?= Date: Sun, 5 Jan 2025 22:34:01 +0100 Subject: [PATCH 27/31] feat: new class PropsViewer --- src/lib/effects/props/props-viewer.ts | 55 +++++++++++++++++++++ src/lib/index.ts | 1 + src/test/test-grass.ts | 69 ++++++++++++++++----------- 3 files changed, 97 insertions(+), 28 deletions(-) create mode 100644 src/lib/effects/props/props-viewer.ts diff --git a/src/lib/effects/props/props-viewer.ts b/src/lib/effects/props/props-viewer.ts new file mode 100644 index 00000000..abfc84ac --- /dev/null +++ b/src/lib/effects/props/props-viewer.ts @@ -0,0 +1,55 @@ +import * as THREE from '../../libs/three-usage'; + +import { PropsHandler } from './props-handler'; + +type Parameters = { + readonly batchSize?: number; + readonly minGroupPartSize?: number; + readonly reactToPlayer?: boolean; + readonly bufferGeometry: THREE.BufferGeometry; + readonly material: THREE.MeshPhongMaterial; + + readonly patchSize: THREE.Vector3Like; +}; + +type PatchId = THREE.Vector3Like; + +function buildPatchIdString(patchId: PatchId): string { + return `${patchId.x}_${patchId.y}_${patchId.z}`; +} + +class PropsViewer extends PropsHandler { + private readonly patchSize: THREE.Vector3Like; + + public constructor(params: Parameters) { + super(params); + + this.patchSize = new THREE.Vector3().copy(params.patchSize); + } + + public setPatchPropsFromLocalMatrices(patchId: THREE.Vector3Like, localMatricesList: ReadonlyArray): void { + const patchWorldOrigin = new THREE.Vector3().multiplyVectors(patchId, this.patchSize); + const patchTransformMatrix = new THREE.Matrix4().makeTranslation(patchWorldOrigin); + const worldMatricesList = localMatricesList.map(localMatrix => + new THREE.Matrix4().multiplyMatrices(patchTransformMatrix, localMatrix) + ); + this.setPatchPropsFromWorldMatrices(patchId, worldMatricesList); + } + + public setPatchPropsFromWorldMatrices(patchId: THREE.Vector3Like, worldMatricesList: ReadonlyArray): void { + const patchIdString = buildPatchIdString(patchId); + this.setGroup(patchIdString, worldMatricesList); + } + + public deletePatchProps(patchId: THREE.Vector3Like): void { + const patchIdString = buildPatchIdString(patchId); + this.deleteGroup(patchIdString); + } + + public hasPatchProps(patchId: THREE.Vector3Like): boolean { + const patchIdString = buildPatchIdString(patchId); + return this.hasGroup(patchIdString); + } +} + +export { PropsViewer }; diff --git a/src/lib/index.ts b/src/lib/index.ts index 3d2daea2..679e173e 100644 --- a/src/lib/index.ts +++ b/src/lib/index.ts @@ -45,3 +45,4 @@ export { GpuInstancedBillboard } from './effects/weather/weather-particles-base' export { CustomizableTexture } from './helpers/customizable-texture'; export { PropsHandler, type PropsHandlerStatistics } from './effects/props/props-handler'; +export { PropsViewer } from './effects/props/props-viewer'; diff --git a/src/test/test-grass.ts b/src/test/test-grass.ts index b451d259..38530771 100644 --- a/src/test/test-grass.ts +++ b/src/test/test-grass.ts @@ -1,7 +1,7 @@ import GUI from 'lil-gui'; import * as THREE from 'three-usage-test'; -import { PropsHandler } from '../lib'; +import { PropsHandler, PropsViewer } from '../lib'; import { RepeatableBluenoise } from './map/repeatable-bluenoise'; import { TestBase } from './test-base'; @@ -78,15 +78,17 @@ type Parameters = { class TestGrass extends TestBase { private readonly gui: GUI; - private readonly grass2D: PropsHandler; - private readonly grass3D: PropsHandler; + private readonly grass2D: PropsViewer; + private readonly grass3D: PropsViewer; private readonly rocks: PropsHandler; - readonly grassRepartition: IRepartition; - readonly rocksRepartition: IRepartition; + private readonly grassRepartition: IRepartition; + private readonly rocksRepartition: IRepartition; private readonly fakePlayer: THREE.Object3D; + private readonly patchSize = 64; + private readonly parameters = { viewRadius: 20, viewRadiusMargin: 2, @@ -158,16 +160,18 @@ class TestGrass extends TestBase { this.grassRepartition = params.repartitions.bluenoise; this.rocksRepartition = params.repartitions.whitenoise; - this.grass2D = new PropsHandler({ + this.grass2D = new PropsViewer({ bufferGeometry: params.propDefinitions.grass2D.bufferGeometry, material: params.propDefinitions.grass2D.material, reactToPlayer: true, + patchSize: new THREE.Vector3(this.patchSize, this.patchSize, this.patchSize), }); propsContainer.add(this.grass2D.container); - this.grass3D = new PropsHandler({ + this.grass3D = new PropsViewer({ bufferGeometry: params.propDefinitions.grass3D.bufferGeometry, material: params.propDefinitions.grass3D.material, reactToPlayer: true, + patchSize: new THREE.Vector3(this.patchSize, this.patchSize, this.patchSize), }); propsContainer.add(this.grass3D.container); @@ -250,40 +254,50 @@ class TestGrass extends TestBase { protected override update(): void { const cameraWorldPosition3d = this.camera.getWorldPosition(new THREE.Vector3()); + const cameraWorldFloorPosition = new THREE.Vector3(cameraWorldPosition3d.x, 0, cameraWorldPosition3d.z); - const patchSize = 64; - const fromPatch = new THREE.Vector2(cameraWorldPosition3d.x, cameraWorldPosition3d.z).subScalar(this.parameters.viewRadius).divideScalar(patchSize).floor(); - const toPatch = new THREE.Vector2(cameraWorldPosition3d.x, cameraWorldPosition3d.z).addScalar(this.parameters.viewRadius).divideScalar(patchSize).floor(); - - for (let iX = fromPatch.x; iX <= toPatch.x; iX++) { - for (let iY = fromPatch.y; iY <= toPatch.y; iY++) { - if ((iX + iY) % 2 === 0) { - continue; - } + const fromPatch = cameraWorldFloorPosition.clone().subScalar(this.parameters.viewRadius).divideScalar(this.patchSize).floor(); + const toPatch = cameraWorldFloorPosition.clone().addScalar(this.parameters.viewRadius).divideScalar(this.patchSize).floor(); - const patchId = `${iX}_${iY}`; - if (!this.grass2D.hasGroup(patchId)) { - const patchStart = new THREE.Vector2(iX, iY).multiplyScalar(patchSize); - const patchEnd = new THREE.Vector2(iX, iY).addScalar(1).multiplyScalar(patchSize); + const patchId = new THREE.Vector3(); + for (patchId.x = fromPatch.x; patchId.x <= toPatch.x; patchId.x++) { + for (patchId.z = fromPatch.z; patchId.z <= toPatch.z; patchId.z++) { + const patchStart = patchId.clone().multiplyScalar(this.patchSize); + const patchEnd = patchStart.clone().addScalar(this.patchSize); - const grassParticlesPositions = this.grassRepartition.getAllItems(patchStart, patchEnd); - const grassParticlesMatrices = grassParticlesPositions.map(position => + if ((patchId.x + patchId.z) % 2 === 0 && !this.grass2D.hasPatchProps(patchId)) { + const grassParticlesPositions = this.grassRepartition.getAllItems( + { x: patchStart.x, y: patchStart.z }, + { x: patchEnd.x, y: patchEnd.z } + ); + const grassParticlesMatricesWorld = grassParticlesPositions.map(position => new THREE.Matrix4().multiplyMatrices( - new THREE.Matrix4().makeTranslation(new THREE.Vector3(position.x, 0, position.y)), + new THREE.Matrix4().makeTranslation(position.x, 0, position.y), new THREE.Matrix4().makeRotationY((Math.PI / 2) * Math.random()) // Math.floor(4 * Math.random())), ) ); - this.grass2D.setGroup(patchId, grassParticlesMatrices); - this.grass3D.setGroup(patchId, grassParticlesMatrices); + this.grass2D.setPatchPropsFromWorldMatrices(patchId, grassParticlesMatricesWorld); - const rockParticlesPositions = this.rocksRepartition.getAllItems(patchStart, patchEnd); + const worldToLocal = new THREE.Matrix4().makeTranslation(-patchStart.x, -patchStart.y, -patchStart.z); + const grassParticlesMatricesLocal = grassParticlesMatricesWorld.map(matrixWorld => + new THREE.Matrix4().multiplyMatrices(worldToLocal, matrixWorld) + ); + this.grass3D.setPatchPropsFromLocalMatrices(patchId, grassParticlesMatricesLocal); + } + + const patchIdString = `${patchId.x}_${patchId.y}_${patchId.z}`; + if (!this.rocks.hasGroup(patchIdString)) { + const rockParticlesPositions = this.rocksRepartition.getAllItems( + { x: patchStart.x, y: patchStart.z }, + { x: patchEnd.x, y: patchEnd.z } + ); const rockParticlesMatrices = rockParticlesPositions.map(position => new THREE.Matrix4().multiplyMatrices( new THREE.Matrix4().makeTranslation(new THREE.Vector3(position.x, 0, position.y)), new THREE.Matrix4().makeRotationY((Math.PI / 2) * Math.random()) ) ); - this.rocks.setGroup(patchId, rockParticlesMatrices); + this.rocks.setGroup(patchIdString, rockParticlesMatrices); } } } @@ -292,7 +306,6 @@ class TestGrass extends TestBase { this.grass2D.setPlayerViewPosition(playerViewPosition); this.grass3D.setPlayerViewPosition(playerViewPosition); - this.rocks.setPlayerViewPosition(playerViewPosition); } } From a6fcc636c13f5d705d80e4c3ab63d7ff6db11c52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Piellard?= Date: Sun, 5 Jan 2025 22:43:16 +0100 Subject: [PATCH 28/31] test: add draw calls counter --- src/test/test-base.ts | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/src/test/test-base.ts b/src/test/test-base.ts index b10bba72..99dba6f8 100644 --- a/src/test/test-base.ts +++ b/src/test/test-base.ts @@ -1,7 +1,9 @@ import * as THREE from 'three-usage-test'; abstract class TestBase { - private readonly stats: THREE.Stats; + private readonly statsFps: THREE.Stats; + private readonly statsDrawCalls: THREE.Stats; + private readonly statsDrawCallsPanel: THREE.Stats.Panel; protected readonly renderer: THREE.WebGLRenderer; protected readonly camera: THREE.PerspectiveCamera; @@ -13,8 +15,15 @@ abstract class TestBase { protected maxFps: number = Infinity; public constructor() { - this.stats = new THREE.Stats(); - document.body.appendChild(this.stats.dom); + this.statsFps = new THREE.Stats(); + document.body.appendChild(this.statsFps.dom); + + this.statsDrawCalls = new THREE.Stats(); + document.body.appendChild(this.statsDrawCalls.dom); + this.statsDrawCallsPanel = new THREE.Stats.Panel('draw calls', '#f8f', '#212'); + this.statsDrawCalls.addPanel(this.statsDrawCallsPanel); + this.statsDrawCalls.showPanel(3); + this.statsDrawCalls.dom.style.cssText = 'position:fixed;top:50px;left:0px;cursor:pointer;z-index:10000'; this.renderer = new THREE.WebGLRenderer(); document.body.appendChild(this.renderer.domElement); @@ -48,6 +57,10 @@ abstract class TestBase { } this.started = true; + setInterval(() => { + this.statsDrawCallsPanel.update(this.renderer.info.render.calls, 200); + }, 100); + let lastRenderTimestamp = performance.now(); const render = () => { @@ -56,7 +69,7 @@ abstract class TestBase { const deltaTime = now - lastRenderTimestamp; if (deltaTime >= minDeltaTime) { - this.stats.update(); + this.statsFps.update(); this.cameraControl.update(); this.update(); From e41b993b22031c13bc866cc31584a4db49039e86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Piellard?= Date: Sun, 5 Jan 2025 23:04:01 +0100 Subject: [PATCH 29/31] perf: don't draw batches that are out of view The stock threejs frustum culling works but does not take distance into account (since the frustum distance is way greater than the props view distance). So, complete this frustum culling by manually hiding batches that are too far to be seen. --- src/lib/effects/props/props-batch.ts | 2 +- src/lib/effects/props/props-handler.ts | 28 ++++++++++++++++++++++++++ src/test/test-grass.ts | 10 +++++++++ 3 files changed, 39 insertions(+), 1 deletion(-) diff --git a/src/lib/effects/props/props-batch.ts b/src/lib/effects/props/props-batch.ts index fe33ccc2..cff49b32 100644 --- a/src/lib/effects/props/props-batch.ts +++ b/src/lib/effects/props/props-batch.ts @@ -142,7 +142,7 @@ type GroupDefinition = { }; class PropsBatch { - public get container(): THREE.Object3D { + public get container(): THREE.InstancedMesh { return this.instancedMesh; } diff --git a/src/lib/effects/props/props-handler.ts b/src/lib/effects/props/props-handler.ts index 80f6cda7..35358326 100644 --- a/src/lib/effects/props/props-handler.ts +++ b/src/lib/effects/props/props-handler.ts @@ -35,6 +35,8 @@ class PropsHandler { private readonly batchesPerGroup: Map>; private batches: PropsBatch[]; + private lastCameraPositionWorld: THREE.Vector3 | null = null; + public constructor(params: Parameters) { this.container = new THREE.Group(); this.container.name = 'props-handler'; @@ -71,6 +73,7 @@ class PropsHandler { remainingMatricesList = remainingMatricesList.slice(instancesCount); batch.setInstancesGroup(groupName, batchMatrices); + this.updateBatchVisibility(batch); const batches = this.batchesPerGroup.get(groupName); if (!batches) { @@ -123,6 +126,7 @@ class PropsHandler { } for (const batch of batchesForThisGroup) { batch.deleteInstancesGroup(groupName); + this.updateBatchVisibility(batch); } this.batchesPerGroup.delete(groupName); } @@ -198,6 +202,30 @@ class PropsHandler { batch.playerViewPosition.copy(this.playerViewPosition); } } + + public updateVisibilities(cameraWorldPosition: THREE.Vector3Like): void { + if (!this.lastCameraPositionWorld) { + this.lastCameraPositionWorld = new THREE.Vector3(); + } + this.lastCameraPositionWorld.copy(cameraWorldPosition); + + for (const batch of this.batches) { + this.updateBatchVisibility(batch); + } + } + + private updateBatchVisibility(batch: PropsBatch): void { + let distanceFromCamera = 0; + if (this.lastCameraPositionWorld) { + if (batch.container.boundingSphere) { + distanceFromCamera = batch.container.boundingSphere.distanceToPoint(this.lastCameraPositionWorld); + } else { + logger.warn(`Batch does not have a bounding sphere.`); + } + } + + batch.container.visible = distanceFromCamera < this.viewDistance + 50; + } } export { PropsHandler, type PropsHandlerStatistics }; diff --git a/src/test/test-grass.ts b/src/test/test-grass.ts index 38530771..2ed3527d 100644 --- a/src/test/test-grass.ts +++ b/src/test/test-grass.ts @@ -228,10 +228,20 @@ class TestGrass extends TestBase { }; applyGrassMode(); + const updateAllVisibilities = () => { + const cameraWorldPosition = this.camera.getWorldPosition(new THREE.Vector3()); + this.grass2D.updateVisibilities(cameraWorldPosition); + this.grass3D.updateVisibilities(cameraWorldPosition); + this.rocks.updateVisibilities(cameraWorldPosition); + }; + setInterval(updateAllVisibilities, 150); + updateAllVisibilities(); + const applyViewDistance = () => { this.grass2D.setViewDistance(this.parameters.viewRadius); this.grass3D.setViewDistance(this.parameters.viewRadius); this.rocks.setViewDistance(this.parameters.viewRadius); + updateAllVisibilities(); }; applyViewDistance(); From 3dad1fc3b2c730c4c2f4ab022b177936d3793970 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Piellard?= Date: Mon, 6 Jan 2025 11:34:33 +0100 Subject: [PATCH 30/31] fix: fix threejs frustum culling --- src/lib/effects/props/props-batch.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/lib/effects/props/props-batch.ts b/src/lib/effects/props/props-batch.ts index cff49b32..21170fa3 100644 --- a/src/lib/effects/props/props-batch.ts +++ b/src/lib/effects/props/props-batch.ts @@ -190,15 +190,16 @@ class PropsBatch { this.instancedMesh.setMatrixAt(newGroup.startIndex + index, matrix); }); this.instancedMesh.instanceMatrix.needsUpdate = true; - this.instancedMesh.computeBoundingBox(); this.instancedMesh.computeBoundingSphere(); this.instancedMesh.count += matricesList.length; + this.updateFrustumCulling(); } public deleteInstancesGroup(groupName: string): void { if (this.groupsDefinitions.has(groupName)) { this.groupsDefinitions.delete(groupName); this.reorderMatricesBuffer(); + this.updateFrustumCulling(); } else { logger.warn(`Unknown props batch group "${groupName}".`); } @@ -252,6 +253,11 @@ class PropsBatch { this.instancedMesh.instanceMatrix.array.set(reorderedMatrices.subarray(0, 16 * instancesCount), 0); this.instancedMesh.count = instancesCount; } + + private updateFrustumCulling(): void { + this.instancedMesh.computeBoundingBox(); + this.instancedMesh.computeBoundingSphere(); + } } export { PropsBatch }; From f69a7281320bf41a4ab2ee4d0e1484fff1212581 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Piellard?= Date: Mon, 6 Jan 2025 21:25:50 +0100 Subject: [PATCH 31/31] feat: better statistics for memory, frustum culling etc --- src/lib/effects/props/props-batch.ts | 12 +++++++++++- src/lib/effects/props/props-handler.ts | 18 ++++++++++++++---- src/test/test-grass.ts | 6 +++++- 3 files changed, 30 insertions(+), 6 deletions(-) diff --git a/src/lib/effects/props/props-batch.ts b/src/lib/effects/props/props-batch.ts index 21170fa3..e3fb7aa6 100644 --- a/src/lib/effects/props/props-batch.ts +++ b/src/lib/effects/props/props-batch.ts @@ -127,6 +127,8 @@ type PropsBatchStatistics = { instancesCapacity: number; instancesUsed: number; groupsCount: number; + boundingSphereRadius: number; + buffersSizeInBytes: number; }; type Paramerers = { @@ -190,7 +192,6 @@ class PropsBatch { this.instancedMesh.setMatrixAt(newGroup.startIndex + index, matrix); }); this.instancedMesh.instanceMatrix.needsUpdate = true; - this.instancedMesh.computeBoundingSphere(); this.instancedMesh.count += matricesList.length; this.updateFrustumCulling(); } @@ -222,10 +223,19 @@ class PropsBatch { } public getStatistics(): PropsBatchStatistics { + let buffersSizeInBytes = 0; + for (const attributeBuffer of Object.values(this.instancedMesh.geometry.attributes)) { + buffersSizeInBytes += attributeBuffer.array.byteLength; + } + buffersSizeInBytes += this.instancedMesh.instanceColor?.array.byteLength ?? 0; + buffersSizeInBytes += this.instancedMesh.instanceMatrix?.array.byteLength ?? 0; + return { instancesCapacity: this.maxInstancesCount, instancesUsed: this.instancedMesh.count, groupsCount: this.groupsDefinitions.size, + boundingSphereRadius: this.instancedMesh.boundingSphere?.radius ?? Infinity, + buffersSizeInBytes, }; } diff --git a/src/lib/effects/props/props-handler.ts b/src/lib/effects/props/props-handler.ts index 35358326..04757fc0 100644 --- a/src/lib/effects/props/props-handler.ts +++ b/src/lib/effects/props/props-handler.ts @@ -4,10 +4,12 @@ import * as THREE from '../../libs/three-usage'; import { PropsBatch } from './props-batch'; type PropsHandlerStatistics = { - batchesCount: number; batchesSize: number; + batchesCount: number; + batchesVisibleCount: number; totalInstancesCapacity: number; totalInstancesUsed: number; + buffersSizeInBytes: number; }; type Parameters = { @@ -41,8 +43,8 @@ class PropsHandler { this.container = new THREE.Group(); this.container.name = 'props-handler'; - this.batchSize = params.batchSize ?? 20000; - this.minGroupPartSize = params.minGroupPartSize ?? 1000; + this.batchSize = params.batchSize ?? 5000; + this.minGroupPartSize = params.minGroupPartSize ?? 200; this.reactToPlayer = params.reactToPlayer ?? false; this.bufferGeometry = params.bufferGeometry; @@ -169,16 +171,24 @@ class PropsHandler { } public getStatistics(): PropsHandlerStatistics { + let batchesVisibleCount = 0; let totalInstancesUsed = 0; + let buffersSizeInBytes = 0; for (const batch of this.batches) { totalInstancesUsed += this.batchSize - batch.spareInstancesLeft; + buffersSizeInBytes += batch.getStatistics().buffersSizeInBytes; + if (batch.container.visible) { + batchesVisibleCount++; + } } return { - batchesCount: this.batches.length, batchesSize: this.batchSize, + batchesCount: this.batches.length, + batchesVisibleCount, totalInstancesCapacity: this.batches.length * this.batchSize, totalInstancesUsed, + buffersSizeInBytes, }; } diff --git a/src/test/test-grass.ts b/src/test/test-grass.ts index 2ed3527d..cebafeac 100644 --- a/src/test/test-grass.ts +++ b/src/test/test-grass.ts @@ -203,7 +203,7 @@ class TestGrass extends TestBase { const groundTextureSize = 5 * voxelsInGroundTexture; const groundTextureBuffer = new Uint8Array(groundTextureSize * groundTextureSize * 4); for (let i = 0; i < groundTextureSize * groundTextureSize; i++) { - const rand = 255 * (Math.random() - 0.5) * 0.2; + const rand = 255 * (Math.random() - 0.5) * 0.1; groundTextureBuffer[4 * i + 0] = THREE.clamp(0 + rand, 0, 255); groundTextureBuffer[4 * i + 1] = THREE.clamp(185 + rand, 0, 255); groundTextureBuffer[4 * i + 2] = THREE.clamp(20 + rand, 0, 255); @@ -222,6 +222,10 @@ class TestGrass extends TestBase { ground.scale.set(groundSize, groundSize, 1); this.scene.add(ground); + setInterval(() => { + console.log(JSON.stringify(this.grass2D.getStatistics())); + }, 1000); + const applyGrassMode = () => { this.grass2D.container.visible = this.parameters.grassMode === EGrassMode.GRASS_2D; this.grass3D.container.visible = this.parameters.grassMode === EGrassMode.GRASS_3D;