[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.

some cleanup

Florian (Sep 29, 2024, 8:16 AM +0200) 9d09e4ca 63d1c4c5

+117 -649
bun.lockb

This is a binary file and will not be displayed.

+2
features.md
··· 8 8 - [ ] noise 9 9 - [x] height 10 10 - [ ] slope 11 + - [ ] distance 11 12 - [ ] different materials 12 13 - [ ] make vegetation sway in the wind 13 14 - [ ] make all random stuff dependent on seed ··· 18 19 19 20 ### water 20 21 - [x] moving water 22 + - [ ] option to turn off water 21 23 - [ ] water reflections 22 24 - [ ] water refractions 23 25 - [x] water caustics
-624
src/noise.ts
··· 1 - import { 2 - type NoiseFunction2D, 3 - type NoiseFunction3D, 4 - type NoiseFunction4D, 5 - createNoise2D, 6 - createNoise3D, 7 - createNoise4D, 8 - } from "simplex-noise"; 9 - import alea from "alea"; 10 - 11 - /** 12 - * 13 - * @property {number} seed - seed for the noise, if not provided, Math.random() will be used, 14 - * currently same results can only be guaranteed for newly created noise objects with same seed 15 - * (as opposed to an old noise object where you changed the seed) 16 - * 17 - * @property {number} min - minimun value of noise 18 - * @property {number} max - maximum value of noise 19 - * 20 - * @property {number} scale - scale of the noise 21 - * @property {number} power - power of the noise (1 = linear, 2 = quadratic, etc) 22 - * @property {Vector} shift - move noise in 2D, 3D or 4D space 23 - * 24 - * @property {number} octaves - number of layers for fbm noise 25 - * @property {number} gain - how much to multiply amplitude per layer 26 - * @property {number} lacunarity - how much to multiply scale per layer 27 - * @property {array} amps - array of amplitudes for each layer 28 - * @property {number} erosion - how much previous layers influence amplitude of later layers 29 - * @property {number} sharpness - billowed or rigded noise (0 = normal, 1 = billowed, -1 = ridged) 30 - * @property {number} steps - will turn noise into steps (integer, number of steps) 31 - * 32 - * @property {number} warp - how much to warp the noise 33 - * @property {number} warpNoise - noise to warp the noise with 34 - * @property {number} warp2 - second warp, can only be used if warp is used too 35 - * @property {number} warpNoise2 - second warp noise 36 - * 37 - * @property {boolean} invert - invert the noise 38 - * @property {boolean} abs - absolute value of the noise 39 - * @property {boolean} clamp - clamp the noise between min and max 40 - * @property {boolean} tileX - tile the noise in x direction 41 - * @property {boolean} tileY - tile the noise in y direction 42 - * @property {boolean} tile - tile the noise in all directions (will override tileX and tileY) 43 - * 44 - * @constructor 45 - */ 46 - 47 - function lerp(a: number, b: number, t: number): number { 48 - return (b - a) * t + a; 49 - } 50 - 51 - function setLength(vector: number[], length: number): number[] { 52 - const oldLength = Math.sqrt(vector.reduce((sum, v) => sum + v * v, 0)); 53 - if (oldLength === 0) return vector; 54 - const scale = length / oldLength; 55 - return vector.map((v) => v * scale); 56 - } 57 - 58 - // angle between two 3D vectors 59 - function angleTo(a: number[], b: number[]): number { 60 - const dot = a.reduce((sum, v, i) => sum + v * b[i], 0); 61 - const length = Math.sqrt( 62 - a.reduce((sum, v) => sum + v * v, 0) * b.reduce((sum, v) => sum + v * v, 0), 63 - ); 64 - return Math.acos(dot / length); 65 - } 66 - 67 - type ErosionUpFunction = (derivative: number[]) => number[]; 68 - 69 - export type VectorObject = { 70 - x: number; 71 - y: number; 72 - z?: number; 73 - w?: number; 74 - }; 75 - 76 - export type NoiseParameterInput = number | UberNoise | UberNoiseOptions; 77 - export type NoiseParameter = number | UberNoise; 78 - 79 - export type UberNoiseOptions = { 80 - seed?: string | number; 81 - 82 - min?: NoiseParameterInput; 83 - max?: NoiseParameterInput; 84 - 85 - scale?: NoiseParameterInput; 86 - power?: NoiseParameterInput; 87 - 88 - shift?: number[]; 89 - 90 - octaves?: number; 91 - gain?: NoiseParameterInput; 92 - lacunarity?: NoiseParameterInput; 93 - 94 - amps?: number[]; 95 - 96 - layers?: UberNoiseOptions[]; 97 - 98 - erosion?: NoiseParameterInput; 99 - erosionUp?: ErosionUpFunction; 100 - 101 - sharpness?: NoiseParameterInput; 102 - steps?: NoiseParameterInput; 103 - warp?: NoiseParameterInput; 104 - warpNoise?: UberNoise; 105 - warp2?: NoiseParameterInput; 106 - warpNoise2?: UberNoise; 107 - 108 - invert?: boolean; 109 - abs?: boolean; 110 - clamp?: boolean; 111 - tileX?: boolean; 112 - tileY?: boolean; 113 - tile?: boolean; 114 - }; 115 - 116 - export class UberNoise { 117 - private noise2D: NoiseFunction2D | undefined; 118 - private noise3D: NoiseFunction3D | undefined; 119 - private noise4D: NoiseFunction4D | undefined; 120 - private seed: string | number; 121 - 122 - private _min: NoiseParameter = -1; 123 - private _max: NoiseParameter = 1; 124 - 125 - private _scale: NoiseParameter = 1; 126 - private _power: NoiseParameter = 1; 127 - 128 - private shift: number[] | undefined = undefined; 129 - 130 - private octaves = 0; 131 - private _gain: NoiseParameter = 0.5; 132 - private _lacunarity: NoiseParameter = 2; 133 - 134 - private amps: number[] = []; 135 - 136 - private _erosion: NoiseParameter = 0; 137 - private _sharpness: NoiseParameter = 0; 138 - private _steps: NoiseParameter = 0; 139 - 140 - private _warp: NoiseParameter = 0; 141 - private _warpNoise: UberNoise | undefined; 142 - private _warp2: NoiseParameter = 0; 143 - private _warpNoise2: UberNoise | undefined; 144 - 145 - private tileX = false; 146 - private tileY = false; 147 - 148 - private erosionDelta = 0; 149 - private erosionDerivative?: number[] = undefined; 150 - private erosionUp?: ErosionUpFunction; 151 - 152 - private static erosionDefaultUp = [0, 0, 1]; 153 - 154 - private position = [0, 0]; 155 - 156 - private pngr; 157 - 158 - private layers: UberNoise[] = []; 159 - 160 - constructor(options: UberNoiseOptions = {}) { 161 - this.seed = options.seed ?? Math.random(); 162 - this.pngr = alea(this.seed); 163 - 164 - this.min = options.min ?? -1; 165 - this.max = options.max ?? 1; 166 - 167 - this.scale = options.scale ?? 1; 168 - this.power = options.power ?? 1; 169 - 170 - this.octaves = options.octaves ?? options.layers?.length ?? 0; 171 - this.gain = options.gain ?? 0.5; 172 - this.lacunarity = options.lacunarity ?? 2; 173 - 174 - this.erosion = options.erosion ?? 0; 175 - this.sharpness = options.sharpness ?? 0; 176 - this.steps = options.steps ?? 0; 177 - 178 - this.warp = options.warp ?? 0; 179 - this.warpNoise = options.warpNoise; 180 - this.warp2 = options.warp2 ?? 0; 181 - this.warpNoise2 = options.warpNoise2; 182 - 183 - this.processLayers(options); 184 - 185 - this.tileX = options.tileX ?? options.tile ?? false; 186 - this.tileY = options.tileY ?? options.tile ?? false; 187 - } 188 - 189 - processLayers(options: UberNoiseOptions) { 190 - this.amps = options.amps ?? []; 191 - 192 - for (let i = 0; i < this.octaves; i++) { 193 - const layerOptions = options.layers?.[i] ?? {}; 194 - if (layerOptions instanceof UberNoise) { 195 - this.layers[i] = layerOptions; 196 - } else { 197 - if (layerOptions.seed === undefined) layerOptions.seed = this.pngr(); 198 - this.layers[i] = new UberNoise(layerOptions); 199 - } 200 - } 201 - if (this.layers.length == 0) { 202 - this.noise2D = createNoise2D(this.pngr); 203 - this.noise3D = createNoise3D(this.pngr); 204 - this.noise4D = createNoise4D(this.pngr); 205 - } 206 - } 207 - 208 - // same as normalized but returns between min and max 209 - get( 210 - x: number | VectorObject | Array<number>, 211 - y: number | undefined = undefined, 212 - z: number | undefined = undefined, 213 - w: number | undefined = undefined, 214 - ): number { 215 - const norm = this.normalized(x, y, z, w); 216 - return this.toMinMax(norm); 217 - } 218 - 219 - // same as get but returns between -1 and 1 220 - normalized( 221 - x: number | VectorObject | Array<number>, 222 - y: number | undefined = undefined, 223 - z: number | undefined = undefined, 224 - w: number | undefined = undefined, 225 - ): number { 226 - // if x is an array, treat it as a vector 227 - if (Array.isArray(x)) { 228 - w = x[3]; 229 - z = x[2]; 230 - y = x[1]; 231 - x = x[0]; 232 - } else if (typeof x === "object") { 233 - w = x.w; 234 - z = x.z; 235 - y = x.y; 236 - x = x.x; 237 - } 238 - // if y is undefined, treat it as 2D noise with y = 0 239 - y = y ?? 0; 240 - 241 - return this.getNoise(x, y, z, w); 242 - } 243 - 244 - private warpPosition(warp: number, warpNoise: UberNoise | undefined) { 245 - if (warp == undefined || warp == 0) return; 246 - 247 - if (warpNoise != undefined) warpNoise.position = this.position; 248 - 249 - let x = this.position[0], 250 - y = this.position[1], 251 - z = this.position[2], 252 - w = this.position[3]; 253 - 254 - const noise = warpNoise ?? this; 255 - const scl = this.scale; 256 - 257 - this.position = [x + 54.47 * scl, y + 34.98 * scl]; 258 - if (z != undefined) this.position.push(z + 21.63 * scl); 259 - if (w != undefined) this.position.push(w + 67.1 * scl); 260 - 261 - x += noise.getFBM() * warp; 262 - y += noise.getFBM() * warp; 263 - if (z != undefined) z += noise.getFBM() * warp; 264 - if (w != undefined) w += noise.getFBM() * warp; 265 - 266 - this.position = [x, y, z, w]; 267 - } 268 - 269 - private tilePosition() { 270 - if (!this.tileX && !this.tileY) return; 271 - const x = this.position[0]; 272 - const y = this.position[1]; 273 - let newX = 0, 274 - newY = 0, 275 - newZ = 0, 276 - newW = 0; 277 - if (this.tileX) { 278 - newX = Math.sin(x * Math.PI * 2); 279 - newY = Math.cos(x * Math.PI * 2); 280 - } 281 - if (this.tileY) { 282 - newZ = Math.sin(y * Math.PI * 2); 283 - newW = Math.cos(y * Math.PI * 2); 284 - } 285 - if (this.tileX && !this.tileY) { 286 - this.position = [newX, newY + y]; 287 - } else if (this.tileY && !this.tileX) { 288 - this.position = [newZ + x, newW]; 289 - } else if (this.tileX && this.tileY) { 290 - this.position = [newX, newY, newZ, newW]; 291 - } 292 - } 293 - 294 - private getDerivative( 295 - x: number, 296 - y: number, 297 - z: number, 298 - n: number | undefined = undefined, 299 - ): number[] | undefined { 300 - // left side or central difference 301 - // very expensive (four/six noise calls), should be changed to analytical derivatives 302 - // see https://iquilezles.org/www/articles/morenoise/morenoise.htm 303 - 304 - if (z == undefined) return undefined; 305 - 306 - const mov = this.erosionDelta; 307 - 308 - const dx = (n ?? this.get(x - mov, y, z)) - this.get(x + mov, y, z); 309 - const dy = (n ?? this.get(x, y - mov, z)) - this.get(x, y + mov, z); 310 - const dz = (n ?? this.get(x, y, z - mov)) - this.get(x, y, z + mov); 311 - 312 - this.erosionDerivative = [dx, dy, dz]; 313 - return this.erosionDerivative; 314 - } 315 - 316 - private getBaseLayer( 317 - scale: number, 318 - x: number, 319 - y: number, 320 - z?: number, 321 - w?: number, 322 - ): number { 323 - if (z != undefined && w != undefined && this.noise4D != undefined) { 324 - return this.noise4D(x * scale, y * scale, z * scale, w * scale); 325 - } 326 - if (z != undefined && this.noise3D != undefined) { 327 - return this.noise3D(x * scale, y * scale, z * scale); 328 - } 329 - if (this.noise2D != undefined) { 330 - return this.noise2D(x * scale, y * scale); 331 - } 332 - return 0; 333 - } 334 - 335 - private getFBM(useErosion = false): number { 336 - const x = this.position[0], 337 - y = this.position[1], 338 - z = this.position[2], 339 - w = this.position[3]; 340 - 341 - const scale = this.scale; 342 - 343 - if (this.layers.length === 0) { 344 - return this.getBaseLayer(scale, x, y, z, w); 345 - } 346 - 347 - let maxAmp = 1; 348 - let amp = 1, 349 - freq = scale; 350 - 351 - const lacunarity = this.lacunarity; 352 - const gain = this.gain; 353 - 354 - // for now we'll always ignore erosion 355 - // remove the following line later to enable erosion 356 - useErosion = false; 357 - const erosion = useErosion ? this.erosion : 0; 358 - const up = 359 - this.erosionUp != undefined 360 - ? this.erosionUp(this.position) 361 - : UberNoise.erosionDefaultUp; 362 - const sum = [0, 0, 0]; 363 - 364 - let n = 0; 365 - 366 - for (let i = 0; i < this.octaves; i++) { 367 - const layer = this.layers[i]; 368 - 369 - const layerAmp = this.amps[i] ?? 1; 370 - 371 - const value = 372 - layer.get( 373 - x * freq, 374 - y * freq, 375 - z != undefined ? z * freq : undefined, 376 - w != undefined ? w * freq : undefined, 377 - ) * 378 - amp * 379 - layerAmp; 380 - if (erosion > 0) { 381 - const derivative = layer.getDerivative(x * freq, y * freq, z * freq); 382 - 383 - if (derivative != undefined) { 384 - setLength(derivative, amp * layerAmp); 385 - sum[0] += derivative[0]; 386 - sum[1] += derivative[1]; 387 - sum[2] += derivative[2]; 388 - 389 - const mult = Math.abs(1 - angleTo(up, sum) / Math.PI); 390 - 391 - n += value * (mult * erosion + 1 - erosion); 392 - } 393 - } else { 394 - n += value; 395 - } 396 - 397 - amp *= gain; 398 - freq *= lacunarity; 399 - maxAmp += amp * layerAmp; 400 - } 401 - return n / maxAmp; 402 - } 403 - 404 - move( 405 - x: number, 406 - y: number, 407 - z: number | undefined = undefined, 408 - w: number | undefined = undefined, 409 - ) { 410 - if (this.shift == undefined) this.shift = [0, 0, 0]; 411 - 412 - this.shift[0] += x; 413 - this.shift[1] += y; 414 - if (z != undefined) this.shift[2] += z; 415 - if (w != undefined) this.shift[3] += w; 416 - } 417 - 418 - private getNoise( 419 - x: number, 420 - y: number, 421 - z: number | undefined = undefined, 422 - w: number | undefined = undefined, 423 - ): number { 424 - // set position 425 - this.position[0] = x; 426 - this.position[1] = y; 427 - if (z !== undefined) { 428 - this.position[2] = z; 429 - } 430 - if (w !== undefined) { 431 - this.position[3] = w; 432 - } 433 - 434 - // apply shift 435 - const shift = this.shift; 436 - if (shift !== undefined) { 437 - for (let i = 0; i < this.position.length && i < shift.length; i++) { 438 - this.position[i] += shift[i]; 439 - } 440 - } 441 - 442 - // apply tiling 443 - this.tilePosition(); 444 - 445 - // apply warp 446 - this.warpPosition(this.warp, this.warpNoise); 447 - this.warpPosition(this.warp2, this.warpNoise2); 448 - 449 - let norm = this.getFBM(true); 450 - 451 - // apply power 452 - const power = this.power; 453 - if (power != 1) { 454 - // convert to [0 - 1], apply power and back to [-1, 1] 455 - norm = (Math.pow((norm + 1) * 0.5, power) - 0.5) * 2; 456 - } 457 - 458 - // apply sharpness 459 - const sharpness = this.sharpness; 460 - if (sharpness != 0) { 461 - const billow = (Math.abs(norm) - 0.5) * 2; 462 - const ridged = (0.5 - Math.abs(norm)) * 2; 463 - 464 - norm = lerp(norm, billow, Math.max(0, sharpness)); 465 - norm = lerp(norm, ridged, Math.max(0, -sharpness)); 466 - } 467 - 468 - // apply steps 469 - const steps = this.steps; 470 - if (steps != 0) { 471 - // convert from -1 to 1 to 0 to 1 472 - norm = (norm + 1) * 0.5; 473 - // apply steps 474 - norm = Math.floor(norm * steps) / steps; 475 - // convert back to -1 to 1 476 - norm = norm * 2 - 1; 477 - } 478 - 479 - return norm; 480 - } 481 - 482 - /** 483 - * value between min and max to normalized value between -1 and 1 484 - * 485 - * @param value {number} 486 - * @returns {number} 487 - */ 488 - toNormalized(value: number): number { 489 - return ((value - this.min) / (this.max - this.min)) * 2 - 1; 490 - } 491 - 492 - /** 493 - * normlized value to value between min and max 494 - * 495 - * @param value {number} 496 - * @returns {number} 497 - */ 498 - toMinMax(value: number): number { 499 - return (value + 1) * 0.5 * (this.max - this.min) + this.min; 500 - } 501 - 502 - private checkParameterInput(value: NoiseParameterInput): UberNoise | number { 503 - if (typeof value === "object" && !(value instanceof UberNoise)) { 504 - if (value.seed === undefined) { 505 - value.seed = this.pngr(); 506 - } 507 - value = new UberNoise(value); 508 - } 509 - return value; 510 - } 511 - 512 - private getParameter(value: NoiseParameter): number { 513 - if (typeof value === "number") { 514 - return value; 515 - } 516 - return value.get(this.position); 517 - } 518 - 519 - // getter and setter for min and max 520 - get min(): number { 521 - return this.getParameter(this._min); 522 - } 523 - set min(value: NoiseParameterInput) { 524 - this._min = this.checkParameterInput(value); 525 - } 526 - get max(): number { 527 - return this.getParameter(this._max); 528 - } 529 - set max(value: NoiseParameterInput) { 530 - this._max = this.checkParameterInput(value); 531 - } 532 - 533 - // getter and setter for scale and power 534 - get scale(): number { 535 - return this.getParameter(this._scale); 536 - } 537 - set scale(value: NoiseParameterInput) { 538 - this._scale = this.checkParameterInput(value); 539 - } 540 - get power(): number { 541 - return this.getParameter(this._power); 542 - } 543 - set power(value: NoiseParameterInput) { 544 - this._power = this.checkParameterInput(value); 545 - } 546 - 547 - // getter and setter for gain and lacunarity 548 - get gain(): number { 549 - return this.getParameter(this._gain); 550 - } 551 - set gain(value: NoiseParameterInput) { 552 - this._gain = this.checkParameterInput(value); 553 - } 554 - get lacunarity(): number { 555 - return this.getParameter(this._lacunarity); 556 - } 557 - set lacunarity(value: NoiseParameterInput) { 558 - this._lacunarity = this.checkParameterInput(value); 559 - } 560 - 561 - // getter and setter for erosion, sharpness and steps 562 - get erosion(): number { 563 - return this.getParameter(this._erosion); 564 - } 565 - set erosion(value: NoiseParameterInput) { 566 - this._erosion = this.checkParameterInput(value); 567 - } 568 - get sharpness(): number { 569 - return this.getParameter(this._sharpness); 570 - } 571 - set sharpness(value: NoiseParameterInput) { 572 - this._sharpness = this.checkParameterInput(value); 573 - } 574 - get steps(): number { 575 - return Math.round(this.getParameter(this._steps)); 576 - } 577 - set steps(value: NoiseParameterInput) { 578 - this._steps = this.checkParameterInput(value); 579 - } 580 - 581 - // getter and setter for warp, warpNoise, warp2 and warpNoise2 582 - get warp(): number { 583 - return this.getParameter(this._warp); 584 - } 585 - set warp(value: NoiseParameterInput) { 586 - this._warp = this.checkParameterInput(value); 587 - } 588 - get warpNoise(): UberNoise | undefined { 589 - return this._warpNoise; 590 - } 591 - set warpNoise(value: UberNoise | UberNoiseOptions | undefined) { 592 - if (value == undefined) { 593 - this._warpNoise = undefined; 594 - return; 595 - } 596 - 597 - const processed = this.checkParameterInput(value); 598 - if (processed instanceof UberNoise) { 599 - this._warpNoise = processed; 600 - } 601 - } 602 - get warp2(): number { 603 - return this.getParameter(this._warp2); 604 - } 605 - set warp2(value: NoiseParameterInput) { 606 - this._warp2 = this.checkParameterInput(value); 607 - } 608 - get warpNoise2(): UberNoise | undefined { 609 - return this._warpNoise2; 610 - } 611 - set warpNoise2(value: UberNoise | UberNoiseOptions | undefined) { 612 - if (value == undefined) { 613 - this._warpNoise2 = undefined; 614 - return; 615 - } 616 - 617 - const processed = this.checkParameterInput(value); 618 - if (processed instanceof UberNoise) { 619 - this._warpNoise2 = processed; 620 - } 621 - } 622 - } 623 - 624 - export default UberNoise;
+6 -3
src/script.ts
··· 1 1 import * as THREE from "three"; 2 2 import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js"; 3 3 import { Planet } from "./worlds/planet"; 4 + import { Stars } from "./worlds/stars"; 4 5 5 6 const presets = ["beach", "forest", "snowForest"]; 6 7 ··· 13 14 throw new Error("Canvas not found"); 14 15 } 15 16 const scene = new THREE.Scene(); 16 - const camera = new THREE.PerspectiveCamera(70, width / height, 0.01, 10); 17 + const camera = new THREE.PerspectiveCamera(70, width / height, 0.01, 30); 17 18 camera.position.set(0, 0, 2.5); 18 19 19 20 const renderer = new THREE.WebGLRenderer({ ··· 23 24 }); 24 25 25 26 renderer.toneMapping = THREE.ACESFilmicToneMapping; 26 - 27 27 renderer.shadowMap.enabled = true; 28 28 29 29 const _ = new OrbitControls(camera, renderer.domElement); ··· 57 57 58 58 const ambientLight = new THREE.AmbientLight(0xffffff, 0.1); 59 59 scene.add(ambientLight); 60 - 61 60 62 61 let total = 0; 63 62 let lastDelta = 0; ··· 77 76 hasPlanet = true; 78 77 } 79 78 }); 79 + 80 + let stars = new Stars(); 81 + 82 + scene.add(stars); 80 83 81 84 // add keydown event listener 82 85 document.addEventListener("keydown", (event) => {
+16 -6
src/worlds/biome.ts
··· 1 1 import { UberNoise, type NoiseOptions } from "uber-noise"; 2 - import { Color, Vector3 } from "three"; 2 + import { Color, ColorRepresentation, Vector3 } from "three"; 3 3 4 4 import { 5 5 ColorGradient, ··· 10 10 11 11 export type VegetationItem = { 12 12 name: string; 13 - density: number; 13 + density?: number; 14 14 15 15 minimumHeight?: number; 16 16 maximumHeight?: number; ··· 21 21 minimumDistance?: number; 22 22 maximumDistance?: number; 23 23 colors?: Record<string, { array?: number[] }>; 24 + 25 + ground?: { 26 + raise?: number; 27 + color?: ColorRepresentation; 28 + radius?: number; 29 + 30 + noise?: NoiseOptions; 31 + }; 24 32 }; 25 33 26 34 export type BiomeOptions = { ··· 37 45 tintColor?: number; 38 46 39 47 vegetation?: { 40 - defaults?: { 41 - density?: number; 42 - }; 48 + defaults?: Omit<Partial<VegetationItem>, "name">; 49 + 43 50 items: VegetationItem[]; 44 51 }; 45 52 }; ··· 143 150 this.vegetationPositions.insert(position, item); 144 151 } 145 152 146 - itemsAround(position: Vector3, radius: number): Vector3[] { 153 + itemsAround( 154 + position: Vector3, 155 + radius: number, 156 + ): (Vector3 & { data: VegetationItem })[] { 147 157 return this.vegetationPositions.query(position, radius); 148 158 } 149 159 }
+12 -9
src/worlds/helper/octree.ts
··· 113 113 114 114 // returns array of points where 115 115 // distance between pos and point is less than dist 116 - query(pos: Vector3Data, dist = 1) { 117 - const points = this.queryXYZ(pos.x, pos.y, pos.z, dist); 118 - for (let i = points.length - 1; i >= 0; i--) { 119 - if (points[i].distanceTo(pos) > dist) points.splice(i, 1); 120 - } 121 - return points; 116 + query(pos: Vector3Data, dist = 1): Vector3Data[] { 117 + const points = this.queryBoxXYZ(pos.x, pos.y, pos.z, dist); 118 + 119 + return points.filter((p) => p.distanceTo(pos) < dist); 122 120 } 123 121 124 - // vector3 free version, returns points in box around xyz 125 - queryXYZ(x: number, y: number, z: number, s: number) { 122 + // vector3 free version, returns points around xyz 123 + queryXYZ(x: number, y: number, z: number, dist: number) { 124 + const point = new Vector3(x, y, z); 125 + 126 + return this.query(point, dist); 127 + } 128 + 129 + queryBoxXYZ(x: number, y: number, z: number, s: number) { 126 130 const min = new Vector3(x - s, y - s, z - s), 127 131 max = new Vector3(x + s, y + s, z + s); 128 132 const box = new Box3(min, max); ··· 235 239 q = this.query(opts.p, opts.min); 236 240 237 241 for (const point of q) { 238 - // @ts-expect-error - close is not a property of Vector3 239 242 point.close = true; 240 243 } 241 244 }
+2 -2
src/worlds/presets.ts
··· 185 185 [-0.1, 0xaaccff], 186 186 ], 187 187 seaNoise: { 188 - min: -0.005, 189 - max: 0.005, 188 + min: -0.0, 189 + max: 0.001, 190 190 scale: 5, 191 191 }, 192 192
+77
src/worlds/stars.ts
··· 1 + import * as THREE from "three"; 2 + 3 + export type StarsOptions = { 4 + particleCount: number; 5 + minimumDistance: number; 6 + maximumDistance: number; 7 + }; 8 + 9 + export class Stars extends THREE.Group { 10 + private particleCount: number = 5000; 11 + 12 + private minimumDistance: number = 10; 13 + private maximumDistance: number = 20; 14 + 15 + private positions: Float32Array; 16 + private colors: Float32Array; 17 + private geometry: THREE.BufferGeometry; 18 + private material: THREE.PointsMaterial; 19 + private particles: THREE.Points; 20 + 21 + private tempVector = new THREE.Vector3(); 22 + 23 + constructor(opts: Partial<StarsOptions> = {}) { 24 + super(); 25 + 26 + this.particleCount = opts.particleCount ?? this.particleCount; 27 + this.minimumDistance = opts.minimumDistance ?? this.minimumDistance; 28 + this.maximumDistance = opts.maximumDistance ?? this.maximumDistance; 29 + 30 + this.positions = new Float32Array(this.particleCount * 3); 31 + this.colors = new Float32Array(this.particleCount * 3); 32 + 33 + this.setupParticles(); 34 + } 35 + 36 + private setupParticles() { 37 + for (let i = 0; i < this.particleCount; i++) { 38 + this.resetParticle(i); 39 + } 40 + 41 + this.geometry = new THREE.BufferGeometry(); 42 + this.geometry.setAttribute( 43 + "position", 44 + new THREE.BufferAttribute(this.positions, 3), 45 + ); 46 + this.geometry.setAttribute( 47 + "color", 48 + new THREE.BufferAttribute(this.colors, 3), 49 + ); 50 + 51 + this.material = new THREE.PointsMaterial({ 52 + size: 0.04, 53 + vertexColors: true, 54 + }); 55 + 56 + this.particles = new THREE.Points(this.geometry, this.material); 57 + 58 + this.add(this.particles); 59 + } 60 + 61 + private resetParticle(i: number) { 62 + const index = i * 3; 63 + 64 + const distance = 65 + Math.random() * (this.maximumDistance - this.minimumDistance) + 66 + this.minimumDistance; 67 + this.tempVector.randomDirection().multiplyScalar(distance); 68 + 69 + this.positions[index] = this.tempVector.x; 70 + this.positions[index + 1] = this.tempVector.y; 71 + this.positions[index + 2] = this.tempVector.z; 72 + 73 + this.colors[index] = Math.random() < 0.2 ? 0.3 : 1.0; 74 + this.colors[index + 1] = Math.random() < 0.2 ? 0.3 : 1.0; 75 + this.colors[index + 2] = Math.random() < 0.2 ? 0.3 : 1.0; 76 + } 77 + }
+2 -5
src/worlds/worker.ts
··· 6 6 Color, 7 7 } from "three"; 8 8 9 - import { Biome, type BiomeOptions } from "./biome"; 9 + import { Biome } from "./biome"; 10 10 import { type PlanetOptions } from "./planet"; 11 11 import UberNoise from "uber-noise"; 12 12 ··· 261 261 j++ 262 262 ) { 263 263 const vegetation = biome.options.vegetation.items[j]; 264 - if (Math.random() < faceSize * vegetation.density) { 264 + if (Math.random() < faceSize * (vegetation.density ?? 1)) { 265 265 // discard if point is below or above height limits 266 266 if ( 267 267 vegetation.minimumHeight !== undefined && ··· 349 349 oceanMorphNormals.push(temp.x, temp.y, temp.z); 350 350 oceanMorphNormals.push(temp.x, temp.y, temp.z); 351 351 } 352 - 353 - console.log("normHeightMax", normHeightMax); 354 - console.log("normHeightMin", normHeightMin); 355 352 356 353 const maxDist = 0.14; 357 354 // go through all vertices again and update height and color based on vegetation