Skip to content

Commit

Permalink
feat(lib): add Worley noise generator
Browse files Browse the repository at this point in the history
  • Loading branch information
rektdeckard committed Dec 23, 2023
1 parent 889aeac commit 2af58c8
Show file tree
Hide file tree
Showing 5 changed files with 141 additions and 9 deletions.
50 changes: 48 additions & 2 deletions example/noise.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,23 @@ import { Noise, NoiseGenerator, uncheckedLerp } from "../src";

const { button, input, label, div, canvas } = van.tags;

function toSaturated(v: number): { r: number; g: number; b: number } {
// v: [0, 255]
// red: 0 -> 255, 0, 0
// yellow: 51
// green: 102
// cyan: 153
// blue: 204
// magenta: 255
const r = 255 - v;
const g = Math.abs(64 - v) * 4;
const b = Math.abs(128 - v) * 4;
return { r, g, b };
}

export default function Noises() {
const WIDTH = 800;
const HEIGHT = 600;
const WIDTH = 400;
const HEIGHT = 400;
const DEFAULT_FREQ = 5;

const c = canvas({ width: WIDTH, height: HEIGHT });
Expand Down Expand Up @@ -144,6 +158,27 @@ export default function Noises() {
}
}

let sign = 1;
function worleyNoise() {
g1.fill(d, {
z: t,
freq: freq.val,
set: ({ x, y, v }) => {
const cell = (x + y * d.width) * 4;
d.data[cell] = d.data[cell + 1] = d.data[cell + 2] = 255 - v * 2;
d.data[cell + 3] = 255;
},
});
ctx.putImageData(d, 0, 0);
t += 0.01 * sign;
if (t > 1 || t < 0) {
sign *= -1;
}
if (run.val) {
raf = requestAnimationFrame(worleyNoise);
}
}

fn();

return div(
Expand Down Expand Up @@ -248,6 +283,17 @@ export default function Noises() {
},
},
"Color (normalized)"
),
button(
{
onclick: () => {
g1 = new Noise.Worley(freq.val);
cancelAnimationFrame(raf);
fn = worleyNoise;
fn();
},
},
"Worley"
)
)
);
Expand Down
4 changes: 4 additions & 0 deletions src/data/KDTree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,10 @@ export class KDTree<K extends number> implements Iterable<Vec<K>> {
return this.nearestNeighbor(point).distance === 0;
}

size(): number {
return this.#data.length;
}

nearestNeighbor(point: Vec<K>): {
point: Vec<K> | null;
distance: number;
Expand Down
66 changes: 59 additions & 7 deletions src/math/noise.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { KDTree } from "../data";
import { Range } from "./range";
import { uncheckedLerp } from "./transforms";

const MAX_ENTROPY = 2 ** 16;
Expand Down Expand Up @@ -90,7 +92,7 @@ function fillConfig(
}

export abstract class NoiseGenerator {
abstract seed(seed: number): this;
abstract seed(init: number): this;
abstract xy(x: number, y: number): number;
abstract xyz(x: number, y: number, z: number): number;
abstract fill(target: NoiseTarget, options?: NoiseFillOptions): void;
Expand Down Expand Up @@ -580,11 +582,61 @@ class Simplex implements NoiseGenerator {
}
}

// class Worley implements NoiseGenerator {
// constructor(seed?: number) {
// void seed;
// }
// }
class Worley implements NoiseGenerator {
#tree: KDTree<3>;

constructor(seed: number = 10) {
this.#tree = this.#generatePoints(seed);
}

#generatePoints(size: number): KDTree<3> {
const points = Range.of<[number, number, number]>(size, () => [
Math.random(),
Math.random(),
Math.random(),
]);
return new KDTree<3>(points);
}

seed(size: number): this {
this.#tree = this.#generatePoints(size);
return this;
}

xy(x: number, y: number): number {
return this.xyz(x, y, 0);
}

xyz(x: number, y: number, z: number): number {
const nearest = this.#tree.nearestNeighbor([x, y, z])!.distance!;
return nearest;
}

fill(target: NoiseTarget, options?: NoiseFillOptions | undefined): void {
// call into WASM with buffer

const { width, height, freq, setCell } = fillConfig(target, options);
if (freq !== this.#tree.size() - 1) {
this.seed(freq);
console.log(freq, this.#tree.size() - 1);
}
const d = Math.min(width, height);

for (let x = 0; x < width; x++) {
for (let y = 0; y < height; y++) {
const sx = x / d;
const sy = y / d;
const v =
options?.z !== undefined
? this.xyz(sx, sy, options?.z)
: this.xy(sx, sy);

const scaled = uncheckedLerp(0, 255, v);
setCell({ x, y, z: options?.z ?? 0, v: scaled });
}
}
}
}

class Color implements NoiseGenerator {
constructor(seed?: number) {
Expand Down Expand Up @@ -658,6 +710,6 @@ class Color implements NoiseGenerator {
export const Noise = {
Perlin,
Simplex,
// Worley,
Worley,
Color,
} as const;
15 changes: 15 additions & 0 deletions test/data/KDTree.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,21 @@ describe("KDTree", () => {
});
});

describe("size", () => {
it("reports correct size", () => {
const data = [
[0, 7, 15],
[1, 1, 0],
[6, 9, 420],
[8, 40, -12],
[31, 9, 33],
];

const tree = new KDTree(data);
expect(tree.size()).toBe(5);
});
});

describe("nearestNeighbor", () => {
it("can find nearest neighbor in a simple 2d tree", () => {
const data = [
Expand Down
15 changes: 15 additions & 0 deletions test/math/noise.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -268,4 +268,19 @@ describe("Noise", () => {
});
});
});

describe("Worley", () => {
describe("construct", () => {
it("can construct a Worley generator", () => {
const g = new Noise.Worley();
expect(g).toBeDefined();
});

it.skip("can construct with a seed", () => {
const g = new Noise.Worley(13);
const f = new Noise.Worley(13);
expect(f.xy(2, 2)).toBe(g.xy(2, 2));
});
});
});
});

0 comments on commit 2af58c8

Please sign in to comment.