[READ-ONLY] Mirror of https://github.com/flo-bit/tiny-planets. procedurally generated tiny planets in the browsers flo-bit.dev/tiny-planets/
low-poly planets procedural-generation threejs
0

Configure Feed

Select the types of activity you want to include in your feed.

move and fix ground calculation

+151 -82
+84 -1
src/worlds/biome.ts
··· 7 7 } from "./helper/colorgradient"; 8 8 import { biomePresets } from "./presets"; 9 9 import { Octree } from "./helper/octree"; 10 + import { type VertexInfo } from "./types"; 10 11 11 12 export type VegetationItem = { 12 13 name: string; ··· 65 66 constructor(opts: BiomeOptions = {}) { 66 67 if (opts.preset) { 67 68 const preset = biomePresets[opts.preset]; 69 + 68 70 if (preset) { 69 71 opts = { 70 72 ...preset, ··· 175 177 position: Vector3, 176 178 radius: number, 177 179 ): (Vector3 & { data?: VegetationItem })[] { 178 - return this.vegetationPositions.query(position, radius); 180 + return this.vegetationPositions.queryBoxXYZ( 181 + position.x, 182 + position.y, 183 + position.z, 184 + radius, 185 + ); 186 + } 187 + 188 + maxVegetationRadius(): number { 189 + let max = 0; 190 + for (const item of this.options.vegetation?.items ?? []) { 191 + if (item.ground?.radius) { 192 + max = Math.max(max, item.ground.radius); 193 + } 194 + } 195 + 196 + return max; 197 + } 198 + 199 + vegetationHeightAndColorForFace( 200 + a: Vector3, 201 + b: Vector3, 202 + c: Vector3, 203 + color: Color, 204 + sideLength: number, 205 + ): { 206 + heightA: number; 207 + heightB: number; 208 + heightC: number; 209 + color: Color; 210 + } { 211 + const maxDist = this.maxVegetationRadius(); 212 + // use a to find all vegetation items, we add sideLength so that we also find vegetation from b and c 213 + // that otherwise would be missed, because they are too far away from a 214 + const vegetations = this.itemsAround(a, maxDist + sideLength * 2); 215 + 216 + // go through a, b and c and add heights for all vegetation items that are close enough (distance is closer than item.ground.radius) 217 + let heightA = 0; 218 + let heightB = 0; 219 + let heightC = 0; 220 + 221 + let all = [a, b, c]; 222 + for (let j = 0; j < 3; j++) { 223 + let p = all[j]; 224 + 225 + for (const vegetation of vegetations) { 226 + if (!vegetation.data?.ground?.radius) continue; 227 + 228 + let distance = p.distanceTo(vegetation); 229 + 230 + if (distance < vegetation.data.ground?.radius) { 231 + let amount = Math.max( 232 + 0, 233 + 1 - distance / vegetation.data.ground.radius, 234 + ); 235 + 236 + amount = Math.pow(amount, 0.5); 237 + 238 + let height = vegetation.data.ground?.raise ?? 0; 239 + height *= amount; 240 + 241 + if (j === 0) heightA += height; 242 + if (j === 1) heightB += height; 243 + if (j === 2) heightC += height; 244 + 245 + if (!vegetation.data.ground.color) continue; 246 + 247 + let newColor = new Color(vegetation.data.ground.color); 248 + 249 + // only lerp a third of the way, because we have three vertices 250 + // so if all vertices are close enough, we lerp 3 times 251 + color.lerp(newColor, amount / 3); 252 + } 253 + } 254 + } 255 + 256 + return { 257 + heightA, 258 + heightB, 259 + heightC, 260 + color, 261 + }; 179 262 } 180 263 }
+11 -11
src/worlds/presets.ts
··· 38 38 vegetation: { 39 39 items: [ 40 40 { 41 + name: "Rock", 42 + density: 50, 43 + minimumHeight: 0.1, 44 + colors: { 45 + Gray: { array: [0x775544] }, 46 + }, 47 + }, 48 + { 41 49 name: "PalmTree", 42 50 density: 50, 43 51 minimumHeight: 0.1, ··· 47 55 DarkGreen: { array: [0x006400] }, 48 56 }, 49 57 ground: { 50 - color: 0x338800, 51 - }, 52 - }, 53 - { 54 - name: "Rock", 55 - density: 10, 56 - minimumHeight: 0.1, 57 - colors: { 58 - Gray: { array: [0x775544] }, 58 + color: 0x229900, 59 + radius: 0.1, 60 + raise: 0.01, 59 61 }, 60 62 }, 61 63 ], ··· 280 282 biome: { 281 283 preset: "snowForest", 282 284 }, 283 - 284 - material: "normal", 285 285 }; 286 286 287 287 export const planetPresets: Record<string, PlanetOptions> = {
+9
src/worlds/types.ts
··· 1 + import { Vector3 } from "three"; 2 + 3 + export type VertexInfo = { 4 + height: number; 5 + scatter: Vector3; 6 + 7 + seaHeight: number; 8 + seaMorph: number; 9 + };
+47 -70
src/worlds/worker.ts
··· 9 9 import { Biome, type VegetationItem } from "./biome"; 10 10 import { type PlanetOptions } from "./planet"; 11 11 import UberNoise from "uber-noise"; 12 + import { type VertexInfo } from "./types"; 12 13 13 14 onmessage = function (e) { 14 15 const { type, data, requestId } = e.data; ··· 75 76 const faceSize = (Math.PI * 4) / faceCount; 76 77 console.log("faces:", faceCount); 77 78 78 - const calculatedVertices = new Map< 79 - string, 80 - { 81 - height: number; 82 - scatter: Vector3; 83 - 84 - seaHeight: number; 85 - seaMorph: number; 86 - } 87 - >(); 88 - 89 - const calculatedVerticesArray: { 90 - height: number; 91 - scatter: Vector3; 92 - 93 - seaHeight: number; 94 - seaMorph: number; 95 - }[] = new Array(faceCount); 79 + // store calculated vertices so we don't have to recalculate them 80 + // once store by hashed position (so we can find vertices of different faces that have the same position) 81 + const calculatedVertices = new Map<string, VertexInfo>(); 82 + // and once by index for vegetation placement 83 + const calculatedVerticesArray: VertexInfo[] = new Array(faceCount); 96 84 97 85 const colors = new Float32Array(vertices.count * 3); 98 86 const oceanColors = new Float32Array(oceanVertices.count * 3); ··· 110 98 a.fromBufferAttribute(vertices, 0); 111 99 b.fromBufferAttribute(vertices, 1); 112 100 113 - // scatterAmount is based on side length of face (all faces are the same size) 114 - const scatterAmount = (planetOptions.scatter ?? 1) * b.distanceTo(a); 101 + const faceSideLength = a.distanceTo(b); 102 + 103 + // scatterAmount is based on side length of face (all faces have the same size) 104 + const scatterAmount = (planetOptions.scatter ?? 1.2) * faceSideLength; 115 105 const scatterScale = 100; 116 106 117 107 const scatterNoise = new UberNoise({ ··· 360 350 } 361 351 362 352 const maxDist = 0.14; 353 + 354 + const color = new Color(); 355 + 363 356 // go through all vertices again and update height and color based on vegetation 364 357 for (let i = 0; i < vertices.count; i += 3) { 365 - let found = false; 366 - let closestDistAll = 1; 367 - let closestVegetation: VegetationItem | undefined = undefined; 358 + a.fromBufferAttribute(vertices, i); 359 + a.normalize(); 360 + b.fromBufferAttribute(vertices, i + 1); 361 + b.normalize(); 362 + c.fromBufferAttribute(vertices, i + 2); 363 + c.normalize(); 368 364 369 - for (let j = 0; j < 3; j++) { 370 - a.fromBufferAttribute(vertices, i + j); 371 - a.normalize(); 365 + color.setRGB(colors[i * 3], colors[i * 3 + 1], colors[i * 3 + 2]); 372 366 373 - let p = biome.itemsAround(a, maxDist); 374 - if (p.length > 0) { 375 - // find closest point 376 - let closest = p[0]; 377 - let closestDist = a.distanceTo(closest); 378 - for (let k = 1; k < p.length; k++) { 379 - let dist = a.distanceTo(p[k]); 380 - if (dist < closestDist) { 381 - closest = p[k]; 382 - closestDist = dist; 383 - } 384 - } 367 + const output = biome.vegetationHeightAndColorForFace( 368 + a, 369 + b, 370 + c, 371 + color, 372 + faceSideLength, 373 + ); 385 374 386 - let moveInfo = calculatedVerticesArray[i + j]; 375 + const moveDataA = calculatedVerticesArray[i]; 376 + const moveDataB = calculatedVerticesArray[i + 1]; 377 + const moveDataC = calculatedVerticesArray[i + 2]; 387 378 388 - a.multiplyScalar( 389 - moveInfo.height + ((maxDist - closestDist) / maxDist) * 0.015, 390 - ); 379 + // update height based on vegetation 380 + a.normalize().multiplyScalar(moveDataA.height + output.heightA); 381 + b.normalize().multiplyScalar(moveDataB.height + output.heightB); 382 + c.normalize().multiplyScalar(moveDataC.height + output.heightC); 391 383 392 - vertices.setXYZ(i + j, a.x, a.y, a.z); 384 + vertices.setXYZ(i, a.x, a.y, a.z); 385 + vertices.setXYZ(i + 1, b.x, b.y, b.z); 386 + vertices.setXYZ(i + 2, c.x, c.y, c.z); 393 387 394 - if (closestDist < closestDistAll) { 395 - closestVegetation = closest.data; 396 - } 388 + // update color based on vegetation 389 + colors[i * 3] = output.color.r; 390 + colors[i * 3 + 1] = output.color.g; 391 + colors[i * 3 + 2] = output.color.b; 397 392 398 - closestDistAll = Math.min(closestDist, closestDistAll); 399 - found = true; 400 - } 401 - } 393 + colors[i * 3 + 3] = output.color.r; 394 + colors[i * 3 + 4] = output.color.g; 395 + colors[i * 3 + 5] = output.color.b; 402 396 403 - if (!found) continue; 404 - 405 - let existingColor = new Color( 406 - colors[i * 3], 407 - colors[i * 3 + 1], 408 - colors[i * 3 + 2], 409 - ); 410 - 411 - if (closestVegetation?.ground?.color) { 412 - // set color 413 - let newColor = new Color(closestVegetation.ground.color); 414 - 415 - newColor.lerp(existingColor, closestDistAll / maxDist); 416 - 417 - for (let j = 0; j < 3; j++) { 418 - colors[(i + j) * 3] = newColor.r; 419 - colors[(i + j) * 3 + 1] = newColor.g; 420 - colors[(i + j) * 3 + 2] = newColor.b; 421 - } 422 - } 397 + colors[i * 3 + 6] = output.color.r; 398 + colors[i * 3 + 7] = output.color.g; 399 + colors[i * 3 + 8] = output.color.b; 423 400 } 424 401 425 402 oceanSphere.morphAttributes.position[0] = new Float32BufferAttribute(