@plasius/gpu-particles 0.1.0
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/CHANGELOG.md +46 -0
- package/LICENSE +203 -0
- package/README.md +82 -0
- package/dist/effects/fire/physics.job.wgsl +84 -0
- package/dist/effects/fire/prelude.wgsl +41 -0
- package/dist/effects/fire/render.job.wgsl +8 -0
- package/dist/effects/firework/prelude.wgsl +41 -0
- package/dist/effects/firework/render.job.wgsl +8 -0
- package/dist/effects/firework/update.job.wgsl +154 -0
- package/dist/effects/rain/prelude.wgsl +35 -0
- package/dist/effects/rain/render.job.wgsl +8 -0
- package/dist/effects/rain/update.job.wgsl +51 -0
- package/dist/effects/snow/prelude.wgsl +35 -0
- package/dist/effects/snow/render.job.wgsl +8 -0
- package/dist/effects/snow/update.job.wgsl +53 -0
- package/dist/effects/sparks/prelude.wgsl +41 -0
- package/dist/effects/sparks/render.job.wgsl +8 -0
- package/dist/effects/sparks/update.job.wgsl +53 -0
- package/dist/effects/text/layout.job.wgsl +57 -0
- package/dist/effects/text/prelude.wgsl +39 -0
- package/dist/effects/text/render.job.wgsl +8 -0
- package/dist/index.cjs +286 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.js +244 -0
- package/dist/index.js.map +1 -0
- package/legal/CLA-REGISTRY.csv +2 -0
- package/legal/CLA.md +22 -0
- package/legal/CORPORATE_CLA.md +57 -0
- package/legal/INDIVIDUAL_CLA.md +91 -0
- package/package.json +74 -0
- package/src/effects/fire/physics.job.wgsl +84 -0
- package/src/effects/fire/prelude.wgsl +41 -0
- package/src/effects/fire/render.job.wgsl +8 -0
- package/src/effects/firework/prelude.wgsl +41 -0
- package/src/effects/firework/render.job.wgsl +8 -0
- package/src/effects/firework/update.job.wgsl +154 -0
- package/src/effects/rain/prelude.wgsl +35 -0
- package/src/effects/rain/render.job.wgsl +8 -0
- package/src/effects/rain/update.job.wgsl +51 -0
- package/src/effects/snow/prelude.wgsl +35 -0
- package/src/effects/snow/render.job.wgsl +8 -0
- package/src/effects/snow/update.job.wgsl +53 -0
- package/src/effects/sparks/prelude.wgsl +41 -0
- package/src/effects/sparks/render.job.wgsl +8 -0
- package/src/effects/sparks/update.job.wgsl +53 -0
- package/src/effects/text/layout.job.wgsl +57 -0
- package/src/effects/text/prelude.wgsl +39 -0
- package/src/effects/text/render.job.wgsl +8 -0
- package/src/index.js +251 -0
package/src/index.js
ADDED
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
const baseUrl = (() => {
|
|
2
|
+
if (typeof __IMPORT_META_URL__ !== "undefined") {
|
|
3
|
+
return new URL("./index.js", __IMPORT_META_URL__);
|
|
4
|
+
}
|
|
5
|
+
if (typeof __filename !== "undefined" && typeof require !== "undefined") {
|
|
6
|
+
const { pathToFileURL } = require("node:url");
|
|
7
|
+
return pathToFileURL(__filename);
|
|
8
|
+
}
|
|
9
|
+
const base =
|
|
10
|
+
typeof process !== "undefined" && process.cwd
|
|
11
|
+
? `file://${process.cwd()}/`
|
|
12
|
+
: "file:///";
|
|
13
|
+
return new URL("./index.js", base);
|
|
14
|
+
})();
|
|
15
|
+
|
|
16
|
+
const effectSpecs = {
|
|
17
|
+
fire: {
|
|
18
|
+
prelude: "prelude.wgsl",
|
|
19
|
+
jobs: {
|
|
20
|
+
physics: "physics.job.wgsl",
|
|
21
|
+
render: "render.job.wgsl",
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
sparks: {
|
|
25
|
+
prelude: "prelude.wgsl",
|
|
26
|
+
jobs: {
|
|
27
|
+
update: "update.job.wgsl",
|
|
28
|
+
render: "render.job.wgsl",
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
text: {
|
|
32
|
+
prelude: "prelude.wgsl",
|
|
33
|
+
jobs: {
|
|
34
|
+
layout: "layout.job.wgsl",
|
|
35
|
+
render: "render.job.wgsl",
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
rain: {
|
|
39
|
+
prelude: "prelude.wgsl",
|
|
40
|
+
jobs: {
|
|
41
|
+
update: "update.job.wgsl",
|
|
42
|
+
render: "render.job.wgsl",
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
snow: {
|
|
46
|
+
prelude: "prelude.wgsl",
|
|
47
|
+
jobs: {
|
|
48
|
+
update: "update.job.wgsl",
|
|
49
|
+
render: "render.job.wgsl",
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
firework: {
|
|
53
|
+
prelude: "prelude.wgsl",
|
|
54
|
+
jobs: {
|
|
55
|
+
update: "update.job.wgsl",
|
|
56
|
+
render: "render.job.wgsl",
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
function buildEffect(name, spec) {
|
|
62
|
+
const preludeUrl = new URL(`./effects/${name}/${spec.prelude}`, baseUrl);
|
|
63
|
+
const jobs = Object.entries(spec.jobs).map(([key, file]) => {
|
|
64
|
+
const label = `particles.${name}.${key}`;
|
|
65
|
+
return {
|
|
66
|
+
key,
|
|
67
|
+
label,
|
|
68
|
+
url: new URL(`./effects/${name}/${file}`, baseUrl),
|
|
69
|
+
sourceName: label,
|
|
70
|
+
};
|
|
71
|
+
});
|
|
72
|
+
return {
|
|
73
|
+
name,
|
|
74
|
+
preludeUrl,
|
|
75
|
+
jobs,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export const particleEffects = Object.freeze(
|
|
80
|
+
Object.fromEntries(
|
|
81
|
+
Object.entries(effectSpecs).map(([name, spec]) => [
|
|
82
|
+
name,
|
|
83
|
+
buildEffect(name, spec),
|
|
84
|
+
])
|
|
85
|
+
)
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
export const particleEffectNames = Object.freeze(
|
|
89
|
+
Object.keys(particleEffects)
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
export const defaultParticleEffect = "fire";
|
|
93
|
+
|
|
94
|
+
function getEffectJob(effect, key) {
|
|
95
|
+
const job = effect.jobs.find((entry) => entry.key === key);
|
|
96
|
+
if (!job) {
|
|
97
|
+
const available = effect.jobs.map((entry) => entry.key).join(", ");
|
|
98
|
+
throw new Error(
|
|
99
|
+
`Unknown job \"${key}\" for effect \"${effect.name}\". ` +
|
|
100
|
+
`Available: ${available}.`
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
return job;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function getParticleEffect(name = defaultParticleEffect) {
|
|
107
|
+
const effect = particleEffects[name];
|
|
108
|
+
if (!effect) {
|
|
109
|
+
const available = particleEffectNames.join(", ");
|
|
110
|
+
throw new Error(`Unknown particle effect \"${name}\". Available: ${available}.`);
|
|
111
|
+
}
|
|
112
|
+
return effect;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const defaultEffect = getParticleEffect(defaultParticleEffect);
|
|
116
|
+
const defaultPhysicsJob = getEffectJob(defaultEffect, "physics");
|
|
117
|
+
const defaultRenderJob = getEffectJob(defaultEffect, "render");
|
|
118
|
+
|
|
119
|
+
export const particlePreludeWgslUrl = defaultEffect.preludeUrl;
|
|
120
|
+
export const particlePhysicsJobWgslUrl = defaultPhysicsJob.url;
|
|
121
|
+
export const particleRenderJobWgslUrl = defaultRenderJob.url;
|
|
122
|
+
|
|
123
|
+
export const particleJobLabels = {
|
|
124
|
+
physics: defaultPhysicsJob.label,
|
|
125
|
+
render: defaultRenderJob.label,
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
export const particleJobs = [
|
|
129
|
+
{
|
|
130
|
+
label: defaultPhysicsJob.label,
|
|
131
|
+
url: defaultPhysicsJob.url,
|
|
132
|
+
sourceName: defaultPhysicsJob.sourceName,
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
label: defaultRenderJob.label,
|
|
136
|
+
url: defaultRenderJob.url,
|
|
137
|
+
sourceName: defaultRenderJob.sourceName,
|
|
138
|
+
},
|
|
139
|
+
];
|
|
140
|
+
|
|
141
|
+
function assertNotHtmlWgsl(source, context) {
|
|
142
|
+
const sample = source.slice(0, 200).toLowerCase();
|
|
143
|
+
if (
|
|
144
|
+
sample.includes("<!doctype") ||
|
|
145
|
+
sample.includes("<html") ||
|
|
146
|
+
sample.includes("<meta")
|
|
147
|
+
) {
|
|
148
|
+
const label = context ? ` for ${context}` : "";
|
|
149
|
+
throw new Error(
|
|
150
|
+
`Expected WGSL${label} but received HTML. Check the URL or server root.`
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
async function loadWgslSource(options = {}) {
|
|
156
|
+
const { wgsl, url, fetcher = globalThis.fetch, base } = options ?? {};
|
|
157
|
+
if (typeof wgsl === "string") {
|
|
158
|
+
assertNotHtmlWgsl(wgsl, "inline WGSL");
|
|
159
|
+
return wgsl;
|
|
160
|
+
}
|
|
161
|
+
if (!url) {
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
const resolved = url instanceof URL ? url : new URL(url, base ?? baseUrl);
|
|
165
|
+
if (!fetcher || resolved.protocol === "file:") {
|
|
166
|
+
const { readFile } = await import("node:fs/promises");
|
|
167
|
+
const { fileURLToPath } = await import("node:url");
|
|
168
|
+
const source = await readFile(fileURLToPath(resolved), "utf8");
|
|
169
|
+
assertNotHtmlWgsl(source, resolved.href);
|
|
170
|
+
return source;
|
|
171
|
+
}
|
|
172
|
+
const response = await fetcher(resolved);
|
|
173
|
+
if (!response.ok) {
|
|
174
|
+
const status = "status" in response ? response.status : "unknown";
|
|
175
|
+
const statusText = "statusText" in response ? response.statusText : "";
|
|
176
|
+
const detail = statusText ? `${status} ${statusText}` : `${status}`;
|
|
177
|
+
throw new Error(`Failed to load WGSL (${detail})`);
|
|
178
|
+
}
|
|
179
|
+
const source = await response.text();
|
|
180
|
+
assertNotHtmlWgsl(source, resolved.href);
|
|
181
|
+
return source;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
async function loadEffectPrelude(effect, fetcher) {
|
|
185
|
+
const source = await loadWgslSource({ url: effect.preludeUrl, fetcher });
|
|
186
|
+
if (typeof source !== "string") {
|
|
187
|
+
throw new Error(`Failed to load ${effect.name} prelude WGSL source.`);
|
|
188
|
+
}
|
|
189
|
+
return source;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
async function loadEffectJob(effect, job, fetcher) {
|
|
193
|
+
const source = await loadWgslSource({ url: job.url, fetcher });
|
|
194
|
+
if (typeof source !== "string") {
|
|
195
|
+
throw new Error(
|
|
196
|
+
`Failed to load ${effect.name} job \"${job.key}\" WGSL source.`
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
return source;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export async function loadParticleEffectPreludeWgsl(effectName, options = {}) {
|
|
203
|
+
const { fetcher } = options ?? {};
|
|
204
|
+
const effect = getParticleEffect(effectName);
|
|
205
|
+
return loadEffectPrelude(effect, fetcher);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export async function loadParticleEffectJobWgsl(
|
|
209
|
+
effectName,
|
|
210
|
+
jobKey,
|
|
211
|
+
options = {}
|
|
212
|
+
) {
|
|
213
|
+
const { fetcher } = options ?? {};
|
|
214
|
+
const effect = getParticleEffect(effectName);
|
|
215
|
+
const job = getEffectJob(effect, jobKey);
|
|
216
|
+
return loadEffectJob(effect, job, fetcher);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
export async function loadParticleEffectJobs(effectName, options = {}) {
|
|
220
|
+
const { fetcher } = options ?? {};
|
|
221
|
+
const effect = getParticleEffect(effectName);
|
|
222
|
+
const preludeWgsl = await loadEffectPrelude(effect, fetcher);
|
|
223
|
+
const jobSources = await Promise.all(
|
|
224
|
+
effect.jobs.map((job) => loadEffectJob(effect, job, fetcher))
|
|
225
|
+
);
|
|
226
|
+
const jobs = effect.jobs.map((job, index) => ({
|
|
227
|
+
wgsl: jobSources[index],
|
|
228
|
+
label: job.label,
|
|
229
|
+
sourceName: job.sourceName,
|
|
230
|
+
}));
|
|
231
|
+
return { preludeWgsl, jobs };
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export async function loadParticlePreludeWgsl(options = {}) {
|
|
235
|
+
const { fetcher } = options ?? {};
|
|
236
|
+
return loadEffectPrelude(defaultEffect, fetcher);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
export async function loadParticlePhysicsJobWgsl(options = {}) {
|
|
240
|
+
const { fetcher } = options ?? {};
|
|
241
|
+
return loadEffectJob(defaultEffect, defaultPhysicsJob, fetcher);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
export async function loadParticleRenderJobWgsl(options = {}) {
|
|
245
|
+
const { fetcher } = options ?? {};
|
|
246
|
+
return loadEffectJob(defaultEffect, defaultRenderJob, fetcher);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
export async function loadParticleJobs(options = {}) {
|
|
250
|
+
return loadParticleEffectJobs(defaultParticleEffect, options);
|
|
251
|
+
}
|