···88 - [ ] noise
99 - [x] height
1010 - [ ] slope
1111+ - [ ] distance
1112- [ ] different materials
1213- [ ] make vegetation sway in the wind
1314- [ ] make all random stuff dependent on seed
···18191920### water
2021- [x] moving water
2222+- [ ] option to turn off water
2123- [ ] water reflections
2224- [ ] water refractions
2325- [x] water caustics
-624
src/noise.ts
···11-import {
22- type NoiseFunction2D,
33- type NoiseFunction3D,
44- type NoiseFunction4D,
55- createNoise2D,
66- createNoise3D,
77- createNoise4D,
88-} from "simplex-noise";
99-import alea from "alea";
1010-1111-/**
1212- *
1313- * @property {number} seed - seed for the noise, if not provided, Math.random() will be used,
1414- * currently same results can only be guaranteed for newly created noise objects with same seed
1515- * (as opposed to an old noise object where you changed the seed)
1616- *
1717- * @property {number} min - minimun value of noise
1818- * @property {number} max - maximum value of noise
1919- *
2020- * @property {number} scale - scale of the noise
2121- * @property {number} power - power of the noise (1 = linear, 2 = quadratic, etc)
2222- * @property {Vector} shift - move noise in 2D, 3D or 4D space
2323- *
2424- * @property {number} octaves - number of layers for fbm noise
2525- * @property {number} gain - how much to multiply amplitude per layer
2626- * @property {number} lacunarity - how much to multiply scale per layer
2727- * @property {array} amps - array of amplitudes for each layer
2828- * @property {number} erosion - how much previous layers influence amplitude of later layers
2929- * @property {number} sharpness - billowed or rigded noise (0 = normal, 1 = billowed, -1 = ridged)
3030- * @property {number} steps - will turn noise into steps (integer, number of steps)
3131- *
3232- * @property {number} warp - how much to warp the noise
3333- * @property {number} warpNoise - noise to warp the noise with
3434- * @property {number} warp2 - second warp, can only be used if warp is used too
3535- * @property {number} warpNoise2 - second warp noise
3636- *
3737- * @property {boolean} invert - invert the noise
3838- * @property {boolean} abs - absolute value of the noise
3939- * @property {boolean} clamp - clamp the noise between min and max
4040- * @property {boolean} tileX - tile the noise in x direction
4141- * @property {boolean} tileY - tile the noise in y direction
4242- * @property {boolean} tile - tile the noise in all directions (will override tileX and tileY)
4343- *
4444- * @constructor
4545- */
4646-4747-function lerp(a: number, b: number, t: number): number {
4848- return (b - a) * t + a;
4949-}
5050-5151-function setLength(vector: number[], length: number): number[] {
5252- const oldLength = Math.sqrt(vector.reduce((sum, v) => sum + v * v, 0));
5353- if (oldLength === 0) return vector;
5454- const scale = length / oldLength;
5555- return vector.map((v) => v * scale);
5656-}
5757-5858-// angle between two 3D vectors
5959-function angleTo(a: number[], b: number[]): number {
6060- const dot = a.reduce((sum, v, i) => sum + v * b[i], 0);
6161- const length = Math.sqrt(
6262- a.reduce((sum, v) => sum + v * v, 0) * b.reduce((sum, v) => sum + v * v, 0),
6363- );
6464- return Math.acos(dot / length);
6565-}
6666-6767-type ErosionUpFunction = (derivative: number[]) => number[];
6868-6969-export type VectorObject = {
7070- x: number;
7171- y: number;
7272- z?: number;
7373- w?: number;
7474-};
7575-7676-export type NoiseParameterInput = number | UberNoise | UberNoiseOptions;
7777-export type NoiseParameter = number | UberNoise;
7878-7979-export type UberNoiseOptions = {
8080- seed?: string | number;
8181-8282- min?: NoiseParameterInput;
8383- max?: NoiseParameterInput;
8484-8585- scale?: NoiseParameterInput;
8686- power?: NoiseParameterInput;
8787-8888- shift?: number[];
8989-9090- octaves?: number;
9191- gain?: NoiseParameterInput;
9292- lacunarity?: NoiseParameterInput;
9393-9494- amps?: number[];
9595-9696- layers?: UberNoiseOptions[];
9797-9898- erosion?: NoiseParameterInput;
9999- erosionUp?: ErosionUpFunction;
100100-101101- sharpness?: NoiseParameterInput;
102102- steps?: NoiseParameterInput;
103103- warp?: NoiseParameterInput;
104104- warpNoise?: UberNoise;
105105- warp2?: NoiseParameterInput;
106106- warpNoise2?: UberNoise;
107107-108108- invert?: boolean;
109109- abs?: boolean;
110110- clamp?: boolean;
111111- tileX?: boolean;
112112- tileY?: boolean;
113113- tile?: boolean;
114114-};
115115-116116-export class UberNoise {
117117- private noise2D: NoiseFunction2D | undefined;
118118- private noise3D: NoiseFunction3D | undefined;
119119- private noise4D: NoiseFunction4D | undefined;
120120- private seed: string | number;
121121-122122- private _min: NoiseParameter = -1;
123123- private _max: NoiseParameter = 1;
124124-125125- private _scale: NoiseParameter = 1;
126126- private _power: NoiseParameter = 1;
127127-128128- private shift: number[] | undefined = undefined;
129129-130130- private octaves = 0;
131131- private _gain: NoiseParameter = 0.5;
132132- private _lacunarity: NoiseParameter = 2;
133133-134134- private amps: number[] = [];
135135-136136- private _erosion: NoiseParameter = 0;
137137- private _sharpness: NoiseParameter = 0;
138138- private _steps: NoiseParameter = 0;
139139-140140- private _warp: NoiseParameter = 0;
141141- private _warpNoise: UberNoise | undefined;
142142- private _warp2: NoiseParameter = 0;
143143- private _warpNoise2: UberNoise | undefined;
144144-145145- private tileX = false;
146146- private tileY = false;
147147-148148- private erosionDelta = 0;
149149- private erosionDerivative?: number[] = undefined;
150150- private erosionUp?: ErosionUpFunction;
151151-152152- private static erosionDefaultUp = [0, 0, 1];
153153-154154- private position = [0, 0];
155155-156156- private pngr;
157157-158158- private layers: UberNoise[] = [];
159159-160160- constructor(options: UberNoiseOptions = {}) {
161161- this.seed = options.seed ?? Math.random();
162162- this.pngr = alea(this.seed);
163163-164164- this.min = options.min ?? -1;
165165- this.max = options.max ?? 1;
166166-167167- this.scale = options.scale ?? 1;
168168- this.power = options.power ?? 1;
169169-170170- this.octaves = options.octaves ?? options.layers?.length ?? 0;
171171- this.gain = options.gain ?? 0.5;
172172- this.lacunarity = options.lacunarity ?? 2;
173173-174174- this.erosion = options.erosion ?? 0;
175175- this.sharpness = options.sharpness ?? 0;
176176- this.steps = options.steps ?? 0;
177177-178178- this.warp = options.warp ?? 0;
179179- this.warpNoise = options.warpNoise;
180180- this.warp2 = options.warp2 ?? 0;
181181- this.warpNoise2 = options.warpNoise2;
182182-183183- this.processLayers(options);
184184-185185- this.tileX = options.tileX ?? options.tile ?? false;
186186- this.tileY = options.tileY ?? options.tile ?? false;
187187- }
188188-189189- processLayers(options: UberNoiseOptions) {
190190- this.amps = options.amps ?? [];
191191-192192- for (let i = 0; i < this.octaves; i++) {
193193- const layerOptions = options.layers?.[i] ?? {};
194194- if (layerOptions instanceof UberNoise) {
195195- this.layers[i] = layerOptions;
196196- } else {
197197- if (layerOptions.seed === undefined) layerOptions.seed = this.pngr();
198198- this.layers[i] = new UberNoise(layerOptions);
199199- }
200200- }
201201- if (this.layers.length == 0) {
202202- this.noise2D = createNoise2D(this.pngr);
203203- this.noise3D = createNoise3D(this.pngr);
204204- this.noise4D = createNoise4D(this.pngr);
205205- }
206206- }
207207-208208- // same as normalized but returns between min and max
209209- get(
210210- x: number | VectorObject | Array<number>,
211211- y: number | undefined = undefined,
212212- z: number | undefined = undefined,
213213- w: number | undefined = undefined,
214214- ): number {
215215- const norm = this.normalized(x, y, z, w);
216216- return this.toMinMax(norm);
217217- }
218218-219219- // same as get but returns between -1 and 1
220220- normalized(
221221- x: number | VectorObject | Array<number>,
222222- y: number | undefined = undefined,
223223- z: number | undefined = undefined,
224224- w: number | undefined = undefined,
225225- ): number {
226226- // if x is an array, treat it as a vector
227227- if (Array.isArray(x)) {
228228- w = x[3];
229229- z = x[2];
230230- y = x[1];
231231- x = x[0];
232232- } else if (typeof x === "object") {
233233- w = x.w;
234234- z = x.z;
235235- y = x.y;
236236- x = x.x;
237237- }
238238- // if y is undefined, treat it as 2D noise with y = 0
239239- y = y ?? 0;
240240-241241- return this.getNoise(x, y, z, w);
242242- }
243243-244244- private warpPosition(warp: number, warpNoise: UberNoise | undefined) {
245245- if (warp == undefined || warp == 0) return;
246246-247247- if (warpNoise != undefined) warpNoise.position = this.position;
248248-249249- let x = this.position[0],
250250- y = this.position[1],
251251- z = this.position[2],
252252- w = this.position[3];
253253-254254- const noise = warpNoise ?? this;
255255- const scl = this.scale;
256256-257257- this.position = [x + 54.47 * scl, y + 34.98 * scl];
258258- if (z != undefined) this.position.push(z + 21.63 * scl);
259259- if (w != undefined) this.position.push(w + 67.1 * scl);
260260-261261- x += noise.getFBM() * warp;
262262- y += noise.getFBM() * warp;
263263- if (z != undefined) z += noise.getFBM() * warp;
264264- if (w != undefined) w += noise.getFBM() * warp;
265265-266266- this.position = [x, y, z, w];
267267- }
268268-269269- private tilePosition() {
270270- if (!this.tileX && !this.tileY) return;
271271- const x = this.position[0];
272272- const y = this.position[1];
273273- let newX = 0,
274274- newY = 0,
275275- newZ = 0,
276276- newW = 0;
277277- if (this.tileX) {
278278- newX = Math.sin(x * Math.PI * 2);
279279- newY = Math.cos(x * Math.PI * 2);
280280- }
281281- if (this.tileY) {
282282- newZ = Math.sin(y * Math.PI * 2);
283283- newW = Math.cos(y * Math.PI * 2);
284284- }
285285- if (this.tileX && !this.tileY) {
286286- this.position = [newX, newY + y];
287287- } else if (this.tileY && !this.tileX) {
288288- this.position = [newZ + x, newW];
289289- } else if (this.tileX && this.tileY) {
290290- this.position = [newX, newY, newZ, newW];
291291- }
292292- }
293293-294294- private getDerivative(
295295- x: number,
296296- y: number,
297297- z: number,
298298- n: number | undefined = undefined,
299299- ): number[] | undefined {
300300- // left side or central difference
301301- // very expensive (four/six noise calls), should be changed to analytical derivatives
302302- // see https://iquilezles.org/www/articles/morenoise/morenoise.htm
303303-304304- if (z == undefined) return undefined;
305305-306306- const mov = this.erosionDelta;
307307-308308- const dx = (n ?? this.get(x - mov, y, z)) - this.get(x + mov, y, z);
309309- const dy = (n ?? this.get(x, y - mov, z)) - this.get(x, y + mov, z);
310310- const dz = (n ?? this.get(x, y, z - mov)) - this.get(x, y, z + mov);
311311-312312- this.erosionDerivative = [dx, dy, dz];
313313- return this.erosionDerivative;
314314- }
315315-316316- private getBaseLayer(
317317- scale: number,
318318- x: number,
319319- y: number,
320320- z?: number,
321321- w?: number,
322322- ): number {
323323- if (z != undefined && w != undefined && this.noise4D != undefined) {
324324- return this.noise4D(x * scale, y * scale, z * scale, w * scale);
325325- }
326326- if (z != undefined && this.noise3D != undefined) {
327327- return this.noise3D(x * scale, y * scale, z * scale);
328328- }
329329- if (this.noise2D != undefined) {
330330- return this.noise2D(x * scale, y * scale);
331331- }
332332- return 0;
333333- }
334334-335335- private getFBM(useErosion = false): number {
336336- const x = this.position[0],
337337- y = this.position[1],
338338- z = this.position[2],
339339- w = this.position[3];
340340-341341- const scale = this.scale;
342342-343343- if (this.layers.length === 0) {
344344- return this.getBaseLayer(scale, x, y, z, w);
345345- }
346346-347347- let maxAmp = 1;
348348- let amp = 1,
349349- freq = scale;
350350-351351- const lacunarity = this.lacunarity;
352352- const gain = this.gain;
353353-354354- // for now we'll always ignore erosion
355355- // remove the following line later to enable erosion
356356- useErosion = false;
357357- const erosion = useErosion ? this.erosion : 0;
358358- const up =
359359- this.erosionUp != undefined
360360- ? this.erosionUp(this.position)
361361- : UberNoise.erosionDefaultUp;
362362- const sum = [0, 0, 0];
363363-364364- let n = 0;
365365-366366- for (let i = 0; i < this.octaves; i++) {
367367- const layer = this.layers[i];
368368-369369- const layerAmp = this.amps[i] ?? 1;
370370-371371- const value =
372372- layer.get(
373373- x * freq,
374374- y * freq,
375375- z != undefined ? z * freq : undefined,
376376- w != undefined ? w * freq : undefined,
377377- ) *
378378- amp *
379379- layerAmp;
380380- if (erosion > 0) {
381381- const derivative = layer.getDerivative(x * freq, y * freq, z * freq);
382382-383383- if (derivative != undefined) {
384384- setLength(derivative, amp * layerAmp);
385385- sum[0] += derivative[0];
386386- sum[1] += derivative[1];
387387- sum[2] += derivative[2];
388388-389389- const mult = Math.abs(1 - angleTo(up, sum) / Math.PI);
390390-391391- n += value * (mult * erosion + 1 - erosion);
392392- }
393393- } else {
394394- n += value;
395395- }
396396-397397- amp *= gain;
398398- freq *= lacunarity;
399399- maxAmp += amp * layerAmp;
400400- }
401401- return n / maxAmp;
402402- }
403403-404404- move(
405405- x: number,
406406- y: number,
407407- z: number | undefined = undefined,
408408- w: number | undefined = undefined,
409409- ) {
410410- if (this.shift == undefined) this.shift = [0, 0, 0];
411411-412412- this.shift[0] += x;
413413- this.shift[1] += y;
414414- if (z != undefined) this.shift[2] += z;
415415- if (w != undefined) this.shift[3] += w;
416416- }
417417-418418- private getNoise(
419419- x: number,
420420- y: number,
421421- z: number | undefined = undefined,
422422- w: number | undefined = undefined,
423423- ): number {
424424- // set position
425425- this.position[0] = x;
426426- this.position[1] = y;
427427- if (z !== undefined) {
428428- this.position[2] = z;
429429- }
430430- if (w !== undefined) {
431431- this.position[3] = w;
432432- }
433433-434434- // apply shift
435435- const shift = this.shift;
436436- if (shift !== undefined) {
437437- for (let i = 0; i < this.position.length && i < shift.length; i++) {
438438- this.position[i] += shift[i];
439439- }
440440- }
441441-442442- // apply tiling
443443- this.tilePosition();
444444-445445- // apply warp
446446- this.warpPosition(this.warp, this.warpNoise);
447447- this.warpPosition(this.warp2, this.warpNoise2);
448448-449449- let norm = this.getFBM(true);
450450-451451- // apply power
452452- const power = this.power;
453453- if (power != 1) {
454454- // convert to [0 - 1], apply power and back to [-1, 1]
455455- norm = (Math.pow((norm + 1) * 0.5, power) - 0.5) * 2;
456456- }
457457-458458- // apply sharpness
459459- const sharpness = this.sharpness;
460460- if (sharpness != 0) {
461461- const billow = (Math.abs(norm) - 0.5) * 2;
462462- const ridged = (0.5 - Math.abs(norm)) * 2;
463463-464464- norm = lerp(norm, billow, Math.max(0, sharpness));
465465- norm = lerp(norm, ridged, Math.max(0, -sharpness));
466466- }
467467-468468- // apply steps
469469- const steps = this.steps;
470470- if (steps != 0) {
471471- // convert from -1 to 1 to 0 to 1
472472- norm = (norm + 1) * 0.5;
473473- // apply steps
474474- norm = Math.floor(norm * steps) / steps;
475475- // convert back to -1 to 1
476476- norm = norm * 2 - 1;
477477- }
478478-479479- return norm;
480480- }
481481-482482- /**
483483- * value between min and max to normalized value between -1 and 1
484484- *
485485- * @param value {number}
486486- * @returns {number}
487487- */
488488- toNormalized(value: number): number {
489489- return ((value - this.min) / (this.max - this.min)) * 2 - 1;
490490- }
491491-492492- /**
493493- * normlized value to value between min and max
494494- *
495495- * @param value {number}
496496- * @returns {number}
497497- */
498498- toMinMax(value: number): number {
499499- return (value + 1) * 0.5 * (this.max - this.min) + this.min;
500500- }
501501-502502- private checkParameterInput(value: NoiseParameterInput): UberNoise | number {
503503- if (typeof value === "object" && !(value instanceof UberNoise)) {
504504- if (value.seed === undefined) {
505505- value.seed = this.pngr();
506506- }
507507- value = new UberNoise(value);
508508- }
509509- return value;
510510- }
511511-512512- private getParameter(value: NoiseParameter): number {
513513- if (typeof value === "number") {
514514- return value;
515515- }
516516- return value.get(this.position);
517517- }
518518-519519- // getter and setter for min and max
520520- get min(): number {
521521- return this.getParameter(this._min);
522522- }
523523- set min(value: NoiseParameterInput) {
524524- this._min = this.checkParameterInput(value);
525525- }
526526- get max(): number {
527527- return this.getParameter(this._max);
528528- }
529529- set max(value: NoiseParameterInput) {
530530- this._max = this.checkParameterInput(value);
531531- }
532532-533533- // getter and setter for scale and power
534534- get scale(): number {
535535- return this.getParameter(this._scale);
536536- }
537537- set scale(value: NoiseParameterInput) {
538538- this._scale = this.checkParameterInput(value);
539539- }
540540- get power(): number {
541541- return this.getParameter(this._power);
542542- }
543543- set power(value: NoiseParameterInput) {
544544- this._power = this.checkParameterInput(value);
545545- }
546546-547547- // getter and setter for gain and lacunarity
548548- get gain(): number {
549549- return this.getParameter(this._gain);
550550- }
551551- set gain(value: NoiseParameterInput) {
552552- this._gain = this.checkParameterInput(value);
553553- }
554554- get lacunarity(): number {
555555- return this.getParameter(this._lacunarity);
556556- }
557557- set lacunarity(value: NoiseParameterInput) {
558558- this._lacunarity = this.checkParameterInput(value);
559559- }
560560-561561- // getter and setter for erosion, sharpness and steps
562562- get erosion(): number {
563563- return this.getParameter(this._erosion);
564564- }
565565- set erosion(value: NoiseParameterInput) {
566566- this._erosion = this.checkParameterInput(value);
567567- }
568568- get sharpness(): number {
569569- return this.getParameter(this._sharpness);
570570- }
571571- set sharpness(value: NoiseParameterInput) {
572572- this._sharpness = this.checkParameterInput(value);
573573- }
574574- get steps(): number {
575575- return Math.round(this.getParameter(this._steps));
576576- }
577577- set steps(value: NoiseParameterInput) {
578578- this._steps = this.checkParameterInput(value);
579579- }
580580-581581- // getter and setter for warp, warpNoise, warp2 and warpNoise2
582582- get warp(): number {
583583- return this.getParameter(this._warp);
584584- }
585585- set warp(value: NoiseParameterInput) {
586586- this._warp = this.checkParameterInput(value);
587587- }
588588- get warpNoise(): UberNoise | undefined {
589589- return this._warpNoise;
590590- }
591591- set warpNoise(value: UberNoise | UberNoiseOptions | undefined) {
592592- if (value == undefined) {
593593- this._warpNoise = undefined;
594594- return;
595595- }
596596-597597- const processed = this.checkParameterInput(value);
598598- if (processed instanceof UberNoise) {
599599- this._warpNoise = processed;
600600- }
601601- }
602602- get warp2(): number {
603603- return this.getParameter(this._warp2);
604604- }
605605- set warp2(value: NoiseParameterInput) {
606606- this._warp2 = this.checkParameterInput(value);
607607- }
608608- get warpNoise2(): UberNoise | undefined {
609609- return this._warpNoise2;
610610- }
611611- set warpNoise2(value: UberNoise | UberNoiseOptions | undefined) {
612612- if (value == undefined) {
613613- this._warpNoise2 = undefined;
614614- return;
615615- }
616616-617617- const processed = this.checkParameterInput(value);
618618- if (processed instanceof UberNoise) {
619619- this._warpNoise2 = processed;
620620- }
621621- }
622622-}
623623-624624-export default UberNoise;
+6-3
src/script.ts
···11import * as THREE from "three";
22import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
33import { Planet } from "./worlds/planet";
44+import { Stars } from "./worlds/stars";
4556const presets = ["beach", "forest", "snowForest"];
67···1314 throw new Error("Canvas not found");
1415}
1516const scene = new THREE.Scene();
1616-const camera = new THREE.PerspectiveCamera(70, width / height, 0.01, 10);
1717+const camera = new THREE.PerspectiveCamera(70, width / height, 0.01, 30);
1718camera.position.set(0, 0, 2.5);
18191920const renderer = new THREE.WebGLRenderer({
···2324});
24252526renderer.toneMapping = THREE.ACESFilmicToneMapping;
2626-2727renderer.shadowMap.enabled = true;
28282929const _ = new OrbitControls(camera, renderer.domElement);
···57575858const ambientLight = new THREE.AmbientLight(0xffffff, 0.1);
5959scene.add(ambientLight);
6060-61606261let total = 0;
6362let lastDelta = 0;
···7776 hasPlanet = true;
7877 }
7978});
7979+8080+let stars = new Stars();
8181+8282+scene.add(stars);
80838184// add keydown event listener
8285document.addEventListener("keydown", (event) => {
···66 Color,
77} from "three";
8899-import { Biome, type BiomeOptions } from "./biome";
99+import { Biome } from "./biome";
1010import { type PlanetOptions } from "./planet";
1111import UberNoise from "uber-noise";
1212···261261 j++
262262 ) {
263263 const vegetation = biome.options.vegetation.items[j];
264264- if (Math.random() < faceSize * vegetation.density) {
264264+ if (Math.random() < faceSize * (vegetation.density ?? 1)) {
265265 // discard if point is below or above height limits
266266 if (
267267 vegetation.minimumHeight !== undefined &&
···349349 oceanMorphNormals.push(temp.x, temp.y, temp.z);
350350 oceanMorphNormals.push(temp.x, temp.y, temp.z);
351351 }
352352-353353- console.log("normHeightMax", normHeightMax);
354354- console.log("normHeightMin", normHeightMin);
355352356353 const maxDist = 0.14;
357354 // go through all vertices again and update height and color based on vegetation