reze-engine 0.39.0 → 0.40.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/dist/engine.d.ts +40 -9
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +128 -28
- package/dist/index.d.ts +1 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/model.d.ts +0 -5
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +0 -36
- package/dist/shaders/passes/composite.d.ts.map +1 -1
- package/dist/shaders/passes/composite.js +98 -3
- package/package.json +1 -1
- package/src/engine.ts +152 -28
- package/src/index.ts +2 -1
- package/src/model.ts +0 -36
- package/src/shaders/passes/composite.ts +98 -3
- package/dist/particles.d.ts +0 -43
- package/dist/particles.d.ts.map +0 -1
- package/dist/particles.js +0 -183
- package/dist/shaders/passes/particles.d.ts +0 -2
- package/dist/shaders/passes/particles.d.ts.map +0 -1
- package/dist/shaders/passes/particles.js +0 -99
- package/src/particles.ts +0 -232
- package/src/shaders/passes/particles.ts +0 -100
|
@@ -44,10 +44,51 @@ override APPLY_GAMMA: bool = true;
|
|
|
44
44
|
// viewU[9] = (grade slope.rgb, grade on/off) — see grade() below.
|
|
45
45
|
// invGamma = 1/gamma precomputed on CPU — avoids a per-pixel divide.
|
|
46
46
|
@group(0) @binding(6) var bgEquirect: texture_2d<f32>;
|
|
47
|
+
// The scene pass's own MSAA depth buffer, bound depth-only. NOT an extra
|
|
48
|
+
// render target: when depth of field is off the scene pass discards depth
|
|
49
|
+
// (TBDR tile memory never spills) and this binding is never read — the whole
|
|
50
|
+
// feature costs one uniform branch. When on, the pass stores depth and the
|
|
51
|
+
// gather below reads sample 0.
|
|
52
|
+
@group(0) @binding(8) var depthTex: texture_depth_multisampled_2d;
|
|
53
|
+
// dofU[0] = (enabled, focusDistance, focusRange, aperture)
|
|
54
|
+
// dofU[1] = (maxBlurRadiusPx, bladeCount, sampleCount, anamorphicRatio)
|
|
55
|
+
// dofU[2] = (projA, projB, _, _) — z-buffer → camera-space depth inversion,
|
|
56
|
+
// viewZ = projB / (z - projA), rebuilt per frame because near/far
|
|
57
|
+
// track the camera radius. Cleared depth (1.0) inverts to the far
|
|
58
|
+
// plane, so empty sky reads as maximally defocused background.
|
|
59
|
+
@group(0) @binding(9) var<uniform> dofU: array<vec4<f32>, 3>;
|
|
47
60
|
|
|
48
61
|
// Must match FILMIC_LUT_WIDTH in engine.ts (bakeFilmicLut).
|
|
49
62
|
const FILMIC_LUT_W: f32 = 256.0;
|
|
50
63
|
|
|
64
|
+
fn linearDepth(coord: vec2<i32>) -> f32 {
|
|
65
|
+
let z = textureLoad(depthTex, coord, 0);
|
|
66
|
+
// projA > 1 for every valid z in [0,1], so the divisor never crosses zero.
|
|
67
|
+
return clamp(dofU[2].y / (z - dofU[2].x), 0.05, 100000.0);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Signed circle of confusion in device pixels — negative in front of the
|
|
71
|
+
* focus band, positive behind it, zero inside it. */
|
|
72
|
+
fn circleOfConfusion(depth: f32) -> f32 {
|
|
73
|
+
let focus = max(dofU[0].y, 0.05);
|
|
74
|
+
let halfRange = max(dofU[0].z * 0.5, 0.01);
|
|
75
|
+
let delta = depth - focus;
|
|
76
|
+
let outside = max(abs(delta) - halfRange, 0.0);
|
|
77
|
+
let radius = min(outside / max(depth, 0.05) * dofU[0].w * dofU[1].x, dofU[1].x);
|
|
78
|
+
return select(-radius, radius, delta >= 0.0);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Premultiplied scene color (HDR + bloom) at an arbitrary pixel, for the
|
|
82
|
+
* bokeh gather. Explicit-LOD sampling only — legal in non-uniform flow. */
|
|
83
|
+
fn sceneSample(coord: vec2<i32>, fullSzI: vec2<i32>, fullSz: vec2f) -> vec4f {
|
|
84
|
+
let p = clamp(coord, vec2<i32>(0), fullSzI - vec2<i32>(1));
|
|
85
|
+
let sAlpha = textureLoad(maskTex, p, 0).g;
|
|
86
|
+
let sHdr = textureLoad(hdrTex, p, 0).rgb / max(sAlpha, 1e-6);
|
|
87
|
+
let sUv = (vec2f(p) + vec2f(0.5)) / fullSz;
|
|
88
|
+
let sBloom = textureSampleLevel(bloomTex, bloomSamp, sUv, 0.0).rgb * viewU[1].xyz * viewU[1].w;
|
|
89
|
+
return vec4f((sHdr + sBloom) * sAlpha, sAlpha);
|
|
90
|
+
}
|
|
91
|
+
|
|
51
92
|
fn filmic(x: f32) -> f32 {
|
|
52
93
|
// Reference checkpoints (Blender 3.6 Filmic MHC, sobotka/filmic-blender
|
|
53
94
|
// look_medium-high-contrast.spi1d): linear 0.18 → ~0.395, linear 1.0 → ~0.83.
|
|
@@ -101,7 +142,59 @@ const COMPOSITE_BODY = /* wgsl */ `
|
|
|
101
142
|
let intensity = viewU[1].w;
|
|
102
143
|
let bloom = textureSampleLevel(bloomTex, bloomSamp, bloomUv, 0.0).rgb * tint * intensity;
|
|
103
144
|
let combined = straight + bloom;
|
|
104
|
-
|
|
145
|
+
|
|
146
|
+
// ── Depth of field ──
|
|
147
|
+
// Single-pass golden-angle gather over a polygonal (bladed) disk, in
|
|
148
|
+
// premultiplied HDR before tonemap. Near-field taps that see focused
|
|
149
|
+
// background are heavily down-weighted so a sharp subject doesn't bleed into
|
|
150
|
+
// a blurred foreground; the reverse leak (background bokeh over the subject
|
|
151
|
+
// edge) is damped less — that soft halo is what real lenses do. Scene layer
|
|
152
|
+
// only: the composited background (solid / 360 / effect) stays sharp, which
|
|
153
|
+
// is invisible while it sits at infinity behind a far-blurred stage.
|
|
154
|
+
var scenePm = vec4f(combined * alpha, alpha);
|
|
155
|
+
if (dofU[0].x > 0.5) {
|
|
156
|
+
let centerDepth = linearDepth(coord);
|
|
157
|
+
let centerCoc = circleOfConfusion(centerDepth);
|
|
158
|
+
let radius = abs(centerCoc);
|
|
159
|
+
if (radius > 0.35) {
|
|
160
|
+
let fullSzI = vec2<i32>(fullSz);
|
|
161
|
+
let sampleCount = clamp(dofU[1].z, 6.0, 24.0);
|
|
162
|
+
let blades = clamp(dofU[1].y, 3.0, 12.0);
|
|
163
|
+
let sector = 6.28318530718 / blades;
|
|
164
|
+
var accum = scenePm;
|
|
165
|
+
var weightSum = 1.0;
|
|
166
|
+
for (var i = 0u; i < 24u; i++) {
|
|
167
|
+
if (f32(i) >= sampleCount) { break; }
|
|
168
|
+
let fi = f32(i) + 0.5;
|
|
169
|
+
let ring = sqrt(fi / sampleCount);
|
|
170
|
+
let angle = fi * 2.39996323;
|
|
171
|
+
let localAngle = (fract((angle + 3.14159265359) / sector) - 0.5) * sector;
|
|
172
|
+
let polygonRadius = cos(3.14159265359 / blades) / max(cos(localAngle), 0.01);
|
|
173
|
+
var disk = vec2f(cos(angle), sin(angle)) * ring * polygonRadius;
|
|
174
|
+
disk.x *= max(dofU[1].w, 0.25);
|
|
175
|
+
let sp = coord + vec2<i32>(round(disk * radius));
|
|
176
|
+
let cp = clamp(sp, vec2<i32>(0), fullSzI - vec2<i32>(1));
|
|
177
|
+
let sampleDepth = linearDepth(cp);
|
|
178
|
+
let sampleCoc = circleOfConfusion(sampleDepth);
|
|
179
|
+
var w = 1.0;
|
|
180
|
+
if (centerCoc < 0.0 && sampleDepth > centerDepth + dofU[0].z) {
|
|
181
|
+
w *= 0.08;
|
|
182
|
+
} else if (centerCoc > 0.0 && sampleDepth > centerDepth + dofU[0].z * 2.0) {
|
|
183
|
+
w *= 0.35;
|
|
184
|
+
}
|
|
185
|
+
// A tap only contributes where its own blur circle reaches this pixel.
|
|
186
|
+
let sampleRadius = abs(sampleCoc);
|
|
187
|
+
w *= mix(0.2, 1.0, smoothstep(ring * radius - 1.0, ring * radius + 1.0, sampleRadius));
|
|
188
|
+
accum += sceneSample(cp, fullSzI, fullSz) * w;
|
|
189
|
+
weightSum += w;
|
|
190
|
+
}
|
|
191
|
+
scenePm = mix(scenePm, accum / max(weightSum, 1e-5), smoothstep(0.35, 1.75, radius));
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
let sceneAlpha = scenePm.a;
|
|
195
|
+
let sceneStraight = scenePm.rgb / max(sceneAlpha, 1e-6);
|
|
196
|
+
|
|
197
|
+
let exposed = sceneStraight * exp2(viewU[0].x);
|
|
105
198
|
let tm = vec3f(filmic(exposed.r), filmic(exposed.g), filmic(exposed.b));
|
|
106
199
|
var disp = max(tm, vec3f(0.0));
|
|
107
200
|
// Grade the SCENE only, before the display gamma. Deliberately not applied to
|
|
@@ -135,7 +228,7 @@ const COMPOSITE_BODY = /* wgsl */ `
|
|
|
135
228
|
}
|
|
136
229
|
BG_EFFECT_CALL
|
|
137
230
|
}
|
|
138
|
-
return vec4f(disp *
|
|
231
|
+
return vec4f(disp * sceneAlpha + bgPm * (1.0 - sceneAlpha), sceneAlpha + bgA * (1.0 - sceneAlpha));
|
|
139
232
|
}
|
|
140
233
|
`;
|
|
141
234
|
// Base variant: no effect installed, the flag is never set — the ray block only
|
|
@@ -165,7 +258,9 @@ const USES_DERIVATIVES = /\b(?:fwidth|dpdx|dpdy)(?:Fine|Coarse)?\s*\(/;
|
|
|
165
258
|
* uniform control flow and forgo the gate. */
|
|
166
259
|
function coverageGate(effect) {
|
|
167
260
|
const gated = !effect || !USES_DERIVATIVES.test(effect.wgsl);
|
|
168
|
-
|
|
261
|
+
// sceneAlpha, not alpha: the bokeh gather spreads coverage, so a pixel the
|
|
262
|
+
// sharp scene fully covered can end up needing background behind its blur.
|
|
263
|
+
return gated ? "&& sceneAlpha < 0.999" : "";
|
|
169
264
|
}
|
|
170
265
|
export function buildCompositeShader(effect) {
|
|
171
266
|
if (!effect)
|
package/package.json
CHANGED
package/src/engine.ts
CHANGED
|
@@ -5,7 +5,6 @@ import { MORPH_COMPUTE_WGSL } from "./shaders/passes/morph"
|
|
|
5
5
|
import { decodeTga } from "./tga-loader"
|
|
6
6
|
import { VMDLoader } from "./vmd-loader"
|
|
7
7
|
import { CameraAnimation } from "./camera-animation"
|
|
8
|
-
import { ParticleSystem, type ParticleEmitOptions } from "./particles"
|
|
9
8
|
import { PmxLoader } from "./pmx-loader"
|
|
10
9
|
import { RezePhysics } from "./physics"
|
|
11
10
|
import type { WindOptions } from "./physics/world"
|
|
@@ -276,6 +275,42 @@ export const DEFAULT_BLOOM_OPTIONS: BloomOptions = {
|
|
|
276
275
|
clamp: 0.0,
|
|
277
276
|
}
|
|
278
277
|
|
|
278
|
+
/** Camera depth of field — a bokeh gather in the composite pass. Costs nothing
|
|
279
|
+
* while disabled: the scene pass discards its depth buffer and the composite
|
|
280
|
+
* branch never runs. Enabled, the pass stores depth and the gather reads it. */
|
|
281
|
+
export type DepthOfFieldOptions = {
|
|
282
|
+
enabled: boolean
|
|
283
|
+
/** "auto" focuses the first visible character each frame — the camera-space
|
|
284
|
+
* depth span of its bones sets both distance and a floor on range — so a
|
|
285
|
+
* dancer stays sharp without anyone touching a slider. "manual" uses
|
|
286
|
+
* focusDistance/focusRange as given. */
|
|
287
|
+
focusMode: "auto" | "manual"
|
|
288
|
+
/** Camera-space distance to the focus plane (MMD units). */
|
|
289
|
+
focusDistance: number
|
|
290
|
+
/** Depth band that stays perfectly sharp, centered on focusDistance. In auto
|
|
291
|
+
* mode this is a minimum — the band never cuts into the subject. */
|
|
292
|
+
focusRange: number
|
|
293
|
+
/** Blur strength scale; 1 is a natural lens, higher is dreamier. */
|
|
294
|
+
aperture: number
|
|
295
|
+
/** Largest blur circle, in device pixels. */
|
|
296
|
+
maxBlurRadius: number
|
|
297
|
+
/** Bokeh polygon blade count (3–12); 6 is the classic hexagon. */
|
|
298
|
+
bladeCount: number
|
|
299
|
+
/** Gather tap count: 8 / 16 / 24. */
|
|
300
|
+
quality: "performance" | "balanced" | "cinematic"
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
export const DEFAULT_DEPTH_OF_FIELD_OPTIONS: DepthOfFieldOptions = {
|
|
304
|
+
enabled: false,
|
|
305
|
+
focusMode: "auto",
|
|
306
|
+
focusDistance: 25,
|
|
307
|
+
focusRange: 2,
|
|
308
|
+
aperture: 1,
|
|
309
|
+
maxBlurRadius: 18,
|
|
310
|
+
bladeCount: 6,
|
|
311
|
+
quality: "balanced",
|
|
312
|
+
}
|
|
313
|
+
|
|
279
314
|
/** Blender Color Management / View (rendering.txt: Filmic, exposure, gamma). `look` is reserved for future curve tweaks. */
|
|
280
315
|
export type ViewTransformOptions = {
|
|
281
316
|
/** Stops applied before Filmic: `linear *= 2^exposure`. */
|
|
@@ -718,8 +753,6 @@ export class Engine {
|
|
|
718
753
|
private multisampleMaskTexture!: GPUTexture
|
|
719
754
|
private maskResolveTexture!: GPUTexture
|
|
720
755
|
private maskResolveView!: GPUTextureView
|
|
721
|
-
/** One-shot particle emissions (emitParticles); lazily built on first emit. */
|
|
722
|
-
private particles: ParticleSystem | null = null
|
|
723
756
|
private renderPassDescriptor!: GPURenderPassDescriptor
|
|
724
757
|
private compositePassDescriptor!: GPURenderPassDescriptor
|
|
725
758
|
// Two specialized composite pipelines via WGSL pipeline-override constants.
|
|
@@ -731,6 +764,12 @@ export class Engine {
|
|
|
731
764
|
private morphComputeBindGroupLayout!: GPUBindGroupLayout
|
|
732
765
|
private compositeBindGroupLayout!: GPUBindGroupLayout
|
|
733
766
|
private compositeBindGroup!: GPUBindGroup
|
|
767
|
+
private depthOfField: DepthOfFieldOptions = { ...DEFAULT_DEPTH_OF_FIELD_OPTIONS }
|
|
768
|
+
private dofUniformBuffer!: GPUBuffer
|
|
769
|
+
private dofUniformData = new Float32Array(12)
|
|
770
|
+
private dofFocusScratch = new Vec3(0, 0, 0)
|
|
771
|
+
/** Depth-only view of the scene's MSAA depth buffer, read by the DoF gather. */
|
|
772
|
+
private depthReadView: GPUTextureView | null = null
|
|
734
773
|
private compositeUniformBuffer!: GPUBuffer
|
|
735
774
|
// [exposure, invGamma, _, _, bloomTint.x, bloomTint.y, bloomTint.z, bloomIntensity]
|
|
736
775
|
private readonly compositeUniformData = new Float32Array(40)
|
|
@@ -1082,7 +1121,7 @@ export class Engine {
|
|
|
1082
1121
|
}
|
|
1083
1122
|
|
|
1084
1123
|
private rebuildCompositeBindGroup(): void {
|
|
1085
|
-
if (!this.device || !this.hdrResolveTexture || !this.compositeBloomView) return
|
|
1124
|
+
if (!this.device || !this.hdrResolveTexture || !this.compositeBloomView || !this.depthReadView) return
|
|
1086
1125
|
this.compositeBindGroup = this.device.createBindGroup({
|
|
1087
1126
|
label: "composite bind group",
|
|
1088
1127
|
layout: this.compositeBindGroupLayout,
|
|
@@ -1095,6 +1134,8 @@ export class Engine {
|
|
|
1095
1134
|
{ binding: 5, resource: this.filmicLutView },
|
|
1096
1135
|
{ binding: 6, resource: this.backdropEquirectView ?? this.fallbackEquirectView },
|
|
1097
1136
|
{ binding: 7, resource: { buffer: this.backgroundEffect?.paramsBuffer ?? this.bgParamsDummyBuffer } },
|
|
1137
|
+
{ binding: 8, resource: this.depthReadView },
|
|
1138
|
+
{ binding: 9, resource: { buffer: this.dofUniformBuffer } },
|
|
1098
1139
|
],
|
|
1099
1140
|
})
|
|
1100
1141
|
}
|
|
@@ -1306,6 +1347,86 @@ export class Engine {
|
|
|
1306
1347
|
}
|
|
1307
1348
|
|
|
1308
1349
|
/** Patch bloom; GPU uniforms update immediately if `init()` has run. */
|
|
1350
|
+
/** Camera depth of field (see DepthOfFieldOptions). Free while disabled —
|
|
1351
|
+
* the scene pass only stores its depth buffer on frames the gather reads. */
|
|
1352
|
+
setDepthOfField(patch: Partial<DepthOfFieldOptions>): void {
|
|
1353
|
+
this.depthOfField = { ...this.depthOfField, ...patch }
|
|
1354
|
+
if (!this.device || !this.dofUniformBuffer) return
|
|
1355
|
+
if (this.depthOfField.enabled) {
|
|
1356
|
+
this.writeDepthOfFieldUniforms()
|
|
1357
|
+
} else {
|
|
1358
|
+
// One last write so the shader's uniform branch reads a clean zero.
|
|
1359
|
+
this.dofUniformData[0] = 0
|
|
1360
|
+
this.device.queue.writeBuffer(this.dofUniformBuffer, 0, this.dofUniformData)
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
1363
|
+
|
|
1364
|
+
getDepthOfField(): DepthOfFieldOptions {
|
|
1365
|
+
return { ...this.depthOfField }
|
|
1366
|
+
}
|
|
1367
|
+
|
|
1368
|
+
/** Auto-focus target: the camera-space depth span of the first visible
|
|
1369
|
+
* character's bones. Focus sits at the span's midpoint; the range covers the
|
|
1370
|
+
* span plus a margin for what bones don't reach (shoes, hair, cloth). */
|
|
1371
|
+
private getModelBodyFocus(): { distance: number; range: number } | null {
|
|
1372
|
+
if (!this.camera) return null
|
|
1373
|
+
const view = this.camera.getViewMatrix().values
|
|
1374
|
+
for (const inst of this.modelInstances.values()) {
|
|
1375
|
+
if (!inst.model.visible || inst.isStage) continue
|
|
1376
|
+
const model = inst.model
|
|
1377
|
+
const matrices = model.getWorldMatrices()
|
|
1378
|
+
if (matrices.length === 0) continue
|
|
1379
|
+
const scale = model.scale
|
|
1380
|
+
const local = this.dofFocusScratch
|
|
1381
|
+
let minDepth = Infinity
|
|
1382
|
+
let maxDepth = -Infinity
|
|
1383
|
+
for (const matrix of matrices) {
|
|
1384
|
+
const values = matrix.values
|
|
1385
|
+
// Bone matrices are model-space; apply the same root transform the
|
|
1386
|
+
// renderer bakes into skinning, then take camera-space z.
|
|
1387
|
+
local.setXYZ(values[12] * scale, values[13] * scale, values[14] * scale)
|
|
1388
|
+
Quat.rotateVecInto(model.rotation, local, local)
|
|
1389
|
+
const x = model.position.x + local.x
|
|
1390
|
+
const y = model.position.y + local.y
|
|
1391
|
+
const z = model.position.z + local.z
|
|
1392
|
+
const depth = view[2] * x + view[6] * y + view[10] * z + view[14]
|
|
1393
|
+
if (!Number.isFinite(depth) || depth <= this.camera.near) continue
|
|
1394
|
+
minDepth = Math.min(minDepth, depth)
|
|
1395
|
+
maxDepth = Math.max(maxDepth, depth)
|
|
1396
|
+
}
|
|
1397
|
+
if (Number.isFinite(minDepth) && Number.isFinite(maxDepth)) {
|
|
1398
|
+
const span = Math.max(0, maxDepth - minDepth)
|
|
1399
|
+
const meshMargin = Math.max(2.0, span * 0.15)
|
|
1400
|
+
return { distance: (minDepth + maxDepth) * 0.5, range: Math.max(2.0, span + meshMargin) }
|
|
1401
|
+
}
|
|
1402
|
+
}
|
|
1403
|
+
return null
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1406
|
+
private writeDepthOfFieldUniforms(): void {
|
|
1407
|
+
if (!this.device || !this.dofUniformBuffer) return
|
|
1408
|
+
const d = this.depthOfField
|
|
1409
|
+
const u = this.dofUniformData
|
|
1410
|
+
const auto = d.focusMode === "auto" ? this.getModelBodyFocus() : null
|
|
1411
|
+
u[0] = d.enabled ? 1 : 0
|
|
1412
|
+
u[1] = auto?.distance ?? Math.max(d.focusDistance, 0.05)
|
|
1413
|
+
// In auto mode the authored range is a floor — the sharp band never cuts
|
|
1414
|
+
// into the character's own depth span.
|
|
1415
|
+
u[2] = Math.max(d.focusRange, auto?.range ?? 0.02, 0.02)
|
|
1416
|
+
u[3] = Math.max(d.aperture, 0)
|
|
1417
|
+
u[4] = Math.max(d.maxBlurRadius, 0)
|
|
1418
|
+
u[5] = Math.min(12, Math.max(3, Math.round(d.bladeCount)))
|
|
1419
|
+
u[6] = d.quality === "performance" ? 8 : d.quality === "cinematic" ? 24 : 16
|
|
1420
|
+
u[7] = 1 // anamorphic ratio, reserved (the shader clamps ≥ 0.25)
|
|
1421
|
+
// viewZ = projB / (z − projA), the inverse of perspectiveInto's z mapping.
|
|
1422
|
+
// near/far track the camera radius, so these refresh every enabled frame.
|
|
1423
|
+
const near = this.camera.near
|
|
1424
|
+
const far = this.camera.far
|
|
1425
|
+
u[8] = (far + near) / (far - near)
|
|
1426
|
+
u[9] = (-2 * near * far) / (far - near)
|
|
1427
|
+
this.device.queue.writeBuffer(this.dofUniformBuffer, 0, u)
|
|
1428
|
+
}
|
|
1429
|
+
|
|
1309
1430
|
setBloomOptions(patch: Partial<BloomOptions>): void {
|
|
1310
1431
|
const b = this.bloomSettings
|
|
1311
1432
|
if (patch.enabled !== undefined) b.enabled = patch.enabled
|
|
@@ -2172,6 +2293,12 @@ export class Engine {
|
|
|
2172
2293
|
size: 160,
|
|
2173
2294
|
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
|
|
2174
2295
|
})
|
|
2296
|
+
this.dofUniformBuffer = this.device.createBuffer({
|
|
2297
|
+
label: "depth of field uniforms",
|
|
2298
|
+
// 3 × vec4f — see the dofU comment in composite.ts.
|
|
2299
|
+
size: 48,
|
|
2300
|
+
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
|
|
2301
|
+
})
|
|
2175
2302
|
this.bgParamsDummyBuffer = this.device.createBuffer({
|
|
2176
2303
|
label: "bg effect params (dummy)",
|
|
2177
2304
|
size: 16,
|
|
@@ -2194,6 +2321,15 @@ export class Engine {
|
|
|
2194
2321
|
// User background-effect params — dummy buffer when no effect is set. The
|
|
2195
2322
|
// layout is explicit, so the base shader legally ignores the binding.
|
|
2196
2323
|
{ binding: 7, visibility: GPUShaderStage.FRAGMENT, buffer: { type: "uniform" } },
|
|
2324
|
+
// The scene pass's MSAA depth, depth-only aspect — read by the DoF
|
|
2325
|
+
// gather. Contents are undefined while DoF is off (the scene pass
|
|
2326
|
+
// discards depth then), and the shader never reads it then either.
|
|
2327
|
+
{
|
|
2328
|
+
binding: 8,
|
|
2329
|
+
visibility: GPUShaderStage.FRAGMENT,
|
|
2330
|
+
texture: { sampleType: "depth", viewDimension: "2d", multisampled: true },
|
|
2331
|
+
},
|
|
2332
|
+
{ binding: 9, visibility: GPUShaderStage.FRAGMENT, buffer: { type: "uniform" } },
|
|
2197
2333
|
],
|
|
2198
2334
|
})
|
|
2199
2335
|
this.fallbackEquirectTexture = this.device.createTexture({
|
|
@@ -2439,10 +2575,13 @@ export class Engine {
|
|
|
2439
2575
|
size: [width, height],
|
|
2440
2576
|
sampleCount: Engine.MULTISAMPLE_COUNT,
|
|
2441
2577
|
format: "depth24plus-stencil8",
|
|
2442
|
-
usage
|
|
2578
|
+
// TEXTURE_BINDING for the DoF gather — a usage flag, not a copy; the
|
|
2579
|
+
// zero-cost-when-off story lives in depthStoreOp, not here.
|
|
2580
|
+
usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING,
|
|
2443
2581
|
})
|
|
2444
2582
|
|
|
2445
2583
|
const depthTextureView = this.depthTexture.createView()
|
|
2584
|
+
this.depthReadView = this.depthTexture.createView({ aspect: "depth-only" })
|
|
2446
2585
|
|
|
2447
2586
|
// storeOp="discard" on MSAA views keeps per-sample data in Apple TBDR tile memory —
|
|
2448
2587
|
// only the resolveTarget (hdrResolveTexture / maskResolveView) gets written to RAM.
|
|
@@ -2923,24 +3062,6 @@ export class Engine {
|
|
|
2923
3062
|
this.camera.fov = fov
|
|
2924
3063
|
}
|
|
2925
3064
|
|
|
2926
|
-
/** Fire a one-shot particle effect (see ParticleEmitOptions): "burst" scatters
|
|
2927
|
-
* glowing sparks from the given world points, "converge" condenses them onto
|
|
2928
|
-
* the points. Pair with Model.sampleSurfacePoints for dissolve/materialize
|
|
2929
|
-
* transitions shaped like the model. Stateless GPU particles — the emission
|
|
2930
|
-
* cleans itself up when it expires. */
|
|
2931
|
-
emitParticles(opts: ParticleEmitOptions): void {
|
|
2932
|
-
if (!this.device) return
|
|
2933
|
-
if (!this.particles) {
|
|
2934
|
-
this.particles = new ParticleSystem(this.device, this.cameraUniformBuffer, {
|
|
2935
|
-
hdr: this.hdrFormat,
|
|
2936
|
-
mask: Engine.BLOOM_MASK_FORMAT,
|
|
2937
|
-
depth: "depth24plus-stencil8",
|
|
2938
|
-
sampleCount: Engine.MULTISAMPLE_COUNT,
|
|
2939
|
-
})
|
|
2940
|
-
}
|
|
2941
|
-
this.particles.emit(opts, performance.now() / 1000)
|
|
2942
|
-
}
|
|
2943
|
-
|
|
2944
3065
|
// Step 5: Create lighting buffers
|
|
2945
3066
|
private setupLighting() {
|
|
2946
3067
|
this.lightUniformBuffer = this.device.createBuffer({
|
|
@@ -3096,8 +3217,6 @@ export class Engine {
|
|
|
3096
3217
|
this.forEachInstance((inst) => inst.model.stop())
|
|
3097
3218
|
if (Engine.instance === this) Engine.instance = null
|
|
3098
3219
|
if (this.camera) this.camera.detachControl()
|
|
3099
|
-
this.particles?.dispose()
|
|
3100
|
-
this.particles = null
|
|
3101
3220
|
|
|
3102
3221
|
// Remove raycasting event listeners
|
|
3103
3222
|
if (this.onRaycast) {
|
|
@@ -5093,6 +5212,14 @@ export class Engine {
|
|
|
5093
5212
|
this.updateCameraUniforms()
|
|
5094
5213
|
this.updateShadowLightVP()
|
|
5095
5214
|
|
|
5215
|
+
// Depth of field's entire disabled cost is this branch: depth stays in
|
|
5216
|
+
// TBDR tile memory (discard) unless the composite gather reads it this
|
|
5217
|
+
// frame. Enabled frames also refresh the uniforms — auto-focus tracks the
|
|
5218
|
+
// character and the depth-inversion constants track near/far.
|
|
5219
|
+
const dofOn = this.depthOfField.enabled
|
|
5220
|
+
this.renderPassDescriptor.depthStencilAttachment!.depthStoreOp = dofOn ? "store" : "discard"
|
|
5221
|
+
if (dofOn) this.writeDepthOfFieldUniforms()
|
|
5222
|
+
|
|
5096
5223
|
const encoder = this.device.createCommandEncoder()
|
|
5097
5224
|
|
|
5098
5225
|
// GPU vertex morphs: write morphed positions into vertex buffers before any pass reads
|
|
@@ -5139,9 +5266,6 @@ export class Engine {
|
|
|
5139
5266
|
this.forEachInstance((inst) => {
|
|
5140
5267
|
if (inst.model.visible) this.renderModelTransparentPhase(pass, inst)
|
|
5141
5268
|
})
|
|
5142
|
-
// Particle emissions last: additive light over everything, depth-tested
|
|
5143
|
-
// against the scene, feeding the bloom mask so the sparks glow.
|
|
5144
|
-
this.particles?.draw(pass, performance.now() / 1000)
|
|
5145
5269
|
pass.end()
|
|
5146
5270
|
|
|
5147
5271
|
// Bloom pyramid (EEVEE 3.6):
|
package/src/index.ts
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
export {
|
|
2
2
|
Engine,
|
|
3
3
|
DEFAULT_BLOOM_OPTIONS,
|
|
4
|
+
DEFAULT_DEPTH_OF_FIELD_OPTIONS,
|
|
4
5
|
DEFAULT_VIEW_TRANSFORM,
|
|
5
6
|
DEFAULT_COLOR_GRADING,
|
|
6
7
|
type ColorGradingOptions,
|
|
7
8
|
type EngineStats,
|
|
8
9
|
type EngineOptions,
|
|
9
10
|
type BloomOptions,
|
|
11
|
+
type DepthOfFieldOptions,
|
|
10
12
|
type ViewTransformOptions,
|
|
11
13
|
type LoadModelFromFilesOptions,
|
|
12
14
|
type MaterialPreset,
|
|
@@ -99,4 +101,3 @@ export { PmxLoader } from "./pmx-loader"
|
|
|
99
101
|
export { CameraAnimation, type CameraPose } from "./camera-animation"
|
|
100
102
|
export { RezePhysics } from "./physics"
|
|
101
103
|
export type { WindOptions } from "./physics/world"
|
|
102
|
-
export { type ParticleEmitOptions } from "./particles"
|
package/src/model.ts
CHANGED
|
@@ -1141,42 +1141,6 @@ export class Model {
|
|
|
1141
1141
|
return this.skeleton.inverseBindMatrices
|
|
1142
1142
|
}
|
|
1143
1143
|
|
|
1144
|
-
/** World-space positions of `count` vertices sampled evenly across the mesh
|
|
1145
|
-
* under the CURRENT pose — the model's skinning replayed on the CPU for
|
|
1146
|
-
* this one call, root transform included. Feed to Engine.emitParticles for
|
|
1147
|
-
* dissolve/materialize effects shaped like the model itself. */
|
|
1148
|
-
sampleSurfacePoints(count: number): Float32Array {
|
|
1149
|
-
const skin = this.getSkinMatrices()
|
|
1150
|
-
const joints = this.skinning.joints
|
|
1151
|
-
const weights = this.skinning.weights
|
|
1152
|
-
const n = this.vertexCount
|
|
1153
|
-
const c = Math.max(1, Math.min(count, n))
|
|
1154
|
-
const out = new Float32Array(c * 3)
|
|
1155
|
-
const step = n / c
|
|
1156
|
-
for (let i = 0; i < c; i++) {
|
|
1157
|
-
const v = Math.min(n - 1, Math.floor(i * step + Math.random() * step))
|
|
1158
|
-
const vi = v * VERTEX_STRIDE
|
|
1159
|
-
const px = this.vertexData[vi]
|
|
1160
|
-
const py = this.vertexData[vi + 1]
|
|
1161
|
-
const pz = this.vertexData[vi + 2]
|
|
1162
|
-
let x = 0
|
|
1163
|
-
let y = 0
|
|
1164
|
-
let z = 0
|
|
1165
|
-
for (let j = 0; j < 4; j++) {
|
|
1166
|
-
const w = weights[v * 4 + j] / 255
|
|
1167
|
-
if (w === 0) continue
|
|
1168
|
-
const m = joints[v * 4 + j] * 16
|
|
1169
|
-
x += w * (skin[m] * px + skin[m + 4] * py + skin[m + 8] * pz + skin[m + 12])
|
|
1170
|
-
y += w * (skin[m + 1] * px + skin[m + 5] * py + skin[m + 9] * pz + skin[m + 13])
|
|
1171
|
-
z += w * (skin[m + 2] * px + skin[m + 6] * py + skin[m + 10] * pz + skin[m + 14])
|
|
1172
|
-
}
|
|
1173
|
-
out[i * 3] = x
|
|
1174
|
-
out[i * 3 + 1] = y
|
|
1175
|
-
out[i * 3 + 2] = z
|
|
1176
|
-
}
|
|
1177
|
-
return out
|
|
1178
|
-
}
|
|
1179
|
-
|
|
1180
1144
|
getSkinMatrices(): Float32Array {
|
|
1181
1145
|
const boneCount = this.skeleton.bones.length
|
|
1182
1146
|
const worldMats = this.runtimeSkeleton.worldMatrices
|
|
@@ -66,10 +66,51 @@ override APPLY_GAMMA: bool = true;
|
|
|
66
66
|
// viewU[9] = (grade slope.rgb, grade on/off) — see grade() below.
|
|
67
67
|
// invGamma = 1/gamma precomputed on CPU — avoids a per-pixel divide.
|
|
68
68
|
@group(0) @binding(6) var bgEquirect: texture_2d<f32>;
|
|
69
|
+
// The scene pass's own MSAA depth buffer, bound depth-only. NOT an extra
|
|
70
|
+
// render target: when depth of field is off the scene pass discards depth
|
|
71
|
+
// (TBDR tile memory never spills) and this binding is never read — the whole
|
|
72
|
+
// feature costs one uniform branch. When on, the pass stores depth and the
|
|
73
|
+
// gather below reads sample 0.
|
|
74
|
+
@group(0) @binding(8) var depthTex: texture_depth_multisampled_2d;
|
|
75
|
+
// dofU[0] = (enabled, focusDistance, focusRange, aperture)
|
|
76
|
+
// dofU[1] = (maxBlurRadiusPx, bladeCount, sampleCount, anamorphicRatio)
|
|
77
|
+
// dofU[2] = (projA, projB, _, _) — z-buffer → camera-space depth inversion,
|
|
78
|
+
// viewZ = projB / (z - projA), rebuilt per frame because near/far
|
|
79
|
+
// track the camera radius. Cleared depth (1.0) inverts to the far
|
|
80
|
+
// plane, so empty sky reads as maximally defocused background.
|
|
81
|
+
@group(0) @binding(9) var<uniform> dofU: array<vec4<f32>, 3>;
|
|
69
82
|
|
|
70
83
|
// Must match FILMIC_LUT_WIDTH in engine.ts (bakeFilmicLut).
|
|
71
84
|
const FILMIC_LUT_W: f32 = 256.0;
|
|
72
85
|
|
|
86
|
+
fn linearDepth(coord: vec2<i32>) -> f32 {
|
|
87
|
+
let z = textureLoad(depthTex, coord, 0);
|
|
88
|
+
// projA > 1 for every valid z in [0,1], so the divisor never crosses zero.
|
|
89
|
+
return clamp(dofU[2].y / (z - dofU[2].x), 0.05, 100000.0);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Signed circle of confusion in device pixels — negative in front of the
|
|
93
|
+
* focus band, positive behind it, zero inside it. */
|
|
94
|
+
fn circleOfConfusion(depth: f32) -> f32 {
|
|
95
|
+
let focus = max(dofU[0].y, 0.05);
|
|
96
|
+
let halfRange = max(dofU[0].z * 0.5, 0.01);
|
|
97
|
+
let delta = depth - focus;
|
|
98
|
+
let outside = max(abs(delta) - halfRange, 0.0);
|
|
99
|
+
let radius = min(outside / max(depth, 0.05) * dofU[0].w * dofU[1].x, dofU[1].x);
|
|
100
|
+
return select(-radius, radius, delta >= 0.0);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** Premultiplied scene color (HDR + bloom) at an arbitrary pixel, for the
|
|
104
|
+
* bokeh gather. Explicit-LOD sampling only — legal in non-uniform flow. */
|
|
105
|
+
fn sceneSample(coord: vec2<i32>, fullSzI: vec2<i32>, fullSz: vec2f) -> vec4f {
|
|
106
|
+
let p = clamp(coord, vec2<i32>(0), fullSzI - vec2<i32>(1));
|
|
107
|
+
let sAlpha = textureLoad(maskTex, p, 0).g;
|
|
108
|
+
let sHdr = textureLoad(hdrTex, p, 0).rgb / max(sAlpha, 1e-6);
|
|
109
|
+
let sUv = (vec2f(p) + vec2f(0.5)) / fullSz;
|
|
110
|
+
let sBloom = textureSampleLevel(bloomTex, bloomSamp, sUv, 0.0).rgb * viewU[1].xyz * viewU[1].w;
|
|
111
|
+
return vec4f((sHdr + sBloom) * sAlpha, sAlpha);
|
|
112
|
+
}
|
|
113
|
+
|
|
73
114
|
fn filmic(x: f32) -> f32 {
|
|
74
115
|
// Reference checkpoints (Blender 3.6 Filmic MHC, sobotka/filmic-blender
|
|
75
116
|
// look_medium-high-contrast.spi1d): linear 0.18 → ~0.395, linear 1.0 → ~0.83.
|
|
@@ -124,7 +165,59 @@ const COMPOSITE_BODY = /* wgsl */ `
|
|
|
124
165
|
let intensity = viewU[1].w;
|
|
125
166
|
let bloom = textureSampleLevel(bloomTex, bloomSamp, bloomUv, 0.0).rgb * tint * intensity;
|
|
126
167
|
let combined = straight + bloom;
|
|
127
|
-
|
|
168
|
+
|
|
169
|
+
// ── Depth of field ──
|
|
170
|
+
// Single-pass golden-angle gather over a polygonal (bladed) disk, in
|
|
171
|
+
// premultiplied HDR before tonemap. Near-field taps that see focused
|
|
172
|
+
// background are heavily down-weighted so a sharp subject doesn't bleed into
|
|
173
|
+
// a blurred foreground; the reverse leak (background bokeh over the subject
|
|
174
|
+
// edge) is damped less — that soft halo is what real lenses do. Scene layer
|
|
175
|
+
// only: the composited background (solid / 360 / effect) stays sharp, which
|
|
176
|
+
// is invisible while it sits at infinity behind a far-blurred stage.
|
|
177
|
+
var scenePm = vec4f(combined * alpha, alpha);
|
|
178
|
+
if (dofU[0].x > 0.5) {
|
|
179
|
+
let centerDepth = linearDepth(coord);
|
|
180
|
+
let centerCoc = circleOfConfusion(centerDepth);
|
|
181
|
+
let radius = abs(centerCoc);
|
|
182
|
+
if (radius > 0.35) {
|
|
183
|
+
let fullSzI = vec2<i32>(fullSz);
|
|
184
|
+
let sampleCount = clamp(dofU[1].z, 6.0, 24.0);
|
|
185
|
+
let blades = clamp(dofU[1].y, 3.0, 12.0);
|
|
186
|
+
let sector = 6.28318530718 / blades;
|
|
187
|
+
var accum = scenePm;
|
|
188
|
+
var weightSum = 1.0;
|
|
189
|
+
for (var i = 0u; i < 24u; i++) {
|
|
190
|
+
if (f32(i) >= sampleCount) { break; }
|
|
191
|
+
let fi = f32(i) + 0.5;
|
|
192
|
+
let ring = sqrt(fi / sampleCount);
|
|
193
|
+
let angle = fi * 2.39996323;
|
|
194
|
+
let localAngle = (fract((angle + 3.14159265359) / sector) - 0.5) * sector;
|
|
195
|
+
let polygonRadius = cos(3.14159265359 / blades) / max(cos(localAngle), 0.01);
|
|
196
|
+
var disk = vec2f(cos(angle), sin(angle)) * ring * polygonRadius;
|
|
197
|
+
disk.x *= max(dofU[1].w, 0.25);
|
|
198
|
+
let sp = coord + vec2<i32>(round(disk * radius));
|
|
199
|
+
let cp = clamp(sp, vec2<i32>(0), fullSzI - vec2<i32>(1));
|
|
200
|
+
let sampleDepth = linearDepth(cp);
|
|
201
|
+
let sampleCoc = circleOfConfusion(sampleDepth);
|
|
202
|
+
var w = 1.0;
|
|
203
|
+
if (centerCoc < 0.0 && sampleDepth > centerDepth + dofU[0].z) {
|
|
204
|
+
w *= 0.08;
|
|
205
|
+
} else if (centerCoc > 0.0 && sampleDepth > centerDepth + dofU[0].z * 2.0) {
|
|
206
|
+
w *= 0.35;
|
|
207
|
+
}
|
|
208
|
+
// A tap only contributes where its own blur circle reaches this pixel.
|
|
209
|
+
let sampleRadius = abs(sampleCoc);
|
|
210
|
+
w *= mix(0.2, 1.0, smoothstep(ring * radius - 1.0, ring * radius + 1.0, sampleRadius));
|
|
211
|
+
accum += sceneSample(cp, fullSzI, fullSz) * w;
|
|
212
|
+
weightSum += w;
|
|
213
|
+
}
|
|
214
|
+
scenePm = mix(scenePm, accum / max(weightSum, 1e-5), smoothstep(0.35, 1.75, radius));
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
let sceneAlpha = scenePm.a;
|
|
218
|
+
let sceneStraight = scenePm.rgb / max(sceneAlpha, 1e-6);
|
|
219
|
+
|
|
220
|
+
let exposed = sceneStraight * exp2(viewU[0].x);
|
|
128
221
|
let tm = vec3f(filmic(exposed.r), filmic(exposed.g), filmic(exposed.b));
|
|
129
222
|
var disp = max(tm, vec3f(0.0));
|
|
130
223
|
// Grade the SCENE only, before the display gamma. Deliberately not applied to
|
|
@@ -158,7 +251,7 @@ const COMPOSITE_BODY = /* wgsl */ `
|
|
|
158
251
|
}
|
|
159
252
|
BG_EFFECT_CALL
|
|
160
253
|
}
|
|
161
|
-
return vec4f(disp *
|
|
254
|
+
return vec4f(disp * sceneAlpha + bgPm * (1.0 - sceneAlpha), sceneAlpha + bgA * (1.0 - sceneAlpha));
|
|
162
255
|
}
|
|
163
256
|
`
|
|
164
257
|
|
|
@@ -192,7 +285,9 @@ const USES_DERIVATIVES = /\b(?:fwidth|dpdx|dpdy)(?:Fine|Coarse)?\s*\(/
|
|
|
192
285
|
* uniform control flow and forgo the gate. */
|
|
193
286
|
function coverageGate(effect?: CompositeEffectSource | null): string {
|
|
194
287
|
const gated = !effect || !USES_DERIVATIVES.test(effect.wgsl)
|
|
195
|
-
|
|
288
|
+
// sceneAlpha, not alpha: the bokeh gather spreads coverage, so a pixel the
|
|
289
|
+
// sharp scene fully covered can end up needing background behind its blur.
|
|
290
|
+
return gated ? "&& sceneAlpha < 0.999" : ""
|
|
196
291
|
}
|
|
197
292
|
|
|
198
293
|
export function buildCompositeShader(effect?: CompositeEffectSource | null): string {
|
package/dist/particles.d.ts
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import { Vec3 } from "./math";
|
|
2
|
-
export interface ParticleEmitOptions {
|
|
3
|
-
/** World-space anchor points, xyz-packed (see Model.sampleSurfacePoints).
|
|
4
|
-
* One particle spawns per point. */
|
|
5
|
-
points: Float32Array;
|
|
6
|
-
/** "burst" flies point → scattered and fades (dissolve); "converge" flies
|
|
7
|
-
* scattered → point and vanishes on arrival (materialize). Default burst. */
|
|
8
|
-
mode?: "burst" | "converge";
|
|
9
|
-
/** Seconds the full effect lasts, staggers included (default 1.6). */
|
|
10
|
-
duration?: number;
|
|
11
|
-
/** HDR color — components above 1 glow through bloom (default warm gold). */
|
|
12
|
-
color?: Vec3;
|
|
13
|
-
/** Particle radius in world units (default 0.5). */
|
|
14
|
-
size?: number;
|
|
15
|
-
/** Scatter distance in world units at the far end of the flight (default 14). */
|
|
16
|
-
scatter?: number;
|
|
17
|
-
/** Upward drift across the flight, world units (default 6). */
|
|
18
|
-
lift?: number;
|
|
19
|
-
/** Radians the cloud winds around its centroid across the flight (default 2). */
|
|
20
|
-
swirl?: number;
|
|
21
|
-
}
|
|
22
|
-
export declare class ParticleSystem {
|
|
23
|
-
private readonly device;
|
|
24
|
-
private readonly pipeline;
|
|
25
|
-
private readonly bindGroupLayout;
|
|
26
|
-
private readonly cameraBuffer;
|
|
27
|
-
private readonly timeBuffer;
|
|
28
|
-
private readonly timeData;
|
|
29
|
-
private emissions;
|
|
30
|
-
constructor(device: GPUDevice, cameraBuffer: GPUBuffer, formats: {
|
|
31
|
-
hdr: GPUTextureFormat;
|
|
32
|
-
mask: GPUTextureFormat;
|
|
33
|
-
depth: GPUTextureFormat;
|
|
34
|
-
sampleCount: number;
|
|
35
|
-
});
|
|
36
|
-
/** Upload one emission. `now` is the shared clock (seconds) draw() receives. */
|
|
37
|
-
emit(opts: ParticleEmitOptions, now: number): void;
|
|
38
|
-
get active(): boolean;
|
|
39
|
-
/** Record draws into the main pass (after the transparent phase). */
|
|
40
|
-
draw(pass: GPURenderPassEncoder, now: number): void;
|
|
41
|
-
dispose(): void;
|
|
42
|
-
}
|
|
43
|
-
//# sourceMappingURL=particles.d.ts.map
|
package/dist/particles.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"particles.d.ts","sourceRoot":"","sources":["../src/particles.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAA;AAG7B,MAAM,WAAW,mBAAmB;IAClC;yCACqC;IACrC,MAAM,EAAE,YAAY,CAAA;IACpB;kFAC8E;IAC9E,IAAI,CAAC,EAAE,OAAO,GAAG,UAAU,CAAA;IAC3B,sEAAsE;IACtE,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,6EAA6E;IAC7E,KAAK,CAAC,EAAE,IAAI,CAAA;IACZ,oDAAoD;IACpD,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,iFAAiF;IACjF,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,+DAA+D;IAC/D,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,iFAAiF;IACjF,KAAK,CAAC,EAAE,MAAM,CAAA;CACf;AAaD,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAW;IAClC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAmB;IAC5C,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAoB;IACpD,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAW;IACxC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAW;IACtC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAsB;IAC/C,OAAO,CAAC,SAAS,CAAiB;gBAGhC,MAAM,EAAE,SAAS,EACjB,YAAY,EAAE,SAAS,EACvB,OAAO,EAAE;QAAE,GAAG,EAAE,gBAAgB,CAAC;QAAC,IAAI,EAAE,gBAAgB,CAAC;QAAC,KAAK,EAAE,gBAAgB,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE;IAwD1G,gFAAgF;IAChF,IAAI,CAAC,IAAI,EAAE,mBAAmB,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI;IAoFlD,IAAI,MAAM,IAAI,OAAO,CAEpB;IAED,qEAAqE;IACrE,IAAI,CAAC,IAAI,EAAE,oBAAoB,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI;IAwBnD,OAAO,IAAI,IAAI;CAQhB"}
|