babylon.quarks 0.25.0 → 0.26.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 +85 -0
- package/dist/babylon.quarks.cjs +1 -1
- package/dist/babylon.quarks.cjs.map +1 -1
- package/dist/babylon.quarks.esm.js +1 -1
- package/dist/babylon.quarks.esm.js.map +1 -1
- package/dist/babylon.quarks.umd.min.js +1 -1
- package/dist/babylon.quarks.umd.min.js.map +1 -1
- package/dist/types/index.d.ts +279 -53
- package/package.json +1 -1
- package/src/BatchedRenderer.ts +23 -17
- package/src/ParticleSystem.ts +240 -38
- package/src/QuarksLoader.ts +42 -8
- package/src/SpriteBatch.ts +52 -1
- package/src/TrailBatch.ts +129 -20
- package/src/VFXBatch.ts +325 -61
- package/src/index.ts +19 -2
- package/src/shaders/chunks/surface.ts +122 -42
- package/src/shaders/local_particle_physics_vert.glsl.ts +15 -0
- package/src/shaders/local_particle_physics_vert.wgsl.ts +15 -0
- package/src/shaders/particle_frag.glsl.ts +3 -0
- package/src/shaders/particle_frag.wgsl.ts +3 -0
- package/src/shaders/particle_physics_frag.glsl.ts +10 -4
- package/src/shaders/particle_physics_frag.wgsl.ts +10 -4
- package/src/shaders/particle_vert.glsl.ts +7 -0
- package/src/shaders/particle_vert.wgsl.ts +7 -0
- package/src/shaders/stretched_bb_particle_vert.glsl.ts +17 -9
- package/src/shaders/stretched_bb_particle_vert.wgsl.ts +17 -9
- package/src/shaders/trail_frag.glsl.ts +3 -0
- package/src/shaders/trail_frag.wgsl.ts +3 -0
- package/src/shaders/trail_vert.glsl.ts +8 -0
- package/src/shaders/trail_vert.wgsl.ts +8 -0
package/src/VFXBatch.ts
CHANGED
|
@@ -87,9 +87,12 @@ export interface MeshRim {
|
|
|
87
87
|
strength: number;
|
|
88
88
|
/**
|
|
89
89
|
* `'add'` (default) adds the colour, for a glowing edge; `'mix'` blends the
|
|
90
|
-
* surface toward it, for an outline (a dark ink line with a dark colour)
|
|
90
|
+
* surface toward it, for an outline (a dark ink line with a dark colour);
|
|
91
|
+
* `'fade'` leaves the colour and fades the faces turned to the camera by
|
|
92
|
+
* `strength` instead, alpha times `1 - strength * (1 - edge)`: a wall of
|
|
93
|
+
* light that reads thick along its silhouette and thin across its face.
|
|
91
94
|
*/
|
|
92
|
-
mode: 'add' | 'mix';
|
|
95
|
+
mode: 'add' | 'mix' | 'fade';
|
|
93
96
|
}
|
|
94
97
|
|
|
95
98
|
/**
|
|
@@ -107,7 +110,7 @@ export function normalizeMeshRim(rim: Partial<MeshRim> | null | undefined): Mesh
|
|
|
107
110
|
color: [channel(color[0], 1), channel(color[1], 1), channel(color[2], 1)],
|
|
108
111
|
power: Math.max(0.01, channel(rim.power, 2)),
|
|
109
112
|
strength: Math.max(0, channel(rim.strength, 1)),
|
|
110
|
-
mode: rim.mode === 'mix'
|
|
113
|
+
mode: rim.mode === 'mix' || rim.mode === 'fade' ? rim.mode : 'add',
|
|
111
114
|
};
|
|
112
115
|
}
|
|
113
116
|
|
|
@@ -129,6 +132,144 @@ export function meshRimEqual(a: MeshRim | undefined, b: MeshRim | undefined): bo
|
|
|
129
132
|
);
|
|
130
133
|
}
|
|
131
134
|
|
|
135
|
+
/**
|
|
136
|
+
* Which texture coordinates a texture is sampled with, as Unity's UV channels
|
|
137
|
+
* and Niagara's ribbon UV0 / UV1:
|
|
138
|
+
*
|
|
139
|
+
* - `0`: the particle's first UV, with the sprite sheet's cell applied: on a
|
|
140
|
+
* sheet the current frame's cell, on a trail the cell's stretch along it, on
|
|
141
|
+
* a mesh its first UV set. Read before the map's own layout and distortion,
|
|
142
|
+
* so a texture on it does not move with them.
|
|
143
|
+
* - `1`: the particle's own UV, never moved by the sheet. The whole quad on a
|
|
144
|
+
* billboard, a stretched billboard or a beam (mirrored with the quad when it
|
|
145
|
+
* flips); on a trail U along it the way the map's runs (from the tail, or
|
|
146
|
+
* from the head with `uvFromHead`) and V across it; on a mesh its second UV
|
|
147
|
+
* set (`instancingUVs2`), or its first when it has none.
|
|
148
|
+
*
|
|
149
|
+
* A mesh can then repeat its picture along UV0 while a mask on UV1 runs once
|
|
150
|
+
* across its whole length, as a lightmap UV does in a game engine.
|
|
151
|
+
*/
|
|
152
|
+
export type UVChannel = 0 | 1;
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* How a texture is laid over the particle: its UV channel, then tiling and
|
|
156
|
+
* offset as in Unity's material Tiling / Offset, then a scroll on the
|
|
157
|
+
* renderer's clock as Unreal's Panner. Sampled at
|
|
158
|
+
* `uv(channel) * tiling + offset + scroll * time`; the offset is used as
|
|
159
|
+
* given, so a clamped texture can be moved off its edge, and only the
|
|
160
|
+
* scroll's share is wrapped (over two repeats, so a mirrored texture stays
|
|
161
|
+
* seamless). A texture that scrolls or tiles should repeat.
|
|
162
|
+
*/
|
|
163
|
+
export interface TextureUV {
|
|
164
|
+
/** See {@link UVChannel}. Default 0. */
|
|
165
|
+
channel: UVChannel;
|
|
166
|
+
/** Repeats across the channel's 0…1, `[u, v]`. Default `[1, 1]`. */
|
|
167
|
+
tiling: [number, number];
|
|
168
|
+
/** Added after tiling, in texture UV units. Default `[0, 0]`. */
|
|
169
|
+
offset: [number, number];
|
|
170
|
+
/** UV units per second on the renderer's clock. Default `[0, 0]`. */
|
|
171
|
+
scroll: [number, number];
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** A complete {@link TextureUV} from a partial one, with `defaults` under the fields left out. */
|
|
175
|
+
export function normalizeTextureUV(
|
|
176
|
+
uv: Partial<TextureUV> | null | undefined,
|
|
177
|
+
defaults: Partial<TextureUV> = {}
|
|
178
|
+
): TextureUV {
|
|
179
|
+
const source = uv && typeof uv === 'object' ? uv : {};
|
|
180
|
+
const num = (value: unknown, fallback: number) =>
|
|
181
|
+
typeof value === 'number' && Number.isFinite(value) ? value : fallback;
|
|
182
|
+
const pair = (value: unknown, fallback: [number, number]): [number, number] =>
|
|
183
|
+
Array.isArray(value) ? [num(value[0], fallback[0]), num(value[1], fallback[1])] : [fallback[0], fallback[1]];
|
|
184
|
+
const channel = source.channel ?? defaults.channel;
|
|
185
|
+
return {
|
|
186
|
+
channel: channel === 1 ? 1 : 0,
|
|
187
|
+
tiling: pair(source.tiling, defaults.tiling ?? [1, 1]),
|
|
188
|
+
offset: pair(source.offset, defaults.offset ?? [0, 0]),
|
|
189
|
+
scroll: pair(source.scroll, defaults.scroll ?? [0, 0]),
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/** True when both lay a texture the same way. */
|
|
194
|
+
export function textureUVEqual(a: TextureUV, b: TextureUV): boolean {
|
|
195
|
+
return (
|
|
196
|
+
a === b ||
|
|
197
|
+
(a.channel === b.channel &&
|
|
198
|
+
a.tiling[0] === b.tiling[0] &&
|
|
199
|
+
a.tiling[1] === b.tiling[1] &&
|
|
200
|
+
a.offset[0] === b.offset[0] &&
|
|
201
|
+
a.offset[1] === b.offset[1] &&
|
|
202
|
+
a.scroll[0] === b.scroll[0] &&
|
|
203
|
+
a.scroll[1] === b.scroll[1])
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/** The fields of `uv` that differ from `defaults`, for JSON written only when set. */
|
|
208
|
+
export function textureUVToJSON(uv: TextureUV, defaults: Partial<TextureUV> = {}): Partial<TextureUV> | undefined {
|
|
209
|
+
const base = normalizeTextureUV(undefined, defaults);
|
|
210
|
+
const json: Partial<TextureUV> = {};
|
|
211
|
+
if (uv.channel !== base.channel) json.channel = uv.channel;
|
|
212
|
+
if (uv.tiling[0] !== base.tiling[0] || uv.tiling[1] !== base.tiling[1]) json.tiling = [...uv.tiling];
|
|
213
|
+
if (uv.offset[0] !== base.offset[0] || uv.offset[1] !== base.offset[1]) json.offset = [...uv.offset];
|
|
214
|
+
if (uv.scroll[0] !== base.scroll[0] || uv.scroll[1] !== base.scroll[1]) json.scroll = [...uv.scroll];
|
|
215
|
+
return Object.keys(json).length > 0 ? json : undefined;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Which value of the map's texel picks the colour along a gradient map's
|
|
220
|
+
* ramp: one channel, so a texture can pack several masks (Unreal's and
|
|
221
|
+
* Unity's channel-packed textures), or the Rec. 709 luminance of its colour.
|
|
222
|
+
*/
|
|
223
|
+
export type GradientMapChannel = 'r' | 'g' | 'b' | 'a' | 'luminance';
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* A gradient map (colour ramp): the map is recoloured by looking its
|
|
227
|
+
* {@link channel} up along U of {@link ramp}. See
|
|
228
|
+
* {@link VFXBatchSettings.gradientMap}.
|
|
229
|
+
*/
|
|
230
|
+
export interface ParticleGradientMap {
|
|
231
|
+
/**
|
|
232
|
+
* The ramp, read along U at mid V: low values on the left. Make it without
|
|
233
|
+
* mipmaps and clamped. Required: without one there is no gradient map.
|
|
234
|
+
*/
|
|
235
|
+
ramp: BaseTexture | null;
|
|
236
|
+
/**
|
|
237
|
+
* The texel value the ramp is looked up by. Default `'r'`. With `'a'` the
|
|
238
|
+
* ramp's alpha becomes the texel's alpha, so a soft shape can glow at its
|
|
239
|
+
* fringe and stay solid at its heart; with any other channel the ramp's
|
|
240
|
+
* alpha multiplies the map's.
|
|
241
|
+
*/
|
|
242
|
+
channel: GradientMapChannel;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/** The weights a texel is dotted with to look up the ramp, per {@link GradientMapChannel}. */
|
|
246
|
+
export const GRADIENT_MAP_WEIGHTS: Readonly<Record<GradientMapChannel, readonly [number, number, number, number]>> = {
|
|
247
|
+
r: [1, 0, 0, 0],
|
|
248
|
+
g: [0, 1, 0, 0],
|
|
249
|
+
b: [0, 0, 1, 0],
|
|
250
|
+
a: [0, 0, 0, 1],
|
|
251
|
+
luminance: [0.2126, 0.7152, 0.0722, 0],
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
/** A complete gradient map, or undefined when unset or without a ramp. */
|
|
255
|
+
export function normalizeGradientMap(
|
|
256
|
+
gradientMap: Partial<ParticleGradientMap> | null | undefined
|
|
257
|
+
): ParticleGradientMap | undefined {
|
|
258
|
+
if (!gradientMap || typeof gradientMap !== 'object' || !gradientMap.ramp) {
|
|
259
|
+
return undefined;
|
|
260
|
+
}
|
|
261
|
+
const channel = gradientMap.channel;
|
|
262
|
+
return {
|
|
263
|
+
ramp: gradientMap.ramp,
|
|
264
|
+
channel: channel !== undefined && channel in GRADIENT_MAP_WEIGHTS ? channel : 'r',
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/** True when both recolour the same way (or both not at all). */
|
|
269
|
+
export function gradientMapEqual(a: ParticleGradientMap | undefined, b: ParticleGradientMap | undefined): boolean {
|
|
270
|
+
return a === b || (!!a && !!b && a.ramp === b.ramp && a.channel === b.channel);
|
|
271
|
+
}
|
|
272
|
+
|
|
132
273
|
/**
|
|
133
274
|
* Alpha erosion: the particle's alpha decides how much of its shape is left
|
|
134
275
|
* instead of how transparent it is, so `ColorOverLife` fading alpha out
|
|
@@ -137,41 +278,45 @@ export function meshRimEqual(a: MeshRim | undefined, b: MeshRim | undefined): bo
|
|
|
137
278
|
*/
|
|
138
279
|
export interface ParticleErosion {
|
|
139
280
|
/**
|
|
140
|
-
* Mask whose red channel orders what goes first,
|
|
141
|
-
*
|
|
142
|
-
* own alpha, so a soft shape shrinks from its edges.
|
|
281
|
+
* Mask whose red channel orders what goes first, laid by {@link maskUV}.
|
|
282
|
+
* Null: the map's own alpha, so a soft shape shrinks from its edges.
|
|
143
283
|
*/
|
|
144
284
|
mask: BaseTexture | null;
|
|
285
|
+
/**
|
|
286
|
+
* How the mask is laid over the particle, see {@link TextureUV}. On UV
|
|
287
|
+
* channel 1 a gradient mask wipes a whole trail or mesh however its map
|
|
288
|
+
* repeats. Default channel 0, untiled, still.
|
|
289
|
+
*/
|
|
290
|
+
maskUV: TextureUV;
|
|
145
291
|
/** Width of the soft transition at the cut, in mask units. Default 0.05. */
|
|
146
292
|
softness: number;
|
|
147
293
|
/** Width of the glowing band above the cut, in mask units; 0 (default) draws none. */
|
|
148
294
|
edgeWidth: number;
|
|
149
295
|
/** Colour added in the edge band, linear RGB, may exceed 1. Default `[1, 1, 1]`. */
|
|
150
296
|
edgeColor: [number, number, number];
|
|
151
|
-
/** Mask UV scroll, UV units per second. Default `[0, 0]`. */
|
|
152
|
-
maskScroll: [number, number];
|
|
153
297
|
}
|
|
154
|
-
// Limits worth knowing:
|
|
155
|
-
// the current tile's), erosion only changes alpha (one-one and premultiplied
|
|
298
|
+
// Limits worth knowing: erosion only changes alpha (one-one and premultiplied
|
|
156
299
|
// blending show a hard cut), and with neither a mask nor a map there is
|
|
157
300
|
// nothing to order the cut by, so the shape goes all at once near alpha 0.
|
|
158
301
|
// Scrolling runs on the renderer's clock, not a system's playback speed.
|
|
159
302
|
|
|
303
|
+
/** {@link ParticleErosion} as given: any field may be left out, the mask's layout too. */
|
|
304
|
+
export type ParticleErosionOptions = Partial<Omit<ParticleErosion, 'maskUV'>> & {maskUV?: Partial<TextureUV>};
|
|
305
|
+
|
|
160
306
|
/** A complete erosion from a partial one, every field defaulted, or undefined when `erosion` is unset. */
|
|
161
|
-
export function normalizeErosion(erosion:
|
|
307
|
+
export function normalizeErosion(erosion: ParticleErosionOptions | null | undefined): ParticleErosion | undefined {
|
|
162
308
|
if (!erosion || typeof erosion !== 'object') {
|
|
163
309
|
return undefined;
|
|
164
310
|
}
|
|
165
311
|
const num = (value: unknown, fallback: number) =>
|
|
166
312
|
typeof value === 'number' && Number.isFinite(value) ? value : fallback;
|
|
167
313
|
const color = Array.isArray(erosion.edgeColor) ? erosion.edgeColor : [1, 1, 1];
|
|
168
|
-
const scroll = Array.isArray(erosion.maskScroll) ? erosion.maskScroll : [0, 0];
|
|
169
314
|
return {
|
|
170
315
|
mask: erosion.mask ?? null,
|
|
316
|
+
maskUV: normalizeTextureUV(erosion.maskUV),
|
|
171
317
|
softness: Math.max(0.0001, num(erosion.softness, 0.05)),
|
|
172
318
|
edgeWidth: Math.max(0, num(erosion.edgeWidth, 0)),
|
|
173
319
|
edgeColor: [num(color[0], 1), num(color[1], 1), num(color[2], 1)],
|
|
174
|
-
maskScroll: [num(scroll[0], 0), num(scroll[1], 0)],
|
|
175
320
|
};
|
|
176
321
|
}
|
|
177
322
|
|
|
@@ -185,35 +330,106 @@ export function erosionEqual(a: ParticleErosion | undefined, b: ParticleErosion
|
|
|
185
330
|
}
|
|
186
331
|
return (
|
|
187
332
|
a.mask === b.mask &&
|
|
333
|
+
textureUVEqual(a.maskUV, b.maskUV) &&
|
|
188
334
|
a.softness === b.softness &&
|
|
189
335
|
a.edgeWidth === b.edgeWidth &&
|
|
190
336
|
a.edgeColor[0] === b.edgeColor[0] &&
|
|
191
337
|
a.edgeColor[1] === b.edgeColor[1] &&
|
|
192
|
-
a.edgeColor[2] === b.edgeColor[2]
|
|
193
|
-
a.maskScroll[0] === b.maskScroll[0] &&
|
|
194
|
-
a.maskScroll[1] === b.maskScroll[1]
|
|
338
|
+
a.edgeColor[2] === b.edgeColor[2]
|
|
195
339
|
);
|
|
196
340
|
}
|
|
197
341
|
|
|
198
|
-
/**
|
|
199
|
-
export
|
|
200
|
-
|
|
342
|
+
/** The distortion noise's default layout: drifting up half a repeat a second. */
|
|
343
|
+
export const UV_DISTORTION_DEFAULT_UV: Partial<TextureUV> = {scroll: [0, 0.5]};
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* UV distortion: the map's UV pushed about by a noise texture, for heat
|
|
347
|
+
* shimmer, licking flames and breathing auras. See
|
|
348
|
+
* {@link VFXBatchSettings.uvDistortion}.
|
|
349
|
+
*/
|
|
350
|
+
export interface ParticleUVDistortion {
|
|
351
|
+
/**
|
|
352
|
+
* Noise whose red and green channels, around 0.5, push U and V. It should
|
|
353
|
+
* wrap (repeat). Required: without one there is no distortion.
|
|
354
|
+
*/
|
|
355
|
+
map: BaseTexture | null;
|
|
356
|
+
/**
|
|
357
|
+
* How the noise is laid over the particle, see {@link TextureUV}. Default
|
|
358
|
+
* channel 0, untiled, scrolling `[0, 0.5]`.
|
|
359
|
+
*/
|
|
360
|
+
mapUV: TextureUV;
|
|
361
|
+
/** How far the map's UV is pushed at most, in UV units. Default 0.05. */
|
|
362
|
+
strength: number;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/** {@link ParticleUVDistortion} as given: any field may be left out, the noise's layout too. */
|
|
366
|
+
export type ParticleUVDistortionOptions = Partial<Omit<ParticleUVDistortion, 'mapUV'>> & {mapUV?: Partial<TextureUV>};
|
|
367
|
+
|
|
368
|
+
/** A complete distortion from a partial one, or undefined when unset or without a map. */
|
|
369
|
+
export function normalizeUVDistortion(
|
|
370
|
+
distortion: ParticleUVDistortionOptions | null | undefined
|
|
371
|
+
): ParticleUVDistortion | undefined {
|
|
372
|
+
if (!distortion || typeof distortion !== 'object' || !distortion.map) {
|
|
201
373
|
return undefined;
|
|
202
374
|
}
|
|
203
|
-
const
|
|
204
|
-
|
|
205
|
-
|
|
375
|
+
const strength = distortion.strength;
|
|
376
|
+
return {
|
|
377
|
+
map: distortion.map,
|
|
378
|
+
mapUV: normalizeTextureUV(distortion.mapUV, UV_DISTORTION_DEFAULT_UV),
|
|
379
|
+
strength: Math.max(0, typeof strength === 'number' && Number.isFinite(strength) ? strength : 0.05),
|
|
380
|
+
};
|
|
206
381
|
}
|
|
207
382
|
|
|
208
|
-
/** True when both
|
|
209
|
-
export function
|
|
210
|
-
|
|
383
|
+
/** True when both describe the same distortion (or both none). */
|
|
384
|
+
export function uvDistortionEqual(a: ParticleUVDistortion | undefined, b: ParticleUVDistortion | undefined): boolean {
|
|
385
|
+
if (a === b) {
|
|
386
|
+
return true;
|
|
387
|
+
}
|
|
388
|
+
if (!a || !b) {
|
|
389
|
+
return false;
|
|
390
|
+
}
|
|
391
|
+
return a.map === b.map && a.strength === b.strength && textureUVEqual(a.mapUV, b.mapUV);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* The main map's layout, or undefined when it is the identity (channel 0,
|
|
396
|
+
* untiled, unmoved, still), which costs nothing in the shader.
|
|
397
|
+
*/
|
|
398
|
+
export function normalizeMapUV(uv: Partial<TextureUV> | null | undefined): TextureUV | undefined {
|
|
399
|
+
if (!uv || typeof uv !== 'object') {
|
|
400
|
+
return undefined;
|
|
401
|
+
}
|
|
402
|
+
const normalized = normalizeTextureUV(uv);
|
|
403
|
+
return textureUVToJSON(normalized) === undefined ? undefined : normalized;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/** True when both lay the map the same way (or both as drawn). */
|
|
407
|
+
export function mapUVEqual(a: TextureUV | undefined, b: TextureUV | undefined): boolean {
|
|
408
|
+
return a === b || (!!a && !!b && textureUVEqual(a, b));
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* True when a texture effect of `settings` samples UV channel 1, see
|
|
413
|
+
* {@link UVChannel}: the map, the distortion noise or an erosion mask laid
|
|
414
|
+
* on it.
|
|
415
|
+
*/
|
|
416
|
+
export function readsUV1(settings: {
|
|
417
|
+
mapUV?: TextureUV;
|
|
418
|
+
uvDistortion?: ParticleUVDistortion;
|
|
419
|
+
erosion?: ParticleErosion;
|
|
420
|
+
}): boolean {
|
|
421
|
+
const {mapUV, uvDistortion, erosion} = settings;
|
|
422
|
+
return (
|
|
423
|
+
mapUV?.channel === 1 || uvDistortion?.mapUV.channel === 1 || (!!erosion?.mask && erosion.maskUV.channel === 1)
|
|
424
|
+
);
|
|
211
425
|
}
|
|
212
426
|
|
|
213
427
|
export interface StoredBatchSettings {
|
|
214
428
|
instancingGeometry: Float32Array;
|
|
215
429
|
instancingIndices: Uint32Array | Uint16Array;
|
|
216
430
|
instancingUVs?: Float32Array;
|
|
431
|
+
/** The mesh's second UV set, see {@link UVChannel}. */
|
|
432
|
+
instancingUVs2?: Float32Array;
|
|
217
433
|
instancingNormals?: Float32Array;
|
|
218
434
|
renderMode: RenderMode;
|
|
219
435
|
renderOrder: number;
|
|
@@ -250,10 +466,12 @@ export interface StoredBatchSettings {
|
|
|
250
466
|
lighting?: MeshLighting;
|
|
251
467
|
/** Mesh particle rim, see {@link VFXBatchSettings.rim}. */
|
|
252
468
|
rim?: MeshRim;
|
|
253
|
-
/**
|
|
254
|
-
|
|
469
|
+
/** The main map's layout, see {@link VFXBatchSettings.mapUV}. */
|
|
470
|
+
mapUV?: TextureUV;
|
|
471
|
+
/** Map UV distortion, see {@link VFXBatchSettings.uvDistortion}. */
|
|
472
|
+
uvDistortion?: ParticleUVDistortion;
|
|
255
473
|
/** Colour ramp for the map, see {@link VFXBatchSettings.gradientMap}. */
|
|
256
|
-
gradientMap
|
|
474
|
+
gradientMap?: ParticleGradientMap;
|
|
257
475
|
/** Alpha erosion, see {@link VFXBatchSettings.erosion}. */
|
|
258
476
|
erosion?: ParticleErosion;
|
|
259
477
|
}
|
|
@@ -274,6 +492,7 @@ export abstract class VFXBatch {
|
|
|
274
492
|
instancingGeometry: settings.instancingGeometry,
|
|
275
493
|
instancingIndices: settings.instancingIndices,
|
|
276
494
|
instancingUVs: settings.instancingUVs,
|
|
495
|
+
instancingUVs2: settings.instancingUVs2,
|
|
277
496
|
instancingNormals: settings.instancingNormals,
|
|
278
497
|
renderMode: settings.renderMode,
|
|
279
498
|
renderOrder: settings.renderOrder,
|
|
@@ -302,8 +521,9 @@ export abstract class VFXBatch {
|
|
|
302
521
|
applyFog: settings.applyFog ?? true,
|
|
303
522
|
lighting: settings.lighting,
|
|
304
523
|
rim: normalizeMeshRim(settings.rim),
|
|
305
|
-
|
|
306
|
-
|
|
524
|
+
mapUV: normalizeMapUV(settings.mapUV),
|
|
525
|
+
uvDistortion: normalizeUVDistortion(settings.uvDistortion),
|
|
526
|
+
gradientMap: normalizeGradientMap(settings.gradientMap),
|
|
307
527
|
erosion: normalizeErosion(settings.erosion),
|
|
308
528
|
};
|
|
309
529
|
this.mesh = new Mesh('vfxBatch', scene);
|
|
@@ -434,20 +654,40 @@ export abstract class VFXBatch {
|
|
|
434
654
|
*/
|
|
435
655
|
time = 0;
|
|
436
656
|
|
|
657
|
+
/** True when a texture effect of this batch samples UV channel 1, see {@link UVChannel}. */
|
|
658
|
+
protected usesUV1(): boolean {
|
|
659
|
+
return readsUV1(this.settings);
|
|
660
|
+
}
|
|
661
|
+
|
|
437
662
|
/** Defines of the texture effects (see `shaders/chunks/surface.ts`) this batch uses. */
|
|
438
663
|
protected surfaceDefines(): string[] {
|
|
439
664
|
const defines: string[] = [];
|
|
440
|
-
|
|
441
|
-
|
|
665
|
+
const {mapUV, uvDistortion, gradientMap, erosion} = this.settings;
|
|
666
|
+
if (this.usesUV1()) {
|
|
667
|
+
defines.push('UV1');
|
|
442
668
|
}
|
|
443
|
-
if (
|
|
669
|
+
if (mapUV) {
|
|
670
|
+
defines.push('MAP_TRANSFORM');
|
|
671
|
+
if (mapUV.channel === 1) {
|
|
672
|
+
defines.push('MAP_UV1');
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
if (uvDistortion) {
|
|
676
|
+
defines.push('UV_DISTORTION');
|
|
677
|
+
if (uvDistortion.mapUV.channel === 1) {
|
|
678
|
+
defines.push('UV_DISTORTION_UV1');
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
if (gradientMap) {
|
|
444
682
|
defines.push('GRADIENT_MAP');
|
|
445
683
|
}
|
|
446
|
-
const erosion = this.settings.erosion;
|
|
447
684
|
if (erosion) {
|
|
448
685
|
defines.push('EROSION');
|
|
449
686
|
if (erosion.mask) {
|
|
450
687
|
defines.push('EROSION_MASK');
|
|
688
|
+
if (erosion.maskUV.channel === 1) {
|
|
689
|
+
defines.push('EROSION_MASK_UV1');
|
|
690
|
+
}
|
|
451
691
|
}
|
|
452
692
|
}
|
|
453
693
|
return defines;
|
|
@@ -455,69 +695,93 @@ export abstract class VFXBatch {
|
|
|
455
695
|
|
|
456
696
|
/** Adds the uniforms and samplers of the texture effects this batch uses. */
|
|
457
697
|
protected surfaceUniforms(uniforms: string[], samplers: string[]): void {
|
|
458
|
-
|
|
459
|
-
|
|
698
|
+
const {mapUV, uvDistortion, gradientMap, erosion} = this.settings;
|
|
699
|
+
if (mapUV) {
|
|
700
|
+
uniforms.push('mapTransform');
|
|
701
|
+
}
|
|
702
|
+
if (uvDistortion) {
|
|
703
|
+
samplers.push('distortionMap');
|
|
704
|
+
uniforms.push('distortionTransform', 'distortionStrength');
|
|
460
705
|
}
|
|
461
|
-
if (
|
|
706
|
+
if (gradientMap) {
|
|
462
707
|
samplers.push('gradientMap');
|
|
463
|
-
uniforms.push('gradientMapRange');
|
|
708
|
+
uniforms.push('gradientMapRange', 'gradientMapChannel');
|
|
464
709
|
}
|
|
465
|
-
const erosion = this.settings.erosion;
|
|
466
710
|
if (erosion) {
|
|
467
711
|
uniforms.push('erosionParams', 'erosionEdgeColor');
|
|
468
712
|
if (erosion.mask) {
|
|
469
713
|
samplers.push('erosionMask');
|
|
714
|
+
uniforms.push('erosionMaskTransform');
|
|
470
715
|
}
|
|
471
716
|
}
|
|
472
717
|
}
|
|
473
718
|
|
|
474
719
|
/** Sets the texture effects' textures and constants, and advances their scrolling on every bind. */
|
|
475
720
|
protected bindSurface(material: ShaderMaterial): void {
|
|
476
|
-
const {
|
|
477
|
-
if (
|
|
478
|
-
material.setTexture('
|
|
721
|
+
const {mapUV, uvDistortion, gradientMap, erosion} = this.settings;
|
|
722
|
+
if (uvDistortion?.map) {
|
|
723
|
+
material.setTexture('distortionMap', uvDistortion.map);
|
|
724
|
+
material.setFloat('distortionStrength', uvDistortion.strength);
|
|
725
|
+
}
|
|
726
|
+
if (gradientMap?.ramp) {
|
|
727
|
+
material.setTexture('gradientMap', gradientMap.ramp);
|
|
728
|
+
const [r, g, b, a] = GRADIENT_MAP_WEIGHTS[gradientMap.channel];
|
|
729
|
+
material.setVector4('gradientMapChannel', new BVector4(r, g, b, a));
|
|
479
730
|
}
|
|
480
731
|
if (erosion) {
|
|
481
732
|
if (erosion.mask) {
|
|
482
733
|
material.setTexture('erosionMask', erosion.mask);
|
|
483
734
|
}
|
|
735
|
+
material.setVector2('erosionParams', new BVector2(erosion.softness, erosion.edgeWidth));
|
|
484
736
|
material.setVector3(
|
|
485
737
|
'erosionEdgeColor',
|
|
486
738
|
new BVector3(erosion.edgeColor[0], erosion.edgeColor[1], erosion.edgeColor[2])
|
|
487
739
|
);
|
|
488
740
|
}
|
|
489
|
-
if (!
|
|
741
|
+
if (!mapUV && !uvDistortion && !erosion?.mask && !gradientMap) {
|
|
490
742
|
return;
|
|
491
743
|
}
|
|
492
744
|
// Wrapped on the CPU: a raw speed × time grows until float32 UVs lose their fraction.
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
const
|
|
745
|
+
// Over two repeats, not one: a mirrored texture repeats every two, and
|
|
746
|
+
// wrapped over one it jumped to its mirror image at every wrap.
|
|
747
|
+
const wrap = (value: number) => value - 2 * Math.floor(value / 2);
|
|
748
|
+
const mapTransform = new BVector4();
|
|
749
|
+
const distortionTransform = new BVector4();
|
|
750
|
+
const maskTransform = new BVector4();
|
|
496
751
|
const gradientRange = new BVector2(0, 1);
|
|
752
|
+
// Only the scroll is wrapped: an offset is kept as given, so a clamped
|
|
753
|
+
// texture moved off its edge stays moved.
|
|
754
|
+
const transform = (uv: TextureUV, time: number, out: BVector4) =>
|
|
755
|
+
out.set(
|
|
756
|
+
uv.tiling[0],
|
|
757
|
+
uv.tiling[1],
|
|
758
|
+
uv.offset[0] + wrap(uv.scroll[0] * time),
|
|
759
|
+
uv.offset[1] + wrap(uv.scroll[1] * time)
|
|
760
|
+
);
|
|
497
761
|
// Written to the material for the next bind and to the bound effect for
|
|
498
762
|
// this one: ShaderMaterial uploads its values before the bind observers run.
|
|
499
763
|
const write = () => {
|
|
500
764
|
const time = this.time;
|
|
501
765
|
const effect = material.getEffect();
|
|
502
|
-
if (
|
|
503
|
-
|
|
504
|
-
material.
|
|
505
|
-
effect?.
|
|
766
|
+
if (mapUV) {
|
|
767
|
+
transform(mapUV, time, mapTransform);
|
|
768
|
+
material.setVector4('mapTransform', mapTransform);
|
|
769
|
+
effect?.setVector4('mapTransform', mapTransform);
|
|
770
|
+
}
|
|
771
|
+
if (uvDistortion) {
|
|
772
|
+
transform(uvDistortion.mapUV, time, distortionTransform);
|
|
773
|
+
material.setVector4('distortionTransform', distortionTransform);
|
|
774
|
+
effect?.setVector4('distortionTransform', distortionTransform);
|
|
506
775
|
}
|
|
507
|
-
if (erosion) {
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
wrap(erosion.maskScroll[0] * time),
|
|
512
|
-
wrap(erosion.maskScroll[1] * time)
|
|
513
|
-
);
|
|
514
|
-
material.setVector4('erosionParams', erosionParams);
|
|
515
|
-
effect?.setVector4('erosionParams', erosionParams);
|
|
776
|
+
if (erosion?.mask) {
|
|
777
|
+
transform(erosion.maskUV, time, maskTransform);
|
|
778
|
+
material.setVector4('erosionMaskTransform', maskTransform);
|
|
779
|
+
effect?.setVector4('erosionMaskTransform', maskTransform);
|
|
516
780
|
}
|
|
517
|
-
if (gradientMap) {
|
|
781
|
+
if (gradientMap?.ramp) {
|
|
518
782
|
// Texel centres of the first and last texel, so a repeating
|
|
519
783
|
// ramp does not blend its two ends at black and white.
|
|
520
|
-
const width = gradientMap.getSize().width;
|
|
784
|
+
const width = gradientMap.ramp.getSize().width;
|
|
521
785
|
const half = width > 0 ? 0.5 / width : 0;
|
|
522
786
|
gradientRange.set(half, 1 - half);
|
|
523
787
|
material.setVector2('gradientMapRange', gradientRange);
|
package/src/index.ts
CHANGED
|
@@ -46,21 +46,38 @@ export {TrailBatch} from './TrailBatch';
|
|
|
46
46
|
export {
|
|
47
47
|
defaultMeshLighting,
|
|
48
48
|
erosionEqual,
|
|
49
|
+
GRADIENT_MAP_WEIGHTS,
|
|
50
|
+
gradientMapEqual,
|
|
51
|
+
mapUVEqual,
|
|
49
52
|
meshRimEqual,
|
|
50
53
|
normalizeErosion,
|
|
54
|
+
normalizeGradientMap,
|
|
55
|
+
normalizeMapUV,
|
|
51
56
|
normalizeMeshRim,
|
|
52
|
-
|
|
57
|
+
normalizeTextureUV,
|
|
58
|
+
normalizeUVDistortion,
|
|
59
|
+
readsUV1,
|
|
53
60
|
RenderMode,
|
|
54
|
-
|
|
61
|
+
textureUVEqual,
|
|
62
|
+
textureUVToJSON,
|
|
63
|
+
UV_DISTORTION_DEFAULT_UV,
|
|
64
|
+
uvDistortionEqual,
|
|
55
65
|
VFXBatch,
|
|
56
66
|
} from './VFXBatch';
|
|
57
67
|
export type {
|
|
68
|
+
GradientMapChannel,
|
|
58
69
|
MeshLighting,
|
|
59
70
|
MeshLightingSettings,
|
|
60
71
|
MeshRim,
|
|
61
72
|
ParticleErosion,
|
|
73
|
+
ParticleErosionOptions,
|
|
74
|
+
ParticleGradientMap,
|
|
75
|
+
ParticleUVDistortion,
|
|
76
|
+
ParticleUVDistortionOptions,
|
|
62
77
|
SortMode,
|
|
63
78
|
StoredBatchSettings,
|
|
79
|
+
TextureUV,
|
|
80
|
+
UVChannel,
|
|
64
81
|
} from './VFXBatch';
|
|
65
82
|
|
|
66
83
|
// Re-export quarks.core types (interfaces - type-only)
|