@aethervtt/shard-noise 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +3 -0
- package/dist/compile.d.ts +61 -0
- package/dist/compile.js +375 -0
- package/dist/gpu.d.ts +11 -0
- package/dist/gpu.js +15 -0
- package/dist/graph.d.ts +44 -0
- package/dist/graph.js +370 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +127 -0
- package/dist/kernel.d.ts +99 -0
- package/dist/kernel.js +263 -0
- package/dist/loader.d.ts +25 -0
- package/dist/loader.js +108 -0
- package/dist/methods.d.ts +9 -0
- package/dist/methods.js +119 -0
- package/dist/nodes.d.ts +74 -0
- package/dist/nodes.js +366 -0
- package/dist/noise-graph.d.ts +80 -0
- package/dist/noise-graph.js +258 -0
- package/dist/planet.d.ts +44 -0
- package/dist/planet.js +15 -0
- package/dist/plugin.d.ts +2 -0
- package/dist/plugin.js +18 -0
- package/dist/reference.d.ts +9 -0
- package/dist/reference.js +97 -0
- package/dist/sample.d.ts +67 -0
- package/dist/sample.js +232 -0
- package/dist/schema.d.ts +2 -0
- package/dist/schema.js +132 -0
- package/dist/stats.d.ts +58 -0
- package/dist/stats.js +164 -0
- package/dist/wgsl.d.ts +17 -0
- package/dist/wgsl.js +641 -0
- package/dist/worker.js +58 -0
- package/package.json +39 -0
- package/wasm/shard_noise.wasm +0 -0
- package/wasm/shard_noise_simd.wasm +0 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Patrick Dias
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { type ParsedGraph } from './graph.js';
|
|
2
|
+
import { type OriginTerms } from './kernel.js';
|
|
3
|
+
/** i32 words per instruction: `[op, dst, a, b, c, d, e, f, k, n, seed, salt]`. */
|
|
4
|
+
export declare const WIDTH = 12;
|
|
5
|
+
/** Registers 0–3 hold the local position (x, y, z, w). */
|
|
6
|
+
export declare const POSITION_REGS = 4;
|
|
7
|
+
/** Opcodes, matching `crates/shard-noise/src/program.rs`. */
|
|
8
|
+
export declare const OP: {
|
|
9
|
+
readonly CONST: 0;
|
|
10
|
+
readonly SOURCE: 1;
|
|
11
|
+
readonly ADD: 2;
|
|
12
|
+
readonly MUL: 3;
|
|
13
|
+
readonly MIN: 4;
|
|
14
|
+
readonly MAX: 5;
|
|
15
|
+
readonly LERP: 6;
|
|
16
|
+
readonly SELECT: 7;
|
|
17
|
+
readonly REMAP: 8;
|
|
18
|
+
readonly CLAMP: 9;
|
|
19
|
+
readonly CURVE: 10;
|
|
20
|
+
readonly TERRACE: 11;
|
|
21
|
+
readonly ABS: 12;
|
|
22
|
+
readonly POWER: 13;
|
|
23
|
+
readonly WARP: 14;
|
|
24
|
+
readonly SCALE_DISP: 15;
|
|
25
|
+
};
|
|
26
|
+
export declare const FRACTAL: {
|
|
27
|
+
readonly NONE: 0;
|
|
28
|
+
readonly FBM: 1;
|
|
29
|
+
readonly RIDGED: 2;
|
|
30
|
+
readonly BILLOW: 3;
|
|
31
|
+
};
|
|
32
|
+
/** Skew applied to a source's lattice coordinates: none, or the simplex skew for 2, 3, or 4 dims. */
|
|
33
|
+
export declare const SKEW: {
|
|
34
|
+
readonly NONE: 0;
|
|
35
|
+
readonly SIMPLEX2: 2;
|
|
36
|
+
readonly SIMPLEX3: 3;
|
|
37
|
+
readonly SIMPLEX4: 4;
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* A compiled graph: instructions in topological order, their constants in one Float32Array, and one
|
|
41
|
+
* origin term per source octave (how that octave's lattice position depends on the sample origin).
|
|
42
|
+
*/
|
|
43
|
+
export interface NoiseProgram {
|
|
44
|
+
readonly code: Int32Array;
|
|
45
|
+
readonly consts: Float32Array;
|
|
46
|
+
/** Registers the program uses (each holds one block of points). */
|
|
47
|
+
readonly registers: number;
|
|
48
|
+
/** The register holding the result. */
|
|
49
|
+
readonly result: number;
|
|
50
|
+
readonly dimensions: 2 | 3 | 4;
|
|
51
|
+
readonly terms: OriginTerms;
|
|
52
|
+
/** Origin records for sampling at absolute positions (origin zero). */
|
|
53
|
+
readonly zeroOrigins: Int32Array;
|
|
54
|
+
/** Instructions: `code.length / WIDTH`. */
|
|
55
|
+
readonly instructions: number;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Compiles a parsed graph (or one of its nodes, for previews) into a program. Throws the first
|
|
59
|
+
* error with the rest in `details`.
|
|
60
|
+
*/
|
|
61
|
+
export declare function compileGraph(graph: ParsedGraph, output?: string): NoiseProgram;
|
package/dist/compile.js
ADDED
|
@@ -0,0 +1,375 @@
|
|
|
1
|
+
import { hashSeed, ShardError } from "@aethervtt/shard-core";
|
|
2
|
+
import { pointer } from "./graph.js";
|
|
3
|
+
import { computeOrigins } from "./kernel.js";
|
|
4
|
+
import { CELL_DISTANCES, CELL_RETURNS, SOURCE_KINDS } from "./nodes.js";
|
|
5
|
+
const WIDTH = 12;
|
|
6
|
+
const POSITION_REGS = 4;
|
|
7
|
+
const OP = {
|
|
8
|
+
CONST: 0,
|
|
9
|
+
SOURCE: 1,
|
|
10
|
+
ADD: 2,
|
|
11
|
+
MUL: 3,
|
|
12
|
+
MIN: 4,
|
|
13
|
+
MAX: 5,
|
|
14
|
+
LERP: 6,
|
|
15
|
+
SELECT: 7,
|
|
16
|
+
REMAP: 8,
|
|
17
|
+
CLAMP: 9,
|
|
18
|
+
CURVE: 10,
|
|
19
|
+
TERRACE: 11,
|
|
20
|
+
ABS: 12,
|
|
21
|
+
POWER: 13,
|
|
22
|
+
WARP: 14,
|
|
23
|
+
SCALE_DISP: 15
|
|
24
|
+
};
|
|
25
|
+
const FRACTAL = { NONE: 0, FBM: 1, RIDGED: 2, BILLOW: 3 };
|
|
26
|
+
const SKEW = { NONE: 0, SIMPLEX2: 2, SIMPLEX3: 3, SIMPLEX4: 4 };
|
|
27
|
+
const ctxKey = (s, t, disp, salt) => `${s.join(",")}|${t.join(",")}|${disp}|${salt}`;
|
|
28
|
+
const ROOT = {
|
|
29
|
+
s: [1, 1, 1, 1],
|
|
30
|
+
t: [0, 0, 0, 0],
|
|
31
|
+
disp: -1,
|
|
32
|
+
salt: 0,
|
|
33
|
+
key: ctxKey([1, 1, 1, 1], [0, 0, 0, 0], -1, 0)
|
|
34
|
+
};
|
|
35
|
+
const fround = Math.fround;
|
|
36
|
+
const I32_LIMIT = 2 ** 31;
|
|
37
|
+
const SKEW_GROWTH = [1, 1, 1.52, 1, 1.62];
|
|
38
|
+
class Compiler {
|
|
39
|
+
code = [];
|
|
40
|
+
consts = [];
|
|
41
|
+
a = [];
|
|
42
|
+
b = [];
|
|
43
|
+
skew = [];
|
|
44
|
+
errors = [];
|
|
45
|
+
registers = POSITION_REGS;
|
|
46
|
+
memo = /* @__PURE__ */ new Map();
|
|
47
|
+
constRegs = /* @__PURE__ */ new Map();
|
|
48
|
+
ids = /* @__PURE__ */ new WeakMap();
|
|
49
|
+
nextId = 0;
|
|
50
|
+
stack = [];
|
|
51
|
+
graph;
|
|
52
|
+
constructor(graph) {
|
|
53
|
+
this.graph = graph;
|
|
54
|
+
}
|
|
55
|
+
reg(count = 1) {
|
|
56
|
+
const r = this.registers;
|
|
57
|
+
this.registers += count;
|
|
58
|
+
return r;
|
|
59
|
+
}
|
|
60
|
+
konst(values) {
|
|
61
|
+
const k = this.consts.length;
|
|
62
|
+
for (const v of values) this.consts.push(fround(v));
|
|
63
|
+
return k;
|
|
64
|
+
}
|
|
65
|
+
emit(op, dst, ins = [], k = 0, n = 0, seed = 0, salt = 0) {
|
|
66
|
+
const w = [op, dst, -1, -1, -1, -1, -1, -1, k, n, seed | 0, salt | 0];
|
|
67
|
+
for (let i = 0; i < ins.length; i++) w[2 + i] = ins[i];
|
|
68
|
+
this.code.push(...w);
|
|
69
|
+
}
|
|
70
|
+
constant(value) {
|
|
71
|
+
const v = fround(value);
|
|
72
|
+
let r = this.constRegs.get(v);
|
|
73
|
+
if (r === void 0) {
|
|
74
|
+
r = this.reg();
|
|
75
|
+
this.emit(OP.CONST, r, [], this.konst([v]));
|
|
76
|
+
this.constRegs.set(v, r);
|
|
77
|
+
}
|
|
78
|
+
return r;
|
|
79
|
+
}
|
|
80
|
+
input(input, ctx) {
|
|
81
|
+
if (input.kind === "const") return this.constant(input.value);
|
|
82
|
+
if (input.kind === "ref") return this.named(input.name, ctx);
|
|
83
|
+
return this.node(input.node, ctx);
|
|
84
|
+
}
|
|
85
|
+
named(name, ctx) {
|
|
86
|
+
const key = `n:${name}@${ctx.key}`;
|
|
87
|
+
const hit = this.memo.get(key);
|
|
88
|
+
if (hit !== void 0) return hit;
|
|
89
|
+
const node = this.graph.nodes.get(name);
|
|
90
|
+
this.stack.push(name);
|
|
91
|
+
const r = this.build(node, ctx);
|
|
92
|
+
this.stack.pop();
|
|
93
|
+
this.memo.set(key, r);
|
|
94
|
+
return r;
|
|
95
|
+
}
|
|
96
|
+
node(node, ctx) {
|
|
97
|
+
let id = this.ids.get(node);
|
|
98
|
+
if (id === void 0) {
|
|
99
|
+
id = this.nextId++;
|
|
100
|
+
this.ids.set(node, id);
|
|
101
|
+
}
|
|
102
|
+
const key = `i:${id}@${ctx.key}`;
|
|
103
|
+
const hit = this.memo.get(key);
|
|
104
|
+
if (hit !== void 0) return hit;
|
|
105
|
+
const r = this.build(node, ctx);
|
|
106
|
+
this.memo.set(key, r);
|
|
107
|
+
return r;
|
|
108
|
+
}
|
|
109
|
+
build(node, ctx) {
|
|
110
|
+
const p = node.params;
|
|
111
|
+
const inp = (name) => this.input(p[name], ctx);
|
|
112
|
+
const num = (name) => p[name];
|
|
113
|
+
switch (node.type) {
|
|
114
|
+
case "value":
|
|
115
|
+
case "perlin":
|
|
116
|
+
case "simplex":
|
|
117
|
+
case "cellular":
|
|
118
|
+
return this.source(node, node.type, FRACTAL.NONE, ctx);
|
|
119
|
+
case "fbm":
|
|
120
|
+
return this.source(node, p.source, FRACTAL.FBM, ctx);
|
|
121
|
+
case "ridged":
|
|
122
|
+
return this.source(node, p.source, FRACTAL.RIDGED, ctx);
|
|
123
|
+
case "billow":
|
|
124
|
+
return this.source(node, p.source, FRACTAL.BILLOW, ctx);
|
|
125
|
+
case "add":
|
|
126
|
+
case "multiply":
|
|
127
|
+
case "min":
|
|
128
|
+
case "max": {
|
|
129
|
+
const op = { add: OP.ADD, multiply: OP.MUL, min: OP.MIN, max: OP.MAX }[node.type];
|
|
130
|
+
const inputs = p.inputs.map((i) => this.input(i, ctx));
|
|
131
|
+
let acc = inputs[0];
|
|
132
|
+
for (let i = 1; i < inputs.length; i++) {
|
|
133
|
+
const dst = this.reg();
|
|
134
|
+
this.emit(op, dst, [acc, inputs[i]]);
|
|
135
|
+
acc = dst;
|
|
136
|
+
}
|
|
137
|
+
return acc;
|
|
138
|
+
}
|
|
139
|
+
case "lerp": {
|
|
140
|
+
const [a, b, t] = [inp("a"), inp("b"), inp("t")];
|
|
141
|
+
const dst = this.reg();
|
|
142
|
+
this.emit(OP.LERP, dst, [a, b, t]);
|
|
143
|
+
return dst;
|
|
144
|
+
}
|
|
145
|
+
case "select": {
|
|
146
|
+
const [a, b, c] = [inp("a"), inp("b"), inp("control")];
|
|
147
|
+
const th = num("threshold");
|
|
148
|
+
const fo = num("falloff");
|
|
149
|
+
const dst = this.reg();
|
|
150
|
+
this.emit(
|
|
151
|
+
OP.SELECT,
|
|
152
|
+
dst,
|
|
153
|
+
[a, b, c],
|
|
154
|
+
this.konst([th, fo, th - fo, fo > 0 ? 1 / (2 * fo) : 0])
|
|
155
|
+
);
|
|
156
|
+
return dst;
|
|
157
|
+
}
|
|
158
|
+
case "remap": {
|
|
159
|
+
const a = inp("input");
|
|
160
|
+
const [f0, f1] = p.from;
|
|
161
|
+
const [t0, t1] = p.to;
|
|
162
|
+
const scale = f1 === f0 ? 0 : (t1 - t0) / (f1 - f0);
|
|
163
|
+
const dst = this.reg();
|
|
164
|
+
this.emit(
|
|
165
|
+
OP.REMAP,
|
|
166
|
+
dst,
|
|
167
|
+
[a],
|
|
168
|
+
this.konst([f0, t0, scale, Math.min(t0, t1), Math.max(t0, t1)]),
|
|
169
|
+
p.clamp ? 1 : 0
|
|
170
|
+
);
|
|
171
|
+
return dst;
|
|
172
|
+
}
|
|
173
|
+
case "clamp": {
|
|
174
|
+
const a = inp("input");
|
|
175
|
+
const dst = this.reg();
|
|
176
|
+
this.emit(OP.CLAMP, dst, [a], this.konst([num("min"), num("max")]));
|
|
177
|
+
return dst;
|
|
178
|
+
}
|
|
179
|
+
case "curve": {
|
|
180
|
+
const a = inp("input");
|
|
181
|
+
const pts = p.points;
|
|
182
|
+
const values = [];
|
|
183
|
+
for (let i = 0; i + 1 < pts.length; i++) {
|
|
184
|
+
const [x0, y0] = pts[i];
|
|
185
|
+
const [x1, y1] = pts[i + 1];
|
|
186
|
+
const fx0 = fround(x0);
|
|
187
|
+
const fy0 = fround(y0);
|
|
188
|
+
values.push(fx0, fy0, 1 / (fround(x1) - fx0), fround(y1) - fy0);
|
|
189
|
+
}
|
|
190
|
+
const dst = this.reg();
|
|
191
|
+
this.emit(OP.CURVE, dst, [a], this.konst(values), pts.length);
|
|
192
|
+
return dst;
|
|
193
|
+
}
|
|
194
|
+
case "terrace": {
|
|
195
|
+
const a = inp("input");
|
|
196
|
+
const [lo, hi] = p.range;
|
|
197
|
+
const steps = num("steps");
|
|
198
|
+
const span = hi - lo || 1;
|
|
199
|
+
const dst = this.reg();
|
|
200
|
+
this.emit(
|
|
201
|
+
OP.TERRACE,
|
|
202
|
+
dst,
|
|
203
|
+
[a],
|
|
204
|
+
this.konst([lo, steps / span, 1 / Math.max(1 - num("sharpness"), 1e-4), span / steps])
|
|
205
|
+
);
|
|
206
|
+
return dst;
|
|
207
|
+
}
|
|
208
|
+
case "abs": {
|
|
209
|
+
const a = inp("input");
|
|
210
|
+
const dst = this.reg();
|
|
211
|
+
this.emit(OP.ABS, dst, [a]);
|
|
212
|
+
return dst;
|
|
213
|
+
}
|
|
214
|
+
case "power": {
|
|
215
|
+
const a = inp("input");
|
|
216
|
+
const dst = this.reg();
|
|
217
|
+
this.emit(OP.POWER, dst, [a], this.konst([num("exponent")]));
|
|
218
|
+
return dst;
|
|
219
|
+
}
|
|
220
|
+
case "constant":
|
|
221
|
+
return this.constant(num("value"));
|
|
222
|
+
case "warp": {
|
|
223
|
+
const by = p.by;
|
|
224
|
+
const channels = [];
|
|
225
|
+
for (let axis = 0; axis < this.graph.dimensions; axis++) {
|
|
226
|
+
const salt = axis === 0 ? ctx.salt : hashSeed(ctx.salt, axis + 1) || 1;
|
|
227
|
+
channels.push(
|
|
228
|
+
this.input(by, axis === 0 ? ctx : this.withCtx(ctx, ctx.s, ctx.t, ctx.disp, salt))
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
while (channels.length < 4) channels.push(-1);
|
|
232
|
+
const disp = this.reg(4);
|
|
233
|
+
this.emit(OP.WARP, disp, [ctx.disp, ...channels], this.konst([num("amount")]));
|
|
234
|
+
return this.input(p.input, this.withCtx(ctx, ctx.s, ctx.t, disp, ctx.salt));
|
|
235
|
+
}
|
|
236
|
+
case "scale": {
|
|
237
|
+
const by = p.by;
|
|
238
|
+
const s = ctx.s.map((v, i) => v * by[i]);
|
|
239
|
+
const t = ctx.t.map((v, i) => v * by[i]);
|
|
240
|
+
let disp = ctx.disp;
|
|
241
|
+
if (disp >= 0) {
|
|
242
|
+
const dst = this.reg(4);
|
|
243
|
+
this.emit(OP.SCALE_DISP, dst, [disp], this.konst(by));
|
|
244
|
+
disp = dst;
|
|
245
|
+
}
|
|
246
|
+
return this.input(p.input, this.withCtx(ctx, s, t, disp, ctx.salt));
|
|
247
|
+
}
|
|
248
|
+
case "translate": {
|
|
249
|
+
const by = p.by;
|
|
250
|
+
const t = ctx.t.map((v, i) => v + by[i]);
|
|
251
|
+
return this.input(p.input, this.withCtx(ctx, ctx.s, t, ctx.disp, ctx.salt));
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
throw new ShardError("noise/unknown-type", `No compiler for node type "${node.type}"`, {
|
|
255
|
+
path: node.path
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
withCtx(_ctx, s, t, disp, salt) {
|
|
259
|
+
return { s, t, disp, salt, key: ctxKey(s, t, disp, salt) };
|
|
260
|
+
}
|
|
261
|
+
source(node, kind, fractal, ctx) {
|
|
262
|
+
const p = node.params;
|
|
263
|
+
const dims = p.dims;
|
|
264
|
+
const octaves = fractal === FRACTAL.NONE ? 1 : p.octaves;
|
|
265
|
+
const freq = p.frequency;
|
|
266
|
+
const lacunarity = fractal === FRACTAL.NONE ? 1 : p.lacunarity;
|
|
267
|
+
const gain = fractal === FRACTAL.NONE ? 1 : p.gain;
|
|
268
|
+
const kindIndex = SOURCE_KINDS.indexOf(kind);
|
|
269
|
+
const skew = kind === "simplex" ? dims : SKEW.NONE;
|
|
270
|
+
const metric = CELL_DISTANCES.indexOf(p.distance ?? "euclidean");
|
|
271
|
+
const ret = CELL_RETURNS.indexOf(p.return ?? "f1");
|
|
272
|
+
let ampSum = 0;
|
|
273
|
+
const amps = [];
|
|
274
|
+
for (let o = 0; o < octaves; o++) {
|
|
275
|
+
const amp = fround(gain ** o);
|
|
276
|
+
amps.push(amp);
|
|
277
|
+
ampSum += amp;
|
|
278
|
+
}
|
|
279
|
+
const values = [p.jitter ?? 1, 1 / ampSum];
|
|
280
|
+
const originBase = this.skew.length;
|
|
281
|
+
for (let o = 0; o < octaves; o++) {
|
|
282
|
+
const f = freq * lacunarity ** o;
|
|
283
|
+
for (let axis = 0; axis < 4; axis++) {
|
|
284
|
+
const a = axis < dims ? fround(f * ctx.s[axis]) : 0;
|
|
285
|
+
values.push(a);
|
|
286
|
+
this.a.push(a);
|
|
287
|
+
this.b.push(axis < dims ? f * ctx.t[axis] : 0);
|
|
288
|
+
}
|
|
289
|
+
values.push(f, amps[o]);
|
|
290
|
+
this.skew.push(skew);
|
|
291
|
+
this.checkRange(node, dims, skew, originBase + o);
|
|
292
|
+
}
|
|
293
|
+
const dst = this.reg();
|
|
294
|
+
this.emit(
|
|
295
|
+
OP.SOURCE,
|
|
296
|
+
dst,
|
|
297
|
+
[ctx.disp, originBase, kindIndex, dims, fractal, metric | ret << 4],
|
|
298
|
+
this.konst(values),
|
|
299
|
+
octaves,
|
|
300
|
+
p.seed >>> 0,
|
|
301
|
+
ctx.salt
|
|
302
|
+
);
|
|
303
|
+
return dst;
|
|
304
|
+
}
|
|
305
|
+
/** With an extent, every lattice coordinate must fit an i32 at that distance. */
|
|
306
|
+
checkRange(node, dims, skew, term) {
|
|
307
|
+
const extent = this.graph.extent;
|
|
308
|
+
if (extent === void 0) return;
|
|
309
|
+
let worst = 0;
|
|
310
|
+
for (let axis = 0; axis < dims; axis++) {
|
|
311
|
+
worst = Math.max(
|
|
312
|
+
worst,
|
|
313
|
+
Math.abs(this.a[term * 4 + axis]) * extent + Math.abs(this.b[term * 4 + axis])
|
|
314
|
+
);
|
|
315
|
+
}
|
|
316
|
+
worst *= SKEW_GROWTH[skew];
|
|
317
|
+
if (worst >= I32_LIMIT) {
|
|
318
|
+
const where = this.stack.length > 0 ? `/nodes/${pointer(this.stack[this.stack.length - 1])}` : node.path;
|
|
319
|
+
this.errors.push(
|
|
320
|
+
new ShardError(
|
|
321
|
+
"noise/frequency-too-high",
|
|
322
|
+
`A frequency in "${node.type}" reaches lattice cell ${worst.toExponential(2)} at extent ${extent}, past what an i32 cell holds (2\xB3\xB9)`,
|
|
323
|
+
{
|
|
324
|
+
path: node.path.startsWith("/nodes/") ? node.path : where,
|
|
325
|
+
hint: "Lower the frequency or octaves: features that fine are invisible anywhere they apply at this extent."
|
|
326
|
+
}
|
|
327
|
+
)
|
|
328
|
+
);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
function compileGraph(graph, output = graph.output) {
|
|
333
|
+
if (!graph.nodes.has(output)) {
|
|
334
|
+
throw new ShardError("noise/unknown-node", `"${output}" isn't a node in this graph`, {
|
|
335
|
+
path: "/output",
|
|
336
|
+
hint: `Nodes: ${[...graph.nodes.keys()].join(", ")}.`
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
const c = new Compiler(graph);
|
|
340
|
+
const result = c.named(output, ROOT);
|
|
341
|
+
if (c.errors.length > 0) {
|
|
342
|
+
const first = c.errors[0];
|
|
343
|
+
throw new ShardError(first.code, first.message, {
|
|
344
|
+
path: first.path,
|
|
345
|
+
hint: first.hint,
|
|
346
|
+
details: c.errors
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
const terms = {
|
|
350
|
+
a: new Float64Array(c.a),
|
|
351
|
+
b: new Float64Array(c.b),
|
|
352
|
+
skew: new Uint8Array(c.skew)
|
|
353
|
+
};
|
|
354
|
+
const zeroOrigins = new Int32Array(terms.skew.length * 8);
|
|
355
|
+
computeOrigins(terms, ZERO, zeroOrigins);
|
|
356
|
+
return {
|
|
357
|
+
code: new Int32Array(c.code),
|
|
358
|
+
consts: new Float32Array(c.consts),
|
|
359
|
+
registers: c.registers,
|
|
360
|
+
result,
|
|
361
|
+
dimensions: graph.dimensions,
|
|
362
|
+
terms,
|
|
363
|
+
zeroOrigins,
|
|
364
|
+
instructions: c.code.length / WIDTH
|
|
365
|
+
};
|
|
366
|
+
}
|
|
367
|
+
const ZERO = new Float64Array(4);
|
|
368
|
+
export {
|
|
369
|
+
FRACTAL,
|
|
370
|
+
OP,
|
|
371
|
+
POSITION_REGS,
|
|
372
|
+
SKEW,
|
|
373
|
+
WIDTH,
|
|
374
|
+
compileGraph
|
|
375
|
+
};
|
package/dist/gpu.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ShaderLibrary } from '@aethervtt/shard-shader';
|
|
2
|
+
import type { NoiseGraph } from './noise-graph.js';
|
|
3
|
+
/** Module path of the shared sources and helpers. */
|
|
4
|
+
export declare const NOISE_LIBRARY_PATH = "shard::noise";
|
|
5
|
+
/** Registers `shard::noise` once. */
|
|
6
|
+
export declare function registerNoiseLibrary(library: ShaderLibrary): void;
|
|
7
|
+
/**
|
|
8
|
+
* Registers a graph's module (`graph.module`, exporting `noise_<name>` and `noise_<name>_at`).
|
|
9
|
+
* Registering a changed graph relinks every shader that imports it; an unchanged one is a no-op.
|
|
10
|
+
*/
|
|
11
|
+
export declare function registerNoiseGraph(library: ShaderLibrary, graph: NoiseGraph, origin?: string): void;
|
package/dist/gpu.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { NOISE_LIBRARY } from "./wgsl.js";
|
|
2
|
+
const NOISE_LIBRARY_PATH = "shard::noise";
|
|
3
|
+
function registerNoiseLibrary(library) {
|
|
4
|
+
if (!library.has(NOISE_LIBRARY_PATH))
|
|
5
|
+
library.register(NOISE_LIBRARY_PATH, NOISE_LIBRARY, "@aethervtt/shard-noise");
|
|
6
|
+
}
|
|
7
|
+
function registerNoiseGraph(library, graph, origin) {
|
|
8
|
+
registerNoiseLibrary(library);
|
|
9
|
+
library.register(graph.module, graph.wgsl, origin ?? graph.module);
|
|
10
|
+
}
|
|
11
|
+
export {
|
|
12
|
+
NOISE_LIBRARY_PATH,
|
|
13
|
+
registerNoiseGraph,
|
|
14
|
+
registerNoiseLibrary
|
|
15
|
+
};
|
package/dist/graph.d.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { ShardError } from '@aethervtt/shard-core';
|
|
2
|
+
import { type NodeDef } from './nodes.js';
|
|
3
|
+
/** Where a node input comes from: another named node, an inline node, or a number. */
|
|
4
|
+
export type GraphInput = {
|
|
5
|
+
readonly kind: 'ref';
|
|
6
|
+
readonly name: string;
|
|
7
|
+
readonly path: string;
|
|
8
|
+
} | {
|
|
9
|
+
readonly kind: 'node';
|
|
10
|
+
readonly node: GraphNode;
|
|
11
|
+
} | {
|
|
12
|
+
readonly kind: 'const';
|
|
13
|
+
readonly value: number;
|
|
14
|
+
};
|
|
15
|
+
export type ParamValue = number | boolean | string | readonly number[] | readonly (readonly [number, number])[] | GraphInput | readonly GraphInput[];
|
|
16
|
+
/** A node with its parameters filled in from the table's defaults. */
|
|
17
|
+
export interface GraphNode {
|
|
18
|
+
readonly type: string;
|
|
19
|
+
readonly def: NodeDef;
|
|
20
|
+
readonly params: Readonly<Record<string, ParamValue>>;
|
|
21
|
+
/** JSON pointer to the node's body (`/nodes/mask/remap`). */
|
|
22
|
+
readonly path: string;
|
|
23
|
+
}
|
|
24
|
+
export interface ParsedGraph {
|
|
25
|
+
readonly output: string;
|
|
26
|
+
/** 3 unless the file says 2 or 4. A 4D graph reads w; 4D sources need one. */
|
|
27
|
+
readonly dimensions: 2 | 3 | 4;
|
|
28
|
+
/** Largest distance from the origin the graph is sampled at (a planet's radius), if set. */
|
|
29
|
+
readonly extent: number | undefined;
|
|
30
|
+
readonly description: string | undefined;
|
|
31
|
+
readonly nodes: ReadonlyMap<string, GraphNode>;
|
|
32
|
+
}
|
|
33
|
+
/** Escapes a JSON pointer segment. */
|
|
34
|
+
export declare function pointer(segment: string): string;
|
|
35
|
+
/**
|
|
36
|
+
* Parses and validates a `*.noise.json` graph. Returns every error found (each with a JSON
|
|
37
|
+
* pointer), and the graph when there are none.
|
|
38
|
+
*/
|
|
39
|
+
export declare function parseGraph(json: unknown): {
|
|
40
|
+
graph?: ParsedGraph;
|
|
41
|
+
errors: ShardError[];
|
|
42
|
+
};
|
|
43
|
+
/** Calls `f` on every node input, inline nodes included. */
|
|
44
|
+
export declare function forEachInput(node: GraphNode, f: (input: GraphInput) => void): void;
|