Better world generation
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
// minecraft's CubicSpline: a smooth curve through points, where a point's value can itself be a spline of another
|
||||
// input. that nesting is how its terrain (and terralith's) turns continentalness, erosion and peaks and valleys into
|
||||
// heights: a spline over continentalness whose points are splines over erosion, whose points are splines over pv
|
||||
|
||||
export interface SplineInputs {
|
||||
continentalness: number;
|
||||
erosion: number;
|
||||
pv: number;
|
||||
weirdness: number;
|
||||
}
|
||||
|
||||
export type SplineValue = number | Spline;
|
||||
|
||||
export interface SplinePoint {
|
||||
at: number;
|
||||
value: SplineValue;
|
||||
// the slope there, worked out from the neighbors when not given
|
||||
slope?: number;
|
||||
}
|
||||
|
||||
export class Spline {
|
||||
readonly input: keyof SplineInputs;
|
||||
#locations: number[];
|
||||
#values: SplineValue[];
|
||||
#slopes: number[];
|
||||
|
||||
constructor(input: keyof SplineInputs, points: SplinePoint[]) {
|
||||
this.input = input;
|
||||
this.#locations = points.map((point) => point.at);
|
||||
this.#values = points.map((point) => point.value);
|
||||
// catmull-rom slopes between the neighbors, flat at the ends and where values are splines
|
||||
this.#slopes = points.map((point, i) => {
|
||||
if (point.slope !== undefined) return point.slope;
|
||||
const before = points[i - 1];
|
||||
const after = points[i + 1];
|
||||
if (!before || !after || typeof before.value !== "number" || typeof after.value !== "number") {
|
||||
return 0;
|
||||
}
|
||||
return (after.value - before.value) / (after.at - before.at);
|
||||
});
|
||||
}
|
||||
|
||||
get(inputs: SplineInputs): number {
|
||||
const x = inputs[this.input];
|
||||
const locations = this.#locations;
|
||||
const last = locations.length - 1;
|
||||
|
||||
if (x <= locations[0]) {
|
||||
return value_of(this.#values[0], inputs) + this.#slopes[0] * (x - locations[0]);
|
||||
}
|
||||
if (x >= locations[last]) {
|
||||
return value_of(this.#values[last], inputs) + this.#slopes[last] * (x - locations[last]);
|
||||
}
|
||||
|
||||
let i = 0;
|
||||
while (locations[i + 1] < x) i++;
|
||||
const x0 = locations[i];
|
||||
const x1 = locations[i + 1];
|
||||
const width = x1 - x0;
|
||||
const t = (x - x0) / width;
|
||||
const y0 = value_of(this.#values[i], inputs);
|
||||
const y1 = value_of(this.#values[i + 1], inputs);
|
||||
// hermite interpolation, written the way minecraft does it
|
||||
const a = this.#slopes[i] * width - (y1 - y0);
|
||||
const b = -this.#slopes[i + 1] * width + (y1 - y0);
|
||||
return y0 + (y1 - y0) * t + t * (1 - t) * (a + (b - a) * t);
|
||||
}
|
||||
}
|
||||
|
||||
function value_of(value: SplineValue, inputs: SplineInputs) {
|
||||
return typeof value === "number" ? value : value.get(inputs);
|
||||
}
|
||||
|
||||
// a spline through evenly spaced values of one input
|
||||
export function spline(input: keyof SplineInputs, at: number[], values: SplineValue[]) {
|
||||
return new Spline(input, at.map((location, i) => ({ at: location, value: values[i] })));
|
||||
}
|
||||
Reference in New Issue
Block a user