From 2800468d1fbe0cd543a07bcc6fe9a7fdaa2520db Mon Sep 17 00:00:00 2001 From: Florian <45694132+flo-bit@users.noreply.github.com> Date: Thu, 3 Oct 2024 15:47:58 +0200 Subject: [PATCH] move and fix ground calculation --- src/worlds/biome.ts | 85 +++++++++++++++++++++++++++++- src/worlds/presets.ts | 22 ++++---- src/worlds/types.ts | 9 ++++ src/worlds/worker.ts | 117 +++++++++++++++++------------------------- 4 files changed, 151 insertions(+), 82 deletions(-) create mode 100644 src/worlds/types.ts diff --git a/src/worlds/biome.ts b/src/worlds/biome.ts index a5ee007..166810e 100644 --- a/src/worlds/biome.ts +++ b/src/worlds/biome.ts @@ -7,6 +7,7 @@ import { } from "./helper/colorgradient"; import { biomePresets } from "./presets"; import { Octree } from "./helper/octree"; +import { type VertexInfo } from "./types"; export type VegetationItem = { name: string; @@ -65,6 +66,7 @@ export class Biome { constructor(opts: BiomeOptions = {}) { if (opts.preset) { const preset = biomePresets[opts.preset]; + if (preset) { opts = { ...preset, @@ -175,6 +177,87 @@ export class Biome { position: Vector3, radius: number, ): (Vector3 & { data?: VegetationItem })[] { - return this.vegetationPositions.query(position, radius); + return this.vegetationPositions.queryBoxXYZ( + position.x, + position.y, + position.z, + radius, + ); + } + + maxVegetationRadius(): number { + let max = 0; + for (const item of this.options.vegetation?.items ?? []) { + if (item.ground?.radius) { + max = Math.max(max, item.ground.radius); + } + } + + return max; + } + + vegetationHeightAndColorForFace( + a: Vector3, + b: Vector3, + c: Vector3, + color: Color, + sideLength: number, + ): { + heightA: number; + heightB: number; + heightC: number; + color: Color; + } { + const maxDist = this.maxVegetationRadius(); + // use a to find all vegetation items, we add sideLength so that we also find vegetation from b and c + // that otherwise would be missed, because they are too far away from a + const vegetations = this.itemsAround(a, maxDist + sideLength * 2); + + // go through a, b and c and add heights for all vegetation items that are close enough (distance is closer than item.ground.radius) + let heightA = 0; + let heightB = 0; + let heightC = 0; + + let all = [a, b, c]; + for (let j = 0; j < 3; j++) { + let p = all[j]; + + for (const vegetation of vegetations) { + if (!vegetation.data?.ground?.radius) continue; + + let distance = p.distanceTo(vegetation); + + if (distance < vegetation.data.ground?.radius) { + let amount = Math.max( + 0, + 1 - distance / vegetation.data.ground.radius, + ); + + amount = Math.pow(amount, 0.5); + + let height = vegetation.data.ground?.raise ?? 0; + height *= amount; + + if (j === 0) heightA += height; + if (j === 1) heightB += height; + if (j === 2) heightC += height; + + if (!vegetation.data.ground.color) continue; + + let newColor = new Color(vegetation.data.ground.color); + + // only lerp a third of the way, because we have three vertices + // so if all vertices are close enough, we lerp 3 times + color.lerp(newColor, amount / 3); + } + } + } + + return { + heightA, + heightB, + heightC, + color, + }; } } diff --git a/src/worlds/presets.ts b/src/worlds/presets.ts index b08a4b2..321ec77 100644 --- a/src/worlds/presets.ts +++ b/src/worlds/presets.ts @@ -37,6 +37,14 @@ const beachBiome: BiomeOptions = { vegetation: { items: [ + { + name: "Rock", + density: 50, + minimumHeight: 0.1, + colors: { + Gray: { array: [0x775544] }, + }, + }, { name: "PalmTree", density: 50, @@ -47,15 +55,9 @@ const beachBiome: BiomeOptions = { DarkGreen: { array: [0x006400] }, }, ground: { - color: 0x338800, - }, - }, - { - name: "Rock", - density: 10, - minimumHeight: 0.1, - colors: { - Gray: { array: [0x775544] }, + color: 0x229900, + radius: 0.1, + raise: 0.01, }, }, ], @@ -280,8 +282,6 @@ const snowForestPlanet: PlanetOptions = { biome: { preset: "snowForest", }, - - material: "normal", }; export const planetPresets: Record = { diff --git a/src/worlds/types.ts b/src/worlds/types.ts new file mode 100644 index 0000000..766b195 --- /dev/null +++ b/src/worlds/types.ts @@ -0,0 +1,9 @@ +import { Vector3 } from "three"; + +export type VertexInfo = { + height: number; + scatter: Vector3; + + seaHeight: number; + seaMorph: number; +}; diff --git a/src/worlds/worker.ts b/src/worlds/worker.ts index 087eece..e86050b 100644 --- a/src/worlds/worker.ts +++ b/src/worlds/worker.ts @@ -9,6 +9,7 @@ import { import { Biome, type VegetationItem } from "./biome"; import { type PlanetOptions } from "./planet"; import UberNoise from "uber-noise"; +import { type VertexInfo } from "./types"; onmessage = function (e) { const { type, data, requestId } = e.data; @@ -75,24 +76,11 @@ function createGeometry( const faceSize = (Math.PI * 4) / faceCount; console.log("faces:", faceCount); - const calculatedVertices = new Map< - string, - { - height: number; - scatter: Vector3; - - seaHeight: number; - seaMorph: number; - } - >(); - - const calculatedVerticesArray: { - height: number; - scatter: Vector3; - - seaHeight: number; - seaMorph: number; - }[] = new Array(faceCount); + // store calculated vertices so we don't have to recalculate them + // once store by hashed position (so we can find vertices of different faces that have the same position) + const calculatedVertices = new Map(); + // and once by index for vegetation placement + const calculatedVerticesArray: VertexInfo[] = new Array(faceCount); const colors = new Float32Array(vertices.count * 3); const oceanColors = new Float32Array(oceanVertices.count * 3); @@ -110,8 +98,10 @@ function createGeometry( a.fromBufferAttribute(vertices, 0); b.fromBufferAttribute(vertices, 1); - // scatterAmount is based on side length of face (all faces are the same size) - const scatterAmount = (planetOptions.scatter ?? 1) * b.distanceTo(a); + const faceSideLength = a.distanceTo(b); + + // scatterAmount is based on side length of face (all faces have the same size) + const scatterAmount = (planetOptions.scatter ?? 1.2) * faceSideLength; const scatterScale = 100; const scatterNoise = new UberNoise({ @@ -360,66 +350,53 @@ function createGeometry( } const maxDist = 0.14; - // go through all vertices again and update height and color based on vegetation - for (let i = 0; i < vertices.count; i += 3) { - let found = false; - let closestDistAll = 1; - let closestVegetation: VegetationItem | undefined = undefined; - - for (let j = 0; j < 3; j++) { - a.fromBufferAttribute(vertices, i + j); - a.normalize(); - - let p = biome.itemsAround(a, maxDist); - if (p.length > 0) { - // find closest point - let closest = p[0]; - let closestDist = a.distanceTo(closest); - for (let k = 1; k < p.length; k++) { - let dist = a.distanceTo(p[k]); - if (dist < closestDist) { - closest = p[k]; - closestDist = dist; - } - } - let moveInfo = calculatedVerticesArray[i + j]; + const color = new Color(); - a.multiplyScalar( - moveInfo.height + ((maxDist - closestDist) / maxDist) * 0.015, - ); + // go through all vertices again and update height and color based on vegetation + for (let i = 0; i < vertices.count; i += 3) { + a.fromBufferAttribute(vertices, i); + a.normalize(); + b.fromBufferAttribute(vertices, i + 1); + b.normalize(); + c.fromBufferAttribute(vertices, i + 2); + c.normalize(); - vertices.setXYZ(i + j, a.x, a.y, a.z); + color.setRGB(colors[i * 3], colors[i * 3 + 1], colors[i * 3 + 2]); - if (closestDist < closestDistAll) { - closestVegetation = closest.data; - } + const output = biome.vegetationHeightAndColorForFace( + a, + b, + c, + color, + faceSideLength, + ); - closestDistAll = Math.min(closestDist, closestDistAll); - found = true; - } - } + const moveDataA = calculatedVerticesArray[i]; + const moveDataB = calculatedVerticesArray[i + 1]; + const moveDataC = calculatedVerticesArray[i + 2]; - if (!found) continue; + // update height based on vegetation + a.normalize().multiplyScalar(moveDataA.height + output.heightA); + b.normalize().multiplyScalar(moveDataB.height + output.heightB); + c.normalize().multiplyScalar(moveDataC.height + output.heightC); - let existingColor = new Color( - colors[i * 3], - colors[i * 3 + 1], - colors[i * 3 + 2], - ); + vertices.setXYZ(i, a.x, a.y, a.z); + vertices.setXYZ(i + 1, b.x, b.y, b.z); + vertices.setXYZ(i + 2, c.x, c.y, c.z); - if (closestVegetation?.ground?.color) { - // set color - let newColor = new Color(closestVegetation.ground.color); + // update color based on vegetation + colors[i * 3] = output.color.r; + colors[i * 3 + 1] = output.color.g; + colors[i * 3 + 2] = output.color.b; - newColor.lerp(existingColor, closestDistAll / maxDist); + colors[i * 3 + 3] = output.color.r; + colors[i * 3 + 4] = output.color.g; + colors[i * 3 + 5] = output.color.b; - for (let j = 0; j < 3; j++) { - colors[(i + j) * 3] = newColor.r; - colors[(i + j) * 3 + 1] = newColor.g; - colors[(i + j) * 3 + 2] = newColor.b; - } - } + colors[i * 3 + 6] = output.color.r; + colors[i * 3 + 7] = output.color.g; + colors[i * 3 + 8] = output.color.b; } oceanSphere.morphAttributes.position[0] = new Float32BufferAttribute(