@aethervtt/shard-particles 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.
@@ -0,0 +1,33 @@
1
+ import { ShardError } from '@aethervtt/shard-core';
2
+ /**
3
+ * Value forms in effect files:
4
+ * - scalar: `3` or a seeded range `[2, 5]`
5
+ * - color: `"#9ad4ff"` (sRGB hex, optional alpha), `[r, g, b, a]` (linear), or a range of two
6
+ * - curve: `[[t, v], …]` over normalized life, piecewise linear, or a constant number
7
+ * - gradient: `[[t, color, alpha?], …]` over normalized life
8
+ */
9
+ export type Scalar = number | [number, number];
10
+ export type Rgba = [number, number, number, number];
11
+ /** A problem in an effect file, with a JSON pointer to it. */
12
+ export declare function invalid(path: string, message: string, hint?: string): ShardError;
13
+ export declare function scalar(json: unknown, path: string, errors: ShardError[], fallback: number): [number, number];
14
+ /** A color or a range of two colors (each picked per particle). */
15
+ export declare function color(json: unknown, path: string, errors: ShardError[]): [Rgba, Rgba];
16
+ /** Piecewise-linear keys over normalized life: [t, v] with t increasing in [0, 1]. */
17
+ export declare function curve(json: unknown, path: string, errors: ShardError[]): [number, number][];
18
+ /** Color keys over normalized life: [t, color, alpha?]. */
19
+ export declare function gradient(json: unknown, path: string, errors: ShardError[]): [number, Rgba][];
20
+ /** PCG hash (u32 → u32): the same bits on the CPU and in the shaders. */
21
+ export declare function pcg(v: number): number;
22
+ /** The k-th random number in [0, 1) of a particle's stream. */
23
+ export declare function rand(seed: number, k: number): number;
24
+ export declare const WGSL_RANDOM = "\nfn pcg(v: u32) -> u32 {\n let state = v * 747796405u + 2891336453u;\n let word = ((state >> ((state >> 28u) + 4u)) ^ state) * 277803737u;\n return (word >> 22u) ^ word;\n}\n\nfn rand(seed: u32, k: u32) -> f32 {\n return f32(pcg(seed ^ pcg(k))) / 4294967296.0;\n}";
25
+ export declare function evalCurve(keys: readonly [number, number][], t: number): number;
26
+ declare const f: (v: number) => string;
27
+ /** A WGSL function `fn <name>(t: f32) -> f32` for a curve, keys inlined. */
28
+ export declare function wgslCurve(name: string, keys: readonly [number, number][]): string;
29
+ /** A WGSL function `fn <name>(t: f32) -> vec4f` for a gradient. */
30
+ export declare function wgslGradient(name: string, keys: readonly [number, Rgba][]): string;
31
+ export declare function evalGradient(keys: readonly [number, Rgba][], t: number, out: Rgba): Rgba;
32
+ export declare const wgslFloat: typeof f;
33
+ export {};
package/dist/values.js ADDED
@@ -0,0 +1,199 @@
1
+ import { ShardError } from "@aethervtt/shard-core";
2
+ function invalid(path, message, hint) {
3
+ return new ShardError("particles/invalid-effect", message, { path, hint });
4
+ }
5
+ function scalar(json, path, errors, fallback) {
6
+ if (json === void 0) return [fallback, fallback];
7
+ if (typeof json === "number" && Number.isFinite(json)) return [json, json];
8
+ if (Array.isArray(json) && json.length === 2 && json.every((v) => typeof v === "number" && Number.isFinite(v))) {
9
+ return [json[0], json[1]];
10
+ }
11
+ errors.push(invalid(path, "Expected a number or a [min, max] range", "Examples: 4, [0.3, 0.6]."));
12
+ return [fallback, fallback];
13
+ }
14
+ const toLinear = (c) => c <= 0.04045 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4;
15
+ function singleColor(json) {
16
+ if (typeof json === "string") {
17
+ const short = /^#([0-9a-f])([0-9a-f])([0-9a-f])([0-9a-f])?$/i.exec(json);
18
+ const hex = short ? `#${short.slice(1).map((c) => (c ?? "") + (c ?? "")).join("")}` : json;
19
+ const m = /^#([0-9a-f]{6})([0-9a-f]{2})?$/i.exec(hex);
20
+ if (!m) return void 0;
21
+ const n = Number.parseInt(m[1], 16);
22
+ const a = m[2] ? Number.parseInt(m[2], 16) / 255 : 1;
23
+ return [
24
+ toLinear((n >> 16 & 255) / 255),
25
+ toLinear((n >> 8 & 255) / 255),
26
+ toLinear((n & 255) / 255),
27
+ a
28
+ ];
29
+ }
30
+ if (Array.isArray(json) && (json.length === 3 || json.length === 4) && json.every((v) => typeof v === "number")) {
31
+ return [json[0], json[1], json[2], json[3] ?? 1];
32
+ }
33
+ return void 0;
34
+ }
35
+ function color(json, path, errors) {
36
+ if (json === void 0)
37
+ return [
38
+ [1, 1, 1, 1],
39
+ [1, 1, 1, 1]
40
+ ];
41
+ const one = singleColor(json);
42
+ if (one) return [one, one];
43
+ if (Array.isArray(json) && json.length === 2) {
44
+ const a = singleColor(json[0]);
45
+ const b = singleColor(json[1]);
46
+ if (a && b) return [a, b];
47
+ }
48
+ errors.push(
49
+ invalid(
50
+ path,
51
+ "Expected a color",
52
+ 'Colors: "#rrggbb" (sRGB), [r, g, b, a] (linear), or a range ["#fff", "#f80"].'
53
+ )
54
+ );
55
+ return [
56
+ [1, 1, 1, 1],
57
+ [1, 1, 1, 1]
58
+ ];
59
+ }
60
+ function curve(json, path, errors) {
61
+ if (typeof json === "number") return [[0, json]];
62
+ if (Array.isArray(json) && json.length > 0 && json.every((k) => Array.isArray(k) && k.length === 2 && k.every((v) => typeof v === "number"))) {
63
+ const keys = json;
64
+ for (let i = 1; i < keys.length; i++) {
65
+ if (keys[i][0] < keys[i - 1][0]) {
66
+ errors.push(invalid(`${path}/${i}/0`, "Curve keys must be in increasing time order"));
67
+ }
68
+ }
69
+ return keys;
70
+ }
71
+ errors.push(
72
+ invalid(
73
+ path,
74
+ "Expected a curve: [[t, value], \u2026] with t from 0 to 1",
75
+ "Example: [[0, 0.5], [0.2, 1], [1, 2]]."
76
+ )
77
+ );
78
+ return [[0, 1]];
79
+ }
80
+ function gradient(json, path, errors) {
81
+ if (Array.isArray(json) && json.length > 0) {
82
+ const out = [];
83
+ json.forEach((k, i) => {
84
+ const c = Array.isArray(k) ? singleColor(k[1]) : void 0;
85
+ if (!Array.isArray(k) || typeof k[0] !== "number" || !c) {
86
+ errors.push(
87
+ invalid(
88
+ `${path}/${i}`,
89
+ "Expected a gradient key: [t, color, alpha?]",
90
+ 'Example: [0.4, "#4aa3ff", 0.7].'
91
+ )
92
+ );
93
+ return;
94
+ }
95
+ if (typeof k[2] === "number") c[3] = k[2];
96
+ out.push([k[0], c]);
97
+ });
98
+ if (out.length > 0) return out;
99
+ }
100
+ errors.push(invalid(path, "Expected a gradient: [[t, color, alpha?], \u2026]"));
101
+ return [[0, [1, 1, 1, 1]]];
102
+ }
103
+ function pcg(v) {
104
+ const state = Math.imul(v >>> 0, 747796405) + 2891336453 >>> 0;
105
+ const word = Math.imul((state >>> (state >>> 28) + 4 ^ state) >>> 0, 277803737) >>> 0;
106
+ return (word >>> 22 ^ word) >>> 0;
107
+ }
108
+ function rand(seed, k) {
109
+ return pcg((seed ^ pcg(k)) >>> 0) / 4294967296;
110
+ }
111
+ const WGSL_RANDOM = `
112
+ fn pcg(v: u32) -> u32 {
113
+ let state = v * 747796405u + 2891336453u;
114
+ let word = ((state >> ((state >> 28u) + 4u)) ^ state) * 277803737u;
115
+ return (word >> 22u) ^ word;
116
+ }
117
+
118
+ fn rand(seed: u32, k: u32) -> f32 {
119
+ return f32(pcg(seed ^ pcg(k))) / 4294967296.0;
120
+ }`;
121
+ function evalCurve(keys, t) {
122
+ if (t <= keys[0][0]) return keys[0][1];
123
+ for (let i = 1; i < keys.length; i++) {
124
+ const [t1, v1] = keys[i];
125
+ if (t <= t1) {
126
+ const [t0, v0] = keys[i - 1];
127
+ return v0 + (v1 - v0) * (t - t0) / Math.max(t1 - t0, 1e-9);
128
+ }
129
+ }
130
+ return keys[keys.length - 1][1];
131
+ }
132
+ const f = (v) => {
133
+ const s = Number.isInteger(v) ? `${v}.0` : `${v}`;
134
+ return s.includes("e") ? v.toFixed(9) : s;
135
+ };
136
+ function wgslCurve(name, keys) {
137
+ const lines = [
138
+ `fn ${name}(t: f32) -> f32 {`,
139
+ ` if (t <= ${f(keys[0][0])}) { return ${f(keys[0][1])}; }`
140
+ ];
141
+ for (let i = 1; i < keys.length; i++) {
142
+ const [t0, v0] = keys[i - 1];
143
+ const [t1, v1] = keys[i];
144
+ lines.push(
145
+ ` if (t <= ${f(t1)}) { return mix(${f(v0)}, ${f(v1)}, (t - ${f(t0)}) / ${f(Math.max(t1 - t0, 1e-9))}); }`
146
+ );
147
+ }
148
+ lines.push(` return ${f(keys[keys.length - 1][1])};`, "}");
149
+ return lines.join("\n");
150
+ }
151
+ function wgslGradient(name, keys) {
152
+ const v = (c) => `vec4f(${c.map(f).join(", ")})`;
153
+ const lines = [
154
+ `fn ${name}(t: f32) -> vec4f {`,
155
+ ` if (t <= ${f(keys[0][0])}) { return ${v(keys[0][1])}; }`
156
+ ];
157
+ for (let i = 1; i < keys.length; i++) {
158
+ const [t0, c0] = keys[i - 1];
159
+ const [t1, c1] = keys[i];
160
+ lines.push(
161
+ ` if (t <= ${f(t1)}) { return mix(${v(c0)}, ${v(c1)}, (t - ${f(t0)}) / ${f(Math.max(t1 - t0, 1e-9))}); }`
162
+ );
163
+ }
164
+ lines.push(` return ${v(keys[keys.length - 1][1])};`, "}");
165
+ return lines.join("\n");
166
+ }
167
+ function evalGradient(keys, t, out) {
168
+ if (t <= keys[0][0]) {
169
+ out.splice(0, 4, ...keys[0][1]);
170
+ return out;
171
+ }
172
+ for (let i = 1; i < keys.length; i++) {
173
+ const [t1, c1] = keys[i];
174
+ if (t <= t1) {
175
+ const [t0, c0] = keys[i - 1];
176
+ const u = (t - t0) / Math.max(t1 - t0, 1e-9);
177
+ for (let k = 0; k < 4; k++) out[k] = c0[k] + (c1[k] - c0[k]) * u;
178
+ return out;
179
+ }
180
+ }
181
+ out.splice(0, 4, ...keys[keys.length - 1][1]);
182
+ return out;
183
+ }
184
+ const wgslFloat = f;
185
+ export {
186
+ WGSL_RANDOM,
187
+ color,
188
+ curve,
189
+ evalCurve,
190
+ evalGradient,
191
+ gradient,
192
+ invalid,
193
+ pcg,
194
+ rand,
195
+ scalar,
196
+ wgslCurve,
197
+ wgslFloat,
198
+ wgslGradient
199
+ };
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "@aethervtt/shard-particles",
3
+ "version": "0.0.1",
4
+ "license": "MIT",
5
+ "type": "module",
6
+ "sideEffects": false,
7
+ "exports": {
8
+ ".": {
9
+ "types": "./dist/index.d.ts",
10
+ "default": "./dist/index.js"
11
+ }
12
+ },
13
+ "files": [
14
+ "dist",
15
+ "LICENSE",
16
+ "README.md"
17
+ ],
18
+ "dependencies": {
19
+ "@aethervtt/shard-assets": "0.0.1",
20
+ "@aethervtt/shard-core": "0.0.1",
21
+ "@aethervtt/shard-gpu": "0.0.1",
22
+ "@aethervtt/shard-mesh": "0.0.1",
23
+ "@aethervtt/shard-render": "0.0.1",
24
+ "@aethervtt/shard-runtime": "0.0.1",
25
+ "@aethervtt/shard-texture": "0.0.1",
26
+ "@aethervtt/shard-transform": "0.0.1"
27
+ },
28
+ "repository": {
29
+ "type": "git",
30
+ "url": "git+https://github.com/aethervtt/shard.git",
31
+ "directory": "packages/particles"
32
+ },
33
+ "publishConfig": {
34
+ "access": "public",
35
+ "provenance": true
36
+ },
37
+ "shard": {
38
+ "buildSha": "d6c5c6aef4675575c878b02cf2c8f738b4190229"
39
+ }
40
+ }