@vib3code/sdk 2.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/CHANGELOG.md +118 -0
- package/DOCS/BLUEPRINT_EXECUTION_PLAN_2026-01-07.md +34 -0
- package/DOCS/CI_TESTING.md +38 -0
- package/DOCS/CLI_ONBOARDING.md +75 -0
- package/DOCS/CONTROL_REFERENCE.md +64 -0
- package/DOCS/DEV_TRACK_ANALYSIS.md +77 -0
- package/DOCS/DEV_TRACK_PLAN_2026-01-07.md +42 -0
- package/DOCS/DEV_TRACK_SESSION_2026-01-31.md +220 -0
- package/DOCS/ENV_SETUP.md +189 -0
- package/DOCS/EXPORT_FORMATS.md +417 -0
- package/DOCS/GPU_DISPOSAL_GUIDE.md +21 -0
- package/DOCS/LICENSING_TIERS.md +275 -0
- package/DOCS/MASTER_PLAN_2026-01-31.md +570 -0
- package/DOCS/OBS_SETUP_GUIDE.md +98 -0
- package/DOCS/PROJECT_SETUP.md +66 -0
- package/DOCS/RENDERER_LIFECYCLE.md +40 -0
- package/DOCS/REPO_MANIFEST.md +121 -0
- package/DOCS/SESSION_014_PLAN.md +195 -0
- package/DOCS/SESSION_LOG_2026-01-07.md +56 -0
- package/DOCS/STRATEGIC_BLUEPRINT_2026-01-07.md +72 -0
- package/DOCS/SYSTEM_AUDIT_2026-01-30.md +738 -0
- package/DOCS/SYSTEM_INVENTORY.md +520 -0
- package/DOCS/TELEMETRY_EXPORTS.md +34 -0
- package/DOCS/WEBGPU_STATUS.md +38 -0
- package/DOCS/XR_BENCHMARKS.md +608 -0
- package/LICENSE +21 -0
- package/README.md +426 -0
- package/docs/.nojekyll +0 -0
- package/docs/01-dissolution_of_euclidean_hegemony.html +346 -0
- package/docs/02-hyperspatial_ego_death.html +346 -0
- package/docs/03-post_cartesian_sublime.html +346 -0
- package/docs/04-crystalline_void_meditation.html +346 -0
- package/docs/05-quantum_decoherence_ballet.html +346 -0
- package/docs/06-dissolution_of_euclidean_hegemony.html +346 -0
- package/docs/07-hyperspatial_ego_death.html +346 -0
- package/docs/08-post_cartesian_sublime.html +346 -0
- package/docs/09-crystalline_void_meditation.html +346 -0
- package/docs/10-quantum_decoherence_ballet.html +346 -0
- package/docs/11-dissolution_of_euclidean_hegemony.html +346 -0
- package/docs/12-hyperspatial_ego_death.html +346 -0
- package/docs/13-post_cartesian_sublime.html +346 -0
- package/docs/index.html +794 -0
- package/docs/test-hub.html +441 -0
- package/docs/url-state.js +102 -0
- package/docs/vib3-exports/01-quantum-quantum-tetrahedron-lattice.html +489 -0
- package/docs/vib3-exports/02-quantum-quantum-hypersphere-matrix.html +489 -0
- package/docs/vib3-exports/03-quantum-quantum-hypertetra-fractal.html +489 -0
- package/docs/vib3-exports/04-faceted-faceted-crystal-structure.html +407 -0
- package/docs/vib3-exports/05-faceted-faceted-klein-bottle.html +407 -0
- package/docs/vib3-exports/06-faceted-faceted-hypertetra-torus.html +407 -0
- package/docs/vib3-exports/07-holographic-holographic-wave-field.html +457 -0
- package/docs/vib3-exports/08-holographic-holographic-hypersphere-sphere.html +457 -0
- package/docs/vib3-exports/09-holographic-holographic-hypertetra-crystal.html +457 -0
- package/docs/vib3-exports/index.html +238 -0
- package/docs/webgpu-live.html +702 -0
- package/package.json +367 -0
- package/src/advanced/AIPresetGenerator.js +777 -0
- package/src/advanced/MIDIController.js +703 -0
- package/src/advanced/OffscreenWorker.js +1051 -0
- package/src/advanced/WebGPUCompute.js +1051 -0
- package/src/advanced/WebXRRenderer.js +680 -0
- package/src/agent/cli/AgentCLI.js +615 -0
- package/src/agent/cli/index.js +14 -0
- package/src/agent/index.js +73 -0
- package/src/agent/mcp/MCPServer.js +950 -0
- package/src/agent/mcp/index.js +9 -0
- package/src/agent/mcp/tools.js +548 -0
- package/src/agent/telemetry/EventStream.js +669 -0
- package/src/agent/telemetry/Instrumentation.js +618 -0
- package/src/agent/telemetry/TelemetryExporters.js +427 -0
- package/src/agent/telemetry/TelemetryService.js +464 -0
- package/src/agent/telemetry/index.js +52 -0
- package/src/benchmarks/BenchmarkRunner.js +381 -0
- package/src/benchmarks/MetricsCollector.js +299 -0
- package/src/benchmarks/index.js +9 -0
- package/src/benchmarks/scenes.js +259 -0
- package/src/cli/index.js +675 -0
- package/src/config/ApiConfig.js +88 -0
- package/src/core/CanvasManager.js +217 -0
- package/src/core/ErrorReporter.js +117 -0
- package/src/core/ParameterMapper.js +333 -0
- package/src/core/Parameters.js +396 -0
- package/src/core/RendererContracts.js +200 -0
- package/src/core/UnifiedResourceManager.js +370 -0
- package/src/core/VIB3Engine.js +636 -0
- package/src/core/renderers/FacetedRendererAdapter.js +32 -0
- package/src/core/renderers/HolographicRendererAdapter.js +29 -0
- package/src/core/renderers/QuantumRendererAdapter.js +29 -0
- package/src/core/renderers/RendererLifecycleManager.js +63 -0
- package/src/creative/ColorPresetsSystem.js +980 -0
- package/src/creative/ParameterTimeline.js +1061 -0
- package/src/creative/PostProcessingPipeline.js +1113 -0
- package/src/creative/TransitionAnimator.js +683 -0
- package/src/export/CSSExporter.js +226 -0
- package/src/export/CardGeneratorBase.js +279 -0
- package/src/export/ExportManager.js +580 -0
- package/src/export/FacetedCardGenerator.js +279 -0
- package/src/export/HolographicCardGenerator.js +543 -0
- package/src/export/LottieExporter.js +552 -0
- package/src/export/QuantumCardGenerator.js +315 -0
- package/src/export/SVGExporter.js +519 -0
- package/src/export/ShaderExporter.js +903 -0
- package/src/export/TradingCardGenerator.js +3055 -0
- package/src/export/TradingCardManager.js +181 -0
- package/src/export/VIB3PackageExporter.js +559 -0
- package/src/export/index.js +14 -0
- package/src/export/systems/TradingCardSystemFaceted.js +494 -0
- package/src/export/systems/TradingCardSystemHolographic.js +452 -0
- package/src/export/systems/TradingCardSystemQuantum.js +411 -0
- package/src/faceted/FacetedSystem.js +963 -0
- package/src/features/CollectionManager.js +433 -0
- package/src/gallery/CollectionManager.js +240 -0
- package/src/gallery/GallerySystem.js +485 -0
- package/src/geometry/GeometryFactory.js +314 -0
- package/src/geometry/GeometryLibrary.js +72 -0
- package/src/geometry/buffers/BufferBuilder.js +338 -0
- package/src/geometry/buffers/index.js +18 -0
- package/src/geometry/generators/Crystal.js +420 -0
- package/src/geometry/generators/Fractal.js +298 -0
- package/src/geometry/generators/KleinBottle.js +197 -0
- package/src/geometry/generators/Sphere.js +192 -0
- package/src/geometry/generators/Tesseract.js +160 -0
- package/src/geometry/generators/Tetrahedron.js +225 -0
- package/src/geometry/generators/Torus.js +304 -0
- package/src/geometry/generators/Wave.js +341 -0
- package/src/geometry/index.js +142 -0
- package/src/geometry/warp/HypersphereCore.js +211 -0
- package/src/geometry/warp/HypertetraCore.js +386 -0
- package/src/geometry/warp/index.js +57 -0
- package/src/holograms/HolographicVisualizer.js +1073 -0
- package/src/holograms/RealHolographicSystem.js +966 -0
- package/src/holograms/variantRegistry.js +69 -0
- package/src/integrations/FigmaPlugin.js +854 -0
- package/src/integrations/OBSMode.js +754 -0
- package/src/integrations/ThreeJsPackage.js +660 -0
- package/src/integrations/TouchDesignerExport.js +552 -0
- package/src/integrations/frameworks/Vib3React.js +591 -0
- package/src/integrations/frameworks/Vib3Svelte.js +654 -0
- package/src/integrations/frameworks/Vib3Vue.js +628 -0
- package/src/llm/LLMParameterInterface.js +240 -0
- package/src/llm/LLMParameterUI.js +577 -0
- package/src/math/Mat4x4.js +708 -0
- package/src/math/Projection.js +341 -0
- package/src/math/Rotor4D.js +637 -0
- package/src/math/Vec4.js +476 -0
- package/src/math/constants.js +164 -0
- package/src/math/index.js +68 -0
- package/src/math/projections.js +54 -0
- package/src/math/rotations.js +196 -0
- package/src/quantum/QuantumEngine.js +906 -0
- package/src/quantum/QuantumVisualizer.js +1103 -0
- package/src/reactivity/ReactivityConfig.js +499 -0
- package/src/reactivity/ReactivityManager.js +586 -0
- package/src/reactivity/SpatialInputSystem.js +1783 -0
- package/src/reactivity/index.js +93 -0
- package/src/render/CommandBuffer.js +465 -0
- package/src/render/MultiCanvasBridge.js +340 -0
- package/src/render/RenderCommand.js +514 -0
- package/src/render/RenderResourceRegistry.js +523 -0
- package/src/render/RenderState.js +552 -0
- package/src/render/RenderTarget.js +512 -0
- package/src/render/ShaderLoader.js +253 -0
- package/src/render/ShaderProgram.js +599 -0
- package/src/render/UnifiedRenderBridge.js +496 -0
- package/src/render/backends/WebGLBackend.js +1108 -0
- package/src/render/backends/WebGPUBackend.js +1409 -0
- package/src/render/commands/CommandBufferExecutor.js +607 -0
- package/src/render/commands/RenderCommandBuffer.js +661 -0
- package/src/render/commands/index.js +17 -0
- package/src/render/index.js +367 -0
- package/src/scene/Disposable.js +498 -0
- package/src/scene/MemoryPool.js +618 -0
- package/src/scene/Node4D.js +697 -0
- package/src/scene/ResourceManager.js +599 -0
- package/src/scene/Scene4D.js +540 -0
- package/src/scene/index.js +98 -0
- package/src/schemas/error.schema.json +84 -0
- package/src/schemas/extension.schema.json +88 -0
- package/src/schemas/index.js +214 -0
- package/src/schemas/parameters.schema.json +142 -0
- package/src/schemas/tool-pack.schema.json +44 -0
- package/src/schemas/tool-response.schema.json +127 -0
- package/src/shaders/common/fullscreen.vert.glsl +5 -0
- package/src/shaders/common/fullscreen.vert.wgsl +17 -0
- package/src/shaders/common/geometry24.glsl +65 -0
- package/src/shaders/common/geometry24.wgsl +54 -0
- package/src/shaders/common/rotation4d.glsl +85 -0
- package/src/shaders/common/rotation4d.wgsl +86 -0
- package/src/shaders/common/uniforms.glsl +44 -0
- package/src/shaders/common/uniforms.wgsl +48 -0
- package/src/shaders/faceted/faceted.frag.glsl +129 -0
- package/src/shaders/faceted/faceted.frag.wgsl +164 -0
- package/src/shaders/holographic/holographic.frag.glsl +406 -0
- package/src/shaders/holographic/holographic.frag.wgsl +185 -0
- package/src/shaders/quantum/quantum.frag.glsl +513 -0
- package/src/shaders/quantum/quantum.frag.wgsl +361 -0
- package/src/testing/ParallelTestFramework.js +519 -0
- package/src/testing/__snapshots__/exportFormats.test.js.snap +24 -0
- package/src/testing/exportFormats.test.js +8 -0
- package/src/testing/projections.test.js +14 -0
- package/src/testing/rotations.test.js +37 -0
- package/src/ui/InteractivityMenu.js +516 -0
- package/src/ui/StatusManager.js +96 -0
- package/src/ui/adaptive/renderers/webgpu/BufferLayout.ts +252 -0
- package/src/ui/adaptive/renderers/webgpu/PolytopeInstanceBuffer.ts +144 -0
- package/src/ui/adaptive/renderers/webgpu/TripleBufferedUniform.ts +170 -0
- package/src/ui/adaptive/renderers/webgpu/WebGPURenderer.ts +735 -0
- package/src/ui/adaptive/renderers/webgpu/index.ts +112 -0
- package/src/variations/VariationManager.js +431 -0
- package/src/viewer/AudioReactivity.js +505 -0
- package/src/viewer/CardBending.js +481 -0
- package/src/viewer/GalleryUI.js +832 -0
- package/src/viewer/ReactivityManager.js +590 -0
- package/src/viewer/TradingCardExporter.js +600 -0
- package/src/viewer/ViewerPortal.js +374 -0
- package/src/viewer/index.js +12 -0
- package/src/wasm/WasmLoader.js +296 -0
- package/src/wasm/index.js +132 -0
- package/tools/agentic/mcpTools.js +88 -0
- package/tools/cli/agent-cli.js +92 -0
- package/tools/export/formats.js +24 -0
- package/tools/math/rotation-baseline.mjs +64 -0
- package/tools/shader-sync-verify.js +937 -0
- package/tools/telemetry/manifestPipeline.js +141 -0
- package/tools/telemetry/telemetryEvents.js +35 -0
- package/types/adaptive-sdk.d.ts +185 -0
- package/types/advanced/AIPresetGenerator.d.ts +81 -0
- package/types/advanced/MIDIController.d.ts +100 -0
- package/types/advanced/OffscreenWorker.d.ts +82 -0
- package/types/advanced/WebGPUCompute.d.ts +52 -0
- package/types/advanced/WebXRRenderer.d.ts +77 -0
- package/types/advanced/index.d.ts +46 -0
- package/types/core/ErrorReporter.d.ts +50 -0
- package/types/core/VIB3Engine.d.ts +204 -0
- package/types/creative/ColorPresetsSystem.d.ts +91 -0
- package/types/creative/ParameterTimeline.d.ts +74 -0
- package/types/creative/PostProcessingPipeline.d.ts +109 -0
- package/types/creative/TransitionAnimator.d.ts +71 -0
- package/types/creative/index.d.ts +35 -0
- package/types/integrations/FigmaPlugin.d.ts +46 -0
- package/types/integrations/OBSMode.d.ts +74 -0
- package/types/integrations/ThreeJsPackage.d.ts +62 -0
- package/types/integrations/TouchDesignerExport.d.ts +36 -0
- package/types/integrations/Vib3React.d.ts +74 -0
- package/types/integrations/Vib3Svelte.d.ts +63 -0
- package/types/integrations/Vib3Vue.d.ts +55 -0
- package/types/integrations/index.d.ts +52 -0
- package/types/reactivity/SpatialInputSystem.d.ts +173 -0
- package/types/reactivity/index.d.ts +394 -0
- package/types/render/CommandBuffer.d.ts +169 -0
- package/types/render/RenderCommand.d.ts +312 -0
- package/types/render/RenderState.d.ts +279 -0
- package/types/render/RenderTarget.d.ts +254 -0
- package/types/render/ShaderProgram.d.ts +277 -0
- package/types/render/UnifiedRenderBridge.d.ts +143 -0
- package/types/render/WebGLBackend.d.ts +168 -0
- package/types/render/WebGPUBackend.d.ts +186 -0
- package/types/render/index.d.ts +141 -0
|
@@ -0,0 +1,1051 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VIB3+ WebGPU Compute Pipeline
|
|
3
|
+
* GPU-accelerated particle systems and audio FFT processing.
|
|
4
|
+
*
|
|
5
|
+
* Provides two compute pipelines:
|
|
6
|
+
* 1. Particle Physics -- 65536 particles influenced by 4D geometry fields,
|
|
7
|
+
* audio reactivity, and user parameters.
|
|
8
|
+
* 2. Audio FFT -- Transforms time-domain audio samples into frequency-domain
|
|
9
|
+
* bands (bass, mid, high, plus configurable sub-bands).
|
|
10
|
+
*
|
|
11
|
+
* Both pipelines are fully self-contained WGSL compute shaders dispatched
|
|
12
|
+
* through a single WebGPU device.
|
|
13
|
+
*
|
|
14
|
+
* @module advanced/WebGPUCompute
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
// WGSL Shader Sources
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* WGSL compute shader for particle physics simulation.
|
|
23
|
+
*
|
|
24
|
+
* Each particle has: position (vec4), velocity (vec4), life (f32), phase (f32).
|
|
25
|
+
* Stride per particle = 10 floats = 40 bytes.
|
|
26
|
+
*
|
|
27
|
+
* The shader applies 4D rotation fields, gravity toward geometry attractors,
|
|
28
|
+
* audio-driven turbulence, and damping per-frame.
|
|
29
|
+
*/
|
|
30
|
+
const PARTICLE_COMPUTE_WGSL = /* wgsl */ `
|
|
31
|
+
|
|
32
|
+
struct Params {
|
|
33
|
+
time: f32,
|
|
34
|
+
deltaTime: f32,
|
|
35
|
+
particleCount: u32,
|
|
36
|
+
_pad0: f32,
|
|
37
|
+
|
|
38
|
+
// 6D rotation angles
|
|
39
|
+
rotXY: f32, rotXZ: f32, rotYZ: f32, _pad1: f32,
|
|
40
|
+
rotXW: f32, rotYW: f32, rotZW: f32, _pad2: f32,
|
|
41
|
+
|
|
42
|
+
// VIB3 parameters
|
|
43
|
+
geometry: f32,
|
|
44
|
+
gridDensity: f32,
|
|
45
|
+
morphFactor: f32,
|
|
46
|
+
chaos: f32,
|
|
47
|
+
|
|
48
|
+
speed: f32,
|
|
49
|
+
hue: f32,
|
|
50
|
+
intensity: f32,
|
|
51
|
+
dimension: f32,
|
|
52
|
+
|
|
53
|
+
// Audio
|
|
54
|
+
bass: f32,
|
|
55
|
+
mid: f32,
|
|
56
|
+
high: f32,
|
|
57
|
+
_pad3: f32,
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
@group(0) @binding(0) var<storage, read_write> particles: array<f32>;
|
|
61
|
+
@group(0) @binding(1) var<uniform> params: Params;
|
|
62
|
+
|
|
63
|
+
// ---- Rotation helpers (4D) ----
|
|
64
|
+
|
|
65
|
+
fn rotateXY(p: vec4<f32>, a: f32) -> vec4<f32> {
|
|
66
|
+
let c = cos(a); let s = sin(a);
|
|
67
|
+
return vec4<f32>(p.x * c - p.y * s, p.x * s + p.y * c, p.z, p.w);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
fn rotateXZ(p: vec4<f32>, a: f32) -> vec4<f32> {
|
|
71
|
+
let c = cos(a); let s = sin(a);
|
|
72
|
+
return vec4<f32>(p.x * c - p.z * s, p.y, p.x * s + p.z * c, p.w);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
fn rotateYZ(p: vec4<f32>, a: f32) -> vec4<f32> {
|
|
76
|
+
let c = cos(a); let s = sin(a);
|
|
77
|
+
return vec4<f32>(p.x, p.y * c - p.z * s, p.y * s + p.z * c, p.w);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
fn rotateXW(p: vec4<f32>, a: f32) -> vec4<f32> {
|
|
81
|
+
let c = cos(a); let s = sin(a);
|
|
82
|
+
return vec4<f32>(p.x * c - p.w * s, p.y, p.z, p.x * s + p.w * c);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
fn rotateYW(p: vec4<f32>, a: f32) -> vec4<f32> {
|
|
86
|
+
let c = cos(a); let s = sin(a);
|
|
87
|
+
return vec4<f32>(p.x, p.y * c - p.w * s, p.z, p.y * s + p.w * c);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
fn rotateZW(p: vec4<f32>, a: f32) -> vec4<f32> {
|
|
91
|
+
let c = cos(a); let s = sin(a);
|
|
92
|
+
return vec4<f32>(p.x, p.y, p.z * c - p.w * s, p.z * s + p.w * c);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
fn rotate4D(p: vec4<f32>) -> vec4<f32> {
|
|
96
|
+
var q = p;
|
|
97
|
+
q = rotateXY(q, params.rotXY);
|
|
98
|
+
q = rotateXZ(q, params.rotXZ);
|
|
99
|
+
q = rotateYZ(q, params.rotYZ);
|
|
100
|
+
q = rotateXW(q, params.rotXW);
|
|
101
|
+
q = rotateYW(q, params.rotYW);
|
|
102
|
+
q = rotateZW(q, params.rotZW);
|
|
103
|
+
return q;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// ---- Pseudo-random (hash-based) ----
|
|
107
|
+
|
|
108
|
+
fn hash(n: f32) -> f32 {
|
|
109
|
+
return fract(sin(n) * 43758.5453123);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
fn hash3(p: vec3<f32>) -> vec3<f32> {
|
|
113
|
+
let q = vec3<f32>(
|
|
114
|
+
dot(p, vec3<f32>(127.1, 311.7, 74.7)),
|
|
115
|
+
dot(p, vec3<f32>(269.5, 183.3, 246.1)),
|
|
116
|
+
dot(p, vec3<f32>(113.5, 271.9, 124.6))
|
|
117
|
+
);
|
|
118
|
+
return fract(sin(q) * 43758.5453123);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// ---- Geometry attractor field ----
|
|
122
|
+
|
|
123
|
+
fn geometryAttractor(pos: vec4<f32>, geom: f32, t: f32) -> vec4<f32> {
|
|
124
|
+
let gi = u32(geom) % 8u;
|
|
125
|
+
var target = vec4<f32>(0.0);
|
|
126
|
+
|
|
127
|
+
switch gi {
|
|
128
|
+
case 0u: { // Tetrahedron vertices
|
|
129
|
+
let phase = t * 0.5;
|
|
130
|
+
target = vec4<f32>(
|
|
131
|
+
sin(phase + pos.x * 3.14159),
|
|
132
|
+
cos(phase + pos.y * 3.14159),
|
|
133
|
+
sin(phase * 0.7 + pos.z * 3.14159),
|
|
134
|
+
cos(phase * 0.3)
|
|
135
|
+
) * 0.5;
|
|
136
|
+
}
|
|
137
|
+
case 1u: { // Hypercube lattice
|
|
138
|
+
target = vec4<f32>(
|
|
139
|
+
round(pos.x * 2.0) * 0.5,
|
|
140
|
+
round(pos.y * 2.0) * 0.5,
|
|
141
|
+
round(pos.z * 2.0) * 0.5,
|
|
142
|
+
round(pos.w * 2.0) * 0.5
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
case 2u: { // Sphere surface
|
|
146
|
+
let len = max(length(pos), 0.001);
|
|
147
|
+
target = pos / len * 0.6;
|
|
148
|
+
}
|
|
149
|
+
case 3u: { // Torus
|
|
150
|
+
let R = 0.5; let r_minor = 0.2;
|
|
151
|
+
let angle1 = atan2(pos.y, pos.x);
|
|
152
|
+
let ringCenter = vec2<f32>(cos(angle1), sin(angle1)) * R;
|
|
153
|
+
let toCenter = vec2<f32>(pos.x - ringCenter.x, pos.y - ringCenter.y);
|
|
154
|
+
let angle2 = atan2(pos.z, length(toCenter));
|
|
155
|
+
target = vec4<f32>(
|
|
156
|
+
(R + r_minor * cos(angle2)) * cos(angle1),
|
|
157
|
+
(R + r_minor * cos(angle2)) * sin(angle1),
|
|
158
|
+
r_minor * sin(angle2),
|
|
159
|
+
pos.w * 0.5
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
case 4u: { // Klein bottle (figure-8 immersion)
|
|
163
|
+
let u_angle = atan2(pos.y, pos.x);
|
|
164
|
+
let v_angle = atan2(pos.w, pos.z);
|
|
165
|
+
target = vec4<f32>(
|
|
166
|
+
(2.0 + cos(v_angle)) * cos(u_angle),
|
|
167
|
+
(2.0 + cos(v_angle)) * sin(u_angle),
|
|
168
|
+
sin(v_angle) * cos(u_angle * 0.5),
|
|
169
|
+
sin(v_angle) * sin(u_angle * 0.5)
|
|
170
|
+
) * 0.25;
|
|
171
|
+
}
|
|
172
|
+
case 5u: { // Fractal (Menger-like attractor)
|
|
173
|
+
var fp = pos;
|
|
174
|
+
for (var i = 0u; i < 3u; i++) {
|
|
175
|
+
fp = abs(fp) * 2.0 - vec4<f32>(1.0);
|
|
176
|
+
}
|
|
177
|
+
target = fp * 0.15;
|
|
178
|
+
}
|
|
179
|
+
case 6u: { // Wave
|
|
180
|
+
target = vec4<f32>(
|
|
181
|
+
pos.x,
|
|
182
|
+
sin(pos.x * 6.28 + t) * 0.3,
|
|
183
|
+
pos.z,
|
|
184
|
+
cos(pos.z * 6.28 + t * 0.7) * 0.3
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
case 7u: { // Crystal (octahedral)
|
|
188
|
+
let absSum = abs(pos.x) + abs(pos.y) + abs(pos.z) + abs(pos.w);
|
|
189
|
+
let scale = select(0.6 / absSum, 1.0, absSum < 0.001);
|
|
190
|
+
target = pos * scale;
|
|
191
|
+
}
|
|
192
|
+
default: {
|
|
193
|
+
target = vec4<f32>(0.0);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return target;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// ---- Main compute ----
|
|
201
|
+
|
|
202
|
+
@compute @workgroup_size(256)
|
|
203
|
+
fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
|
|
204
|
+
let idx = gid.x;
|
|
205
|
+
if (idx >= params.particleCount) { return; }
|
|
206
|
+
|
|
207
|
+
let stride = 10u; // floats per particle
|
|
208
|
+
let base = idx * stride;
|
|
209
|
+
|
|
210
|
+
// Read particle state
|
|
211
|
+
var pos = vec4<f32>(particles[base + 0u], particles[base + 1u],
|
|
212
|
+
particles[base + 2u], particles[base + 3u]);
|
|
213
|
+
var vel = vec4<f32>(particles[base + 4u], particles[base + 5u],
|
|
214
|
+
particles[base + 6u], particles[base + 7u]);
|
|
215
|
+
var life = particles[base + 8u];
|
|
216
|
+
var phase = particles[base + 9u];
|
|
217
|
+
|
|
218
|
+
let dt = params.deltaTime * params.speed;
|
|
219
|
+
let t = params.time;
|
|
220
|
+
let fi = f32(idx);
|
|
221
|
+
|
|
222
|
+
// ---- Respawn dead particles ----
|
|
223
|
+
if (life <= 0.0) {
|
|
224
|
+
let seed = hash3(vec3<f32>(fi, t, fi * 0.37));
|
|
225
|
+
pos = vec4<f32>(
|
|
226
|
+
(seed.x - 0.5) * 2.0,
|
|
227
|
+
(seed.y - 0.5) * 2.0,
|
|
228
|
+
(seed.z - 0.5) * 2.0,
|
|
229
|
+
(hash(fi + t) - 0.5) * 2.0
|
|
230
|
+
);
|
|
231
|
+
vel = vec4<f32>(0.0);
|
|
232
|
+
life = 2.0 + hash(fi * 7.13 + t) * 4.0;
|
|
233
|
+
phase = hash(fi * 3.17 + t) * 6.28318;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// ---- 4D rotation field ----
|
|
237
|
+
let rotatedTarget = rotate4D(pos);
|
|
238
|
+
let rotForce = (rotatedTarget - pos) * 0.5;
|
|
239
|
+
|
|
240
|
+
// ---- Geometry attractor ----
|
|
241
|
+
let attractor = geometryAttractor(pos, params.geometry, t);
|
|
242
|
+
let attractForce = (attractor - pos) * (0.3 + params.morphFactor * 0.5);
|
|
243
|
+
|
|
244
|
+
// ---- Audio turbulence ----
|
|
245
|
+
let audioForce = vec4<f32>(
|
|
246
|
+
sin(phase + t * 3.0) * params.bass * 0.8,
|
|
247
|
+
cos(phase + t * 2.5) * params.mid * 0.6,
|
|
248
|
+
sin(phase * 1.3 + t * 1.8) * params.high * 0.7,
|
|
249
|
+
cos(phase * 0.7 + t) * (params.bass + params.mid) * 0.3
|
|
250
|
+
);
|
|
251
|
+
|
|
252
|
+
// ---- Chaos / noise ----
|
|
253
|
+
let chaosNoise = vec4<f32>(
|
|
254
|
+
hash(fi + t * 100.0) - 0.5,
|
|
255
|
+
hash(fi * 2.0 + t * 100.0) - 0.5,
|
|
256
|
+
hash(fi * 3.0 + t * 100.0) - 0.5,
|
|
257
|
+
hash(fi * 4.0 + t * 100.0) - 0.5
|
|
258
|
+
) * params.chaos * 2.0;
|
|
259
|
+
|
|
260
|
+
// ---- Grid density influence (repulsion from neighbors approximation) ----
|
|
261
|
+
let densityRepel = -pos * (params.gridDensity * 0.001);
|
|
262
|
+
|
|
263
|
+
// ---- Integrate ----
|
|
264
|
+
let totalForce = rotForce + attractForce + audioForce + chaosNoise + densityRepel;
|
|
265
|
+
vel = vel + totalForce * dt;
|
|
266
|
+
|
|
267
|
+
// Damping
|
|
268
|
+
let damping = 0.96 - params.chaos * 0.1;
|
|
269
|
+
vel = vel * damping;
|
|
270
|
+
|
|
271
|
+
// Clamp velocity
|
|
272
|
+
let maxSpeed = 2.0 + params.speed;
|
|
273
|
+
let speed = length(vel);
|
|
274
|
+
if (speed > maxSpeed) {
|
|
275
|
+
vel = vel * (maxSpeed / speed);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
pos = pos + vel * dt;
|
|
279
|
+
|
|
280
|
+
// Boundary wrap
|
|
281
|
+
pos = (fract((pos + vec4<f32>(2.0)) / 4.0) - vec4<f32>(0.5)) * 4.0;
|
|
282
|
+
|
|
283
|
+
// Life decay
|
|
284
|
+
life = life - dt;
|
|
285
|
+
phase = phase + dt * 1.5;
|
|
286
|
+
|
|
287
|
+
// ---- Write back ----
|
|
288
|
+
particles[base + 0u] = pos.x;
|
|
289
|
+
particles[base + 1u] = pos.y;
|
|
290
|
+
particles[base + 2u] = pos.z;
|
|
291
|
+
particles[base + 3u] = pos.w;
|
|
292
|
+
particles[base + 4u] = vel.x;
|
|
293
|
+
particles[base + 5u] = vel.y;
|
|
294
|
+
particles[base + 6u] = vel.z;
|
|
295
|
+
particles[base + 7u] = vel.w;
|
|
296
|
+
particles[base + 8u] = life;
|
|
297
|
+
particles[base + 9u] = phase;
|
|
298
|
+
}
|
|
299
|
+
`;
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* WGSL compute shader for audio FFT processing.
|
|
303
|
+
*
|
|
304
|
+
* Performs a radix-2 Cooley-Tukey FFT on 1024 samples,
|
|
305
|
+
* then bins the magnitudes into frequency bands.
|
|
306
|
+
*/
|
|
307
|
+
const FFT_COMPUTE_WGSL = /* wgsl */ `
|
|
308
|
+
|
|
309
|
+
// -- Bindings --
|
|
310
|
+
// binding 0: input time-domain audio (1024 f32 samples, read)
|
|
311
|
+
// binding 1: working buffer for FFT (2048 f32: 1024 real + 1024 imag, read_write)
|
|
312
|
+
// binding 2: output frequency bands (32 f32, read_write)
|
|
313
|
+
// binding 3: FFT params uniform
|
|
314
|
+
|
|
315
|
+
struct FFTParams {
|
|
316
|
+
sampleCount: u32,
|
|
317
|
+
bandCount: u32,
|
|
318
|
+
sampleRate: f32,
|
|
319
|
+
_pad: f32,
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
@group(0) @binding(0) var<storage, read> audioInput: array<f32>;
|
|
323
|
+
@group(0) @binding(1) var<storage, read_write> fftWork: array<f32>;
|
|
324
|
+
@group(0) @binding(2) var<storage, read_write> bands: array<f32>;
|
|
325
|
+
@group(0) @binding(3) var<uniform> fftParams: FFTParams;
|
|
326
|
+
|
|
327
|
+
// ---- Bit-reversal permutation ----
|
|
328
|
+
fn bitReverse(x: u32, bits: u32) -> u32 {
|
|
329
|
+
var v = x;
|
|
330
|
+
var r = 0u;
|
|
331
|
+
for (var i = 0u; i < bits; i++) {
|
|
332
|
+
r = (r << 1u) | (v & 1u);
|
|
333
|
+
v = v >> 1u;
|
|
334
|
+
}
|
|
335
|
+
return r;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// ---- Pass 1: Bit-reverse copy + Hann window ----
|
|
339
|
+
@compute @workgroup_size(256)
|
|
340
|
+
fn fftBitReverse(@builtin(global_invocation_id) gid: vec3<u32>) {
|
|
341
|
+
let idx = gid.x;
|
|
342
|
+
let N = fftParams.sampleCount;
|
|
343
|
+
if (idx >= N) { return; }
|
|
344
|
+
|
|
345
|
+
let bits = u32(log2(f32(N)));
|
|
346
|
+
let target = bitReverse(idx, bits);
|
|
347
|
+
|
|
348
|
+
// Apply Hann window
|
|
349
|
+
let n = f32(idx);
|
|
350
|
+
let Nf = f32(N);
|
|
351
|
+
let window = 0.5 * (1.0 - cos(6.28318530718 * n / (Nf - 1.0)));
|
|
352
|
+
let sample = audioInput[idx] * window;
|
|
353
|
+
|
|
354
|
+
// Bit-reversed copy: real in [0..N), imag in [N..2N)
|
|
355
|
+
fftWork[target] = sample;
|
|
356
|
+
fftWork[target + N] = 0.0;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// ---- Pass 2..log2(N): Butterfly operations ----
|
|
360
|
+
// We dispatch this multiple times from JS, once per FFT stage,
|
|
361
|
+
// passing the stage via push constant emulation in the work buffer layout.
|
|
362
|
+
|
|
363
|
+
// For simplicity and correctness, we encode a full iterative FFT in a
|
|
364
|
+
// single dispatch with a serial loop per invocation over its assigned
|
|
365
|
+
// butterflies across all stages. Each invocation handles one butterfly
|
|
366
|
+
// within the current stage dispatched from JS.
|
|
367
|
+
|
|
368
|
+
// In practice we use a staged approach: JS dispatches log2(N) times.
|
|
369
|
+
// Each dispatch operates on one stage. The stage index is encoded at
|
|
370
|
+
// fftWork[2*N] (a reserved slot written by JS before each dispatch).
|
|
371
|
+
|
|
372
|
+
@compute @workgroup_size(256)
|
|
373
|
+
fn fftButterfly(@builtin(global_invocation_id) gid: vec3<u32>) {
|
|
374
|
+
let N = fftParams.sampleCount;
|
|
375
|
+
let idx = gid.x;
|
|
376
|
+
|
|
377
|
+
// Stage encoded at the reserved slot
|
|
378
|
+
let stage = u32(fftWork[2u * N]);
|
|
379
|
+
let halfLen = 1u << stage; // half the sub-DFT length
|
|
380
|
+
let fullLen = halfLen << 1u; // full sub-DFT length
|
|
381
|
+
let numButterflies = N / 2u;
|
|
382
|
+
|
|
383
|
+
if (idx >= numButterflies) { return; }
|
|
384
|
+
|
|
385
|
+
// Determine which butterfly within which sub-DFT
|
|
386
|
+
let subDFT = idx / halfLen;
|
|
387
|
+
let j = idx % halfLen;
|
|
388
|
+
let base = subDFT * fullLen;
|
|
389
|
+
|
|
390
|
+
let evenIdx = base + j;
|
|
391
|
+
let oddIdx = base + j + halfLen;
|
|
392
|
+
|
|
393
|
+
// Twiddle factor: W_N^(j * N/fullLen)
|
|
394
|
+
let angle = -6.28318530718 * f32(j) / f32(fullLen);
|
|
395
|
+
let tw_re = cos(angle);
|
|
396
|
+
let tw_im = sin(angle);
|
|
397
|
+
|
|
398
|
+
// Read even and odd
|
|
399
|
+
let e_re = fftWork[evenIdx];
|
|
400
|
+
let e_im = fftWork[evenIdx + N];
|
|
401
|
+
let o_re = fftWork[oddIdx];
|
|
402
|
+
let o_im = fftWork[oddIdx + N];
|
|
403
|
+
|
|
404
|
+
// Twiddle multiply
|
|
405
|
+
let t_re = o_re * tw_re - o_im * tw_im;
|
|
406
|
+
let t_im = o_re * tw_im + o_im * tw_re;
|
|
407
|
+
|
|
408
|
+
// Butterfly
|
|
409
|
+
fftWork[evenIdx] = e_re + t_re;
|
|
410
|
+
fftWork[evenIdx + N] = e_im + t_im;
|
|
411
|
+
fftWork[oddIdx] = e_re - t_re;
|
|
412
|
+
fftWork[oddIdx + N] = e_im - t_im;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// ---- Pass final: Compute magnitude bands ----
|
|
416
|
+
@compute @workgroup_size(32)
|
|
417
|
+
fn fftBands(@builtin(global_invocation_id) gid: vec3<u32>) {
|
|
418
|
+
let bandIdx = gid.x;
|
|
419
|
+
let bandCount = fftParams.bandCount;
|
|
420
|
+
if (bandIdx >= bandCount) { return; }
|
|
421
|
+
|
|
422
|
+
let N = fftParams.sampleCount;
|
|
423
|
+
let halfN = N / 2u; // Nyquist
|
|
424
|
+
|
|
425
|
+
// Map bands logarithmically across spectrum
|
|
426
|
+
let lo = f32(bandIdx) / f32(bandCount);
|
|
427
|
+
let hi = f32(bandIdx + 1u) / f32(bandCount);
|
|
428
|
+
|
|
429
|
+
// Logarithmic frequency mapping
|
|
430
|
+
let freqLo = u32(pow(f32(halfN), lo));
|
|
431
|
+
let freqHi = max(u32(pow(f32(halfN), hi)), freqLo + 1u);
|
|
432
|
+
|
|
433
|
+
var mag = 0.0;
|
|
434
|
+
var count = 0.0;
|
|
435
|
+
for (var k = freqLo; k < freqHi && k < halfN; k++) {
|
|
436
|
+
let re = fftWork[k];
|
|
437
|
+
let im = fftWork[k + N];
|
|
438
|
+
mag += sqrt(re * re + im * im);
|
|
439
|
+
count += 1.0;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
if (count > 0.0) {
|
|
443
|
+
mag = mag / count;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// Normalize (approximate, assumes input in [-1, 1])
|
|
447
|
+
mag = mag / f32(N) * 2.0;
|
|
448
|
+
|
|
449
|
+
bands[bandIdx] = mag;
|
|
450
|
+
}
|
|
451
|
+
`;
|
|
452
|
+
|
|
453
|
+
// ---------------------------------------------------------------------------
|
|
454
|
+
// Exports
|
|
455
|
+
// ---------------------------------------------------------------------------
|
|
456
|
+
|
|
457
|
+
export class WebGPUComputePipeline {
|
|
458
|
+
constructor() {
|
|
459
|
+
/** @type {GPUDevice|null} */
|
|
460
|
+
this.device = null;
|
|
461
|
+
|
|
462
|
+
/** @type {GPUAdapter|null} */
|
|
463
|
+
this.adapter = null;
|
|
464
|
+
|
|
465
|
+
// -- Particle resources --
|
|
466
|
+
/** @type {GPUComputePipeline|null} */
|
|
467
|
+
this.particlePipeline = null;
|
|
468
|
+
|
|
469
|
+
/** @type {GPUBuffer|null} */
|
|
470
|
+
this.particleBuffer = null;
|
|
471
|
+
|
|
472
|
+
/** @type {GPUBuffer|null} */
|
|
473
|
+
this.particleParamsBuffer = null;
|
|
474
|
+
|
|
475
|
+
/** @type {GPUBuffer|null} */
|
|
476
|
+
this.particleReadBuffer = null;
|
|
477
|
+
|
|
478
|
+
/** @type {GPUBindGroup|null} */
|
|
479
|
+
this.particleBindGroup = null;
|
|
480
|
+
|
|
481
|
+
/** @type {number} */
|
|
482
|
+
this.particleCount = 65536;
|
|
483
|
+
|
|
484
|
+
/** @type {number} Floats per particle (pos4 + vel4 + life + phase) */
|
|
485
|
+
this.particleStride = 10;
|
|
486
|
+
|
|
487
|
+
// -- FFT resources --
|
|
488
|
+
/** @type {GPUComputePipeline|null} FFT bit-reverse pipeline */
|
|
489
|
+
this.fftBitReversePipeline = null;
|
|
490
|
+
|
|
491
|
+
/** @type {GPUComputePipeline|null} FFT butterfly pipeline */
|
|
492
|
+
this.fftButterflyPipeline = null;
|
|
493
|
+
|
|
494
|
+
/** @type {GPUComputePipeline|null} FFT band computation pipeline */
|
|
495
|
+
this.fftBandsPipeline = null;
|
|
496
|
+
|
|
497
|
+
/** @type {GPUBuffer|null} */
|
|
498
|
+
this.fftInputBuffer = null;
|
|
499
|
+
|
|
500
|
+
/** @type {GPUBuffer|null} */
|
|
501
|
+
this.fftWorkBuffer = null;
|
|
502
|
+
|
|
503
|
+
/** @type {GPUBuffer|null} */
|
|
504
|
+
this.fftBandsBuffer = null;
|
|
505
|
+
|
|
506
|
+
/** @type {GPUBuffer|null} */
|
|
507
|
+
this.fftParamsBuffer = null;
|
|
508
|
+
|
|
509
|
+
/** @type {GPUBuffer|null} */
|
|
510
|
+
this.fftReadBuffer = null;
|
|
511
|
+
|
|
512
|
+
/** @type {GPUBindGroup|null} */
|
|
513
|
+
this.fftBindGroup = null;
|
|
514
|
+
|
|
515
|
+
/** @type {number} */
|
|
516
|
+
this.fftSize = 1024;
|
|
517
|
+
|
|
518
|
+
/** @type {number} */
|
|
519
|
+
this.bandCount = 32;
|
|
520
|
+
|
|
521
|
+
/** @type {boolean} */
|
|
522
|
+
this._initialized = false;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
// -----------------------------------------------------------------------
|
|
526
|
+
// Initialization
|
|
527
|
+
// -----------------------------------------------------------------------
|
|
528
|
+
|
|
529
|
+
/**
|
|
530
|
+
* Initialize WebGPU adapter, device, and both compute pipelines.
|
|
531
|
+
* @returns {Promise<void>}
|
|
532
|
+
* @throws {Error} If WebGPU is unavailable
|
|
533
|
+
*/
|
|
534
|
+
async initialize() {
|
|
535
|
+
if (this._initialized) return;
|
|
536
|
+
|
|
537
|
+
if (!navigator.gpu) {
|
|
538
|
+
throw new Error('WebGPU is not supported in this browser.');
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
this.adapter = await navigator.gpu.requestAdapter({
|
|
542
|
+
powerPreference: 'high-performance'
|
|
543
|
+
});
|
|
544
|
+
|
|
545
|
+
if (!this.adapter) {
|
|
546
|
+
throw new Error('Failed to obtain WebGPU adapter.');
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
this.device = await this.adapter.requestDevice({
|
|
550
|
+
requiredLimits: {
|
|
551
|
+
maxStorageBufferBindingSize: this.adapter.limits.maxStorageBufferBindingSize,
|
|
552
|
+
maxComputeWorkgroupsPerDimension: this.adapter.limits.maxComputeWorkgroupsPerDimension
|
|
553
|
+
}
|
|
554
|
+
});
|
|
555
|
+
|
|
556
|
+
this.device.lost.then((info) => {
|
|
557
|
+
console.error('WebGPU device lost:', info.message);
|
|
558
|
+
this._initialized = false;
|
|
559
|
+
});
|
|
560
|
+
|
|
561
|
+
this._createParticlePipeline();
|
|
562
|
+
this._createFFTPipeline();
|
|
563
|
+
|
|
564
|
+
this._initialized = true;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
// -----------------------------------------------------------------------
|
|
568
|
+
// Particle System
|
|
569
|
+
// -----------------------------------------------------------------------
|
|
570
|
+
|
|
571
|
+
/**
|
|
572
|
+
* Return the WGSL source for the particle compute shader.
|
|
573
|
+
* @returns {string}
|
|
574
|
+
*/
|
|
575
|
+
createParticleComputeShader() {
|
|
576
|
+
return PARTICLE_COMPUTE_WGSL;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
/**
|
|
580
|
+
* Build particle compute pipeline and allocate GPU buffers.
|
|
581
|
+
* @private
|
|
582
|
+
*/
|
|
583
|
+
_createParticlePipeline() {
|
|
584
|
+
const device = this.device;
|
|
585
|
+
const totalFloats = this.particleCount * this.particleStride;
|
|
586
|
+
const bufferSize = totalFloats * 4; // bytes
|
|
587
|
+
|
|
588
|
+
// Particle storage buffer
|
|
589
|
+
this.particleBuffer = device.createBuffer({
|
|
590
|
+
size: bufferSize,
|
|
591
|
+
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC,
|
|
592
|
+
mappedAtCreation: true
|
|
593
|
+
});
|
|
594
|
+
|
|
595
|
+
// Initialize particles with random positions
|
|
596
|
+
{
|
|
597
|
+
const data = new Float32Array(this.particleBuffer.getMappedRange());
|
|
598
|
+
for (let i = 0; i < this.particleCount; i++) {
|
|
599
|
+
const base = i * this.particleStride;
|
|
600
|
+
// Position
|
|
601
|
+
data[base + 0] = (Math.random() - 0.5) * 2.0;
|
|
602
|
+
data[base + 1] = (Math.random() - 0.5) * 2.0;
|
|
603
|
+
data[base + 2] = (Math.random() - 0.5) * 2.0;
|
|
604
|
+
data[base + 3] = (Math.random() - 0.5) * 2.0;
|
|
605
|
+
// Velocity
|
|
606
|
+
data[base + 4] = 0;
|
|
607
|
+
data[base + 5] = 0;
|
|
608
|
+
data[base + 6] = 0;
|
|
609
|
+
data[base + 7] = 0;
|
|
610
|
+
// Life
|
|
611
|
+
data[base + 8] = 2.0 + Math.random() * 4.0;
|
|
612
|
+
// Phase
|
|
613
|
+
data[base + 9] = Math.random() * Math.PI * 2.0;
|
|
614
|
+
}
|
|
615
|
+
this.particleBuffer.unmap();
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
// Params uniform buffer (must be 16-byte aligned, total struct = 96 bytes)
|
|
619
|
+
const paramsSize = 128; // Rounded up for alignment
|
|
620
|
+
this.particleParamsBuffer = device.createBuffer({
|
|
621
|
+
size: paramsSize,
|
|
622
|
+
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
|
|
623
|
+
});
|
|
624
|
+
|
|
625
|
+
// Readback staging buffer
|
|
626
|
+
this.particleReadBuffer = device.createBuffer({
|
|
627
|
+
size: bufferSize,
|
|
628
|
+
usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST
|
|
629
|
+
});
|
|
630
|
+
|
|
631
|
+
// Shader module
|
|
632
|
+
const shaderModule = device.createShaderModule({
|
|
633
|
+
label: 'VIB3 Particle Compute',
|
|
634
|
+
code: PARTICLE_COMPUTE_WGSL
|
|
635
|
+
});
|
|
636
|
+
|
|
637
|
+
// Bind group layout
|
|
638
|
+
const bindGroupLayout = device.createBindGroupLayout({
|
|
639
|
+
entries: [
|
|
640
|
+
{
|
|
641
|
+
binding: 0,
|
|
642
|
+
visibility: GPUShaderStage.COMPUTE,
|
|
643
|
+
buffer: { type: 'storage' }
|
|
644
|
+
},
|
|
645
|
+
{
|
|
646
|
+
binding: 1,
|
|
647
|
+
visibility: GPUShaderStage.COMPUTE,
|
|
648
|
+
buffer: { type: 'uniform' }
|
|
649
|
+
}
|
|
650
|
+
]
|
|
651
|
+
});
|
|
652
|
+
|
|
653
|
+
// Pipeline
|
|
654
|
+
this.particlePipeline = device.createComputePipeline({
|
|
655
|
+
label: 'VIB3 Particle Pipeline',
|
|
656
|
+
layout: device.createPipelineLayout({
|
|
657
|
+
bindGroupLayouts: [bindGroupLayout]
|
|
658
|
+
}),
|
|
659
|
+
compute: {
|
|
660
|
+
module: shaderModule,
|
|
661
|
+
entryPoint: 'main'
|
|
662
|
+
}
|
|
663
|
+
});
|
|
664
|
+
|
|
665
|
+
// Bind group
|
|
666
|
+
this.particleBindGroup = device.createBindGroup({
|
|
667
|
+
layout: bindGroupLayout,
|
|
668
|
+
entries: [
|
|
669
|
+
{ binding: 0, resource: { buffer: this.particleBuffer } },
|
|
670
|
+
{ binding: 1, resource: { buffer: this.particleParamsBuffer } }
|
|
671
|
+
]
|
|
672
|
+
});
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
/**
|
|
676
|
+
* Dispatch the particle compute shader to update all particle positions.
|
|
677
|
+
*
|
|
678
|
+
* @param {Object} params - VIB3 parameter state
|
|
679
|
+
* @param {number} params.time - Current time in seconds
|
|
680
|
+
* @param {number} params.deltaTime - Frame delta in seconds
|
|
681
|
+
* @param {number} [params.rotXY=0] - XY rotation (radians)
|
|
682
|
+
* @param {number} [params.rotXZ=0] - XZ rotation
|
|
683
|
+
* @param {number} [params.rotYZ=0] - YZ rotation
|
|
684
|
+
* @param {number} [params.rotXW=0] - XW rotation
|
|
685
|
+
* @param {number} [params.rotYW=0] - YW rotation
|
|
686
|
+
* @param {number} [params.rotZW=0] - ZW rotation
|
|
687
|
+
* @param {number} [params.geometry=0] - Geometry index (0-23)
|
|
688
|
+
* @param {number} [params.gridDensity=20] - Grid density
|
|
689
|
+
* @param {number} [params.morphFactor=0] - Morph factor
|
|
690
|
+
* @param {number} [params.chaos=0] - Chaos amount
|
|
691
|
+
* @param {number} [params.speed=1] - Animation speed
|
|
692
|
+
* @param {number} [params.hue=180] - Color hue
|
|
693
|
+
* @param {number} [params.intensity=0.5] - Intensity
|
|
694
|
+
* @param {number} [params.dimension=4.0] - Projection dimension
|
|
695
|
+
* @param {number} [params.bass=0] - Audio bass level
|
|
696
|
+
* @param {number} [params.mid=0] - Audio mid level
|
|
697
|
+
* @param {number} [params.high=0] - Audio high level
|
|
698
|
+
*/
|
|
699
|
+
updateParticles(params) {
|
|
700
|
+
if (!this._initialized || !this.device) return;
|
|
701
|
+
|
|
702
|
+
// Pack params into Float32Array matching the Params struct layout
|
|
703
|
+
const data = new Float32Array(32); // 128 bytes / 4
|
|
704
|
+
data[0] = params.time || 0;
|
|
705
|
+
data[1] = params.deltaTime || 0.016;
|
|
706
|
+
// u32 particleCount at byte offset 8 -- use DataView
|
|
707
|
+
const dv = new DataView(data.buffer);
|
|
708
|
+
dv.setUint32(8, this.particleCount, true);
|
|
709
|
+
data[3] = 0; // _pad0
|
|
710
|
+
|
|
711
|
+
data[4] = params.rotXY || 0;
|
|
712
|
+
data[5] = params.rotXZ || 0;
|
|
713
|
+
data[6] = params.rotYZ || 0;
|
|
714
|
+
data[7] = 0; // _pad1
|
|
715
|
+
|
|
716
|
+
data[8] = params.rotXW || 0;
|
|
717
|
+
data[9] = params.rotYW || 0;
|
|
718
|
+
data[10] = params.rotZW || 0;
|
|
719
|
+
data[11] = 0; // _pad2
|
|
720
|
+
|
|
721
|
+
data[12] = params.geometry || 0;
|
|
722
|
+
data[13] = params.gridDensity || 20;
|
|
723
|
+
data[14] = params.morphFactor || 0;
|
|
724
|
+
data[15] = params.chaos || 0;
|
|
725
|
+
|
|
726
|
+
data[16] = params.speed || 1;
|
|
727
|
+
data[17] = params.hue || 180;
|
|
728
|
+
data[18] = params.intensity || 0.5;
|
|
729
|
+
data[19] = params.dimension || 4.0;
|
|
730
|
+
|
|
731
|
+
data[20] = params.bass || 0;
|
|
732
|
+
data[21] = params.mid || 0;
|
|
733
|
+
data[22] = params.high || 0;
|
|
734
|
+
data[23] = 0; // _pad3
|
|
735
|
+
|
|
736
|
+
this.device.queue.writeBuffer(this.particleParamsBuffer, 0, data);
|
|
737
|
+
|
|
738
|
+
// Dispatch
|
|
739
|
+
const encoder = this.device.createCommandEncoder();
|
|
740
|
+
const pass = encoder.beginComputePass();
|
|
741
|
+
pass.setPipeline(this.particlePipeline);
|
|
742
|
+
pass.setBindGroup(0, this.particleBindGroup);
|
|
743
|
+
|
|
744
|
+
const workgroupSize = 256;
|
|
745
|
+
const workgroupCount = Math.ceil(this.particleCount / workgroupSize);
|
|
746
|
+
pass.dispatchWorkgroups(workgroupCount);
|
|
747
|
+
pass.end();
|
|
748
|
+
|
|
749
|
+
this.device.queue.submit([encoder.finish()]);
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
/**
|
|
753
|
+
* Read back particle data from the GPU.
|
|
754
|
+
* Returns a Float32Array of all particle data (position, velocity, life, phase).
|
|
755
|
+
*
|
|
756
|
+
* @returns {Promise<Float32Array>} Particle data array
|
|
757
|
+
*/
|
|
758
|
+
async getParticleData() {
|
|
759
|
+
if (!this._initialized || !this.device) {
|
|
760
|
+
return new Float32Array(0);
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
const totalBytes = this.particleCount * this.particleStride * 4;
|
|
764
|
+
|
|
765
|
+
const encoder = this.device.createCommandEncoder();
|
|
766
|
+
encoder.copyBufferToBuffer(
|
|
767
|
+
this.particleBuffer, 0,
|
|
768
|
+
this.particleReadBuffer, 0,
|
|
769
|
+
totalBytes
|
|
770
|
+
);
|
|
771
|
+
this.device.queue.submit([encoder.finish()]);
|
|
772
|
+
|
|
773
|
+
await this.particleReadBuffer.mapAsync(GPUMapMode.READ);
|
|
774
|
+
const data = new Float32Array(this.particleReadBuffer.getMappedRange().slice(0));
|
|
775
|
+
this.particleReadBuffer.unmap();
|
|
776
|
+
|
|
777
|
+
return data;
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
/**
|
|
781
|
+
* Get the GPU particle buffer directly for use in a render pipeline
|
|
782
|
+
* (zero-copy path).
|
|
783
|
+
* @returns {GPUBuffer|null}
|
|
784
|
+
*/
|
|
785
|
+
getParticleBuffer() {
|
|
786
|
+
return this.particleBuffer;
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
// -----------------------------------------------------------------------
|
|
790
|
+
// Audio FFT
|
|
791
|
+
// -----------------------------------------------------------------------
|
|
792
|
+
|
|
793
|
+
/**
|
|
794
|
+
* Return the WGSL source for the FFT compute shader.
|
|
795
|
+
* @returns {string}
|
|
796
|
+
*/
|
|
797
|
+
createFFTComputeShader() {
|
|
798
|
+
return FFT_COMPUTE_WGSL;
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
/**
|
|
802
|
+
* Build FFT compute pipelines and allocate GPU buffers.
|
|
803
|
+
* @private
|
|
804
|
+
*/
|
|
805
|
+
_createFFTPipeline() {
|
|
806
|
+
const device = this.device;
|
|
807
|
+
const N = this.fftSize;
|
|
808
|
+
|
|
809
|
+
// Input buffer (time-domain samples)
|
|
810
|
+
this.fftInputBuffer = device.createBuffer({
|
|
811
|
+
size: N * 4,
|
|
812
|
+
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST
|
|
813
|
+
});
|
|
814
|
+
|
|
815
|
+
// Work buffer: N real + N imag + 1 stage index = (2N + 1) floats
|
|
816
|
+
const workSize = (2 * N + 4) * 4; // +4 for alignment
|
|
817
|
+
this.fftWorkBuffer = device.createBuffer({
|
|
818
|
+
size: workSize,
|
|
819
|
+
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST
|
|
820
|
+
});
|
|
821
|
+
|
|
822
|
+
// Output bands buffer
|
|
823
|
+
this.fftBandsBuffer = device.createBuffer({
|
|
824
|
+
size: this.bandCount * 4,
|
|
825
|
+
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC
|
|
826
|
+
});
|
|
827
|
+
|
|
828
|
+
// Read buffer for bands
|
|
829
|
+
this.fftReadBuffer = device.createBuffer({
|
|
830
|
+
size: this.bandCount * 4,
|
|
831
|
+
usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST
|
|
832
|
+
});
|
|
833
|
+
|
|
834
|
+
// Params uniform
|
|
835
|
+
this.fftParamsBuffer = device.createBuffer({
|
|
836
|
+
size: 16, // FFTParams struct
|
|
837
|
+
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
|
|
838
|
+
});
|
|
839
|
+
|
|
840
|
+
// Write FFT params
|
|
841
|
+
const fftParamsData = new ArrayBuffer(16);
|
|
842
|
+
const fftParamsDV = new DataView(fftParamsData);
|
|
843
|
+
fftParamsDV.setUint32(0, N, true); // sampleCount
|
|
844
|
+
fftParamsDV.setUint32(4, this.bandCount, true); // bandCount
|
|
845
|
+
fftParamsDV.setFloat32(8, 44100.0, true); // sampleRate
|
|
846
|
+
fftParamsDV.setFloat32(12, 0.0, true); // _pad
|
|
847
|
+
device.queue.writeBuffer(this.fftParamsBuffer, 0, fftParamsData);
|
|
848
|
+
|
|
849
|
+
// Shader module
|
|
850
|
+
const shaderModule = device.createShaderModule({
|
|
851
|
+
label: 'VIB3 FFT Compute',
|
|
852
|
+
code: FFT_COMPUTE_WGSL
|
|
853
|
+
});
|
|
854
|
+
|
|
855
|
+
// Bind group layout (shared for all 3 entry points)
|
|
856
|
+
const bindGroupLayout = device.createBindGroupLayout({
|
|
857
|
+
entries: [
|
|
858
|
+
{ binding: 0, visibility: GPUShaderStage.COMPUTE, buffer: { type: 'read-only-storage' } },
|
|
859
|
+
{ binding: 1, visibility: GPUShaderStage.COMPUTE, buffer: { type: 'storage' } },
|
|
860
|
+
{ binding: 2, visibility: GPUShaderStage.COMPUTE, buffer: { type: 'storage' } },
|
|
861
|
+
{ binding: 3, visibility: GPUShaderStage.COMPUTE, buffer: { type: 'uniform' } }
|
|
862
|
+
]
|
|
863
|
+
});
|
|
864
|
+
|
|
865
|
+
const pipelineLayout = device.createPipelineLayout({
|
|
866
|
+
bindGroupLayouts: [bindGroupLayout]
|
|
867
|
+
});
|
|
868
|
+
|
|
869
|
+
// Three pipelines for three entry points
|
|
870
|
+
this.fftBitReversePipeline = device.createComputePipeline({
|
|
871
|
+
label: 'VIB3 FFT BitReverse',
|
|
872
|
+
layout: pipelineLayout,
|
|
873
|
+
compute: { module: shaderModule, entryPoint: 'fftBitReverse' }
|
|
874
|
+
});
|
|
875
|
+
|
|
876
|
+
this.fftButterflyPipeline = device.createComputePipeline({
|
|
877
|
+
label: 'VIB3 FFT Butterfly',
|
|
878
|
+
layout: pipelineLayout,
|
|
879
|
+
compute: { module: shaderModule, entryPoint: 'fftButterfly' }
|
|
880
|
+
});
|
|
881
|
+
|
|
882
|
+
this.fftBandsPipeline = device.createComputePipeline({
|
|
883
|
+
label: 'VIB3 FFT Bands',
|
|
884
|
+
layout: pipelineLayout,
|
|
885
|
+
compute: { module: shaderModule, entryPoint: 'fftBands' }
|
|
886
|
+
});
|
|
887
|
+
|
|
888
|
+
// Bind group
|
|
889
|
+
this.fftBindGroup = device.createBindGroup({
|
|
890
|
+
layout: bindGroupLayout,
|
|
891
|
+
entries: [
|
|
892
|
+
{ binding: 0, resource: { buffer: this.fftInputBuffer } },
|
|
893
|
+
{ binding: 1, resource: { buffer: this.fftWorkBuffer } },
|
|
894
|
+
{ binding: 2, resource: { buffer: this.fftBandsBuffer } },
|
|
895
|
+
{ binding: 3, resource: { buffer: this.fftParamsBuffer } }
|
|
896
|
+
]
|
|
897
|
+
});
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
/**
|
|
901
|
+
* Process time-domain audio data through the GPU FFT pipeline.
|
|
902
|
+
*
|
|
903
|
+
* @param {Float32Array} audioData - Time-domain audio samples (length should equal fftSize)
|
|
904
|
+
* @returns {Promise<{bands: Float32Array, bass: number, mid: number, high: number}>}
|
|
905
|
+
*/
|
|
906
|
+
async processAudioFFT(audioData) {
|
|
907
|
+
if (!this._initialized || !this.device) {
|
|
908
|
+
return { bands: new Float32Array(this.bandCount), bass: 0, mid: 0, high: 0 };
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
const N = this.fftSize;
|
|
912
|
+
|
|
913
|
+
// Pad or truncate input to fftSize
|
|
914
|
+
let input = audioData;
|
|
915
|
+
if (audioData.length !== N) {
|
|
916
|
+
input = new Float32Array(N);
|
|
917
|
+
input.set(audioData.subarray(0, Math.min(audioData.length, N)));
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
// Upload audio data
|
|
921
|
+
this.device.queue.writeBuffer(this.fftInputBuffer, 0, input);
|
|
922
|
+
|
|
923
|
+
const encoder = this.device.createCommandEncoder();
|
|
924
|
+
|
|
925
|
+
// Pass 1: Bit-reverse copy with windowing
|
|
926
|
+
{
|
|
927
|
+
const pass = encoder.beginComputePass();
|
|
928
|
+
pass.setPipeline(this.fftBitReversePipeline);
|
|
929
|
+
pass.setBindGroup(0, this.fftBindGroup);
|
|
930
|
+
pass.dispatchWorkgroups(Math.ceil(N / 256));
|
|
931
|
+
pass.end();
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
// Pass 2..log2(N): Butterfly stages
|
|
935
|
+
const numStages = Math.log2(N);
|
|
936
|
+
for (let stage = 0; stage < numStages; stage++) {
|
|
937
|
+
// Write stage index into the reserved slot in fftWorkBuffer
|
|
938
|
+
const stageData = new Float32Array([stage]);
|
|
939
|
+
this.device.queue.writeBuffer(this.fftWorkBuffer, 2 * N * 4, stageData);
|
|
940
|
+
|
|
941
|
+
// Submit the pending commands so the stage index write is visible
|
|
942
|
+
this.device.queue.submit([encoder.finish()]);
|
|
943
|
+
|
|
944
|
+
const stageEncoder = this.device.createCommandEncoder();
|
|
945
|
+
const pass = stageEncoder.beginComputePass();
|
|
946
|
+
pass.setPipeline(this.fftButterflyPipeline);
|
|
947
|
+
pass.setBindGroup(0, this.fftBindGroup);
|
|
948
|
+
pass.dispatchWorkgroups(Math.ceil(N / 2 / 256));
|
|
949
|
+
pass.end();
|
|
950
|
+
|
|
951
|
+
// Use stageEncoder for next iteration or final steps
|
|
952
|
+
if (stage < numStages - 1) {
|
|
953
|
+
this.device.queue.submit([stageEncoder.finish()]);
|
|
954
|
+
} else {
|
|
955
|
+
// Last stage -- continue to band computation
|
|
956
|
+
const bandsPass = stageEncoder.beginComputePass();
|
|
957
|
+
bandsPass.setPipeline(this.fftBandsPipeline);
|
|
958
|
+
bandsPass.setBindGroup(0, this.fftBindGroup);
|
|
959
|
+
bandsPass.dispatchWorkgroups(Math.ceil(this.bandCount / 32));
|
|
960
|
+
bandsPass.end();
|
|
961
|
+
|
|
962
|
+
// Copy bands to readback buffer
|
|
963
|
+
stageEncoder.copyBufferToBuffer(
|
|
964
|
+
this.fftBandsBuffer, 0,
|
|
965
|
+
this.fftReadBuffer, 0,
|
|
966
|
+
this.bandCount * 4
|
|
967
|
+
);
|
|
968
|
+
|
|
969
|
+
this.device.queue.submit([stageEncoder.finish()]);
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
// Read back bands
|
|
974
|
+
await this.fftReadBuffer.mapAsync(GPUMapMode.READ);
|
|
975
|
+
const bandsData = new Float32Array(this.fftReadBuffer.getMappedRange().slice(0));
|
|
976
|
+
this.fftReadBuffer.unmap();
|
|
977
|
+
|
|
978
|
+
// Derive bass / mid / high from bands
|
|
979
|
+
// bass: bands 0-7, mid: bands 8-19, high: bands 20-31
|
|
980
|
+
const bassEnd = Math.floor(this.bandCount * 0.25);
|
|
981
|
+
const midEnd = Math.floor(this.bandCount * 0.625);
|
|
982
|
+
|
|
983
|
+
let bass = 0, mid = 0, high = 0;
|
|
984
|
+
for (let i = 0; i < this.bandCount; i++) {
|
|
985
|
+
if (i < bassEnd) {
|
|
986
|
+
bass += bandsData[i];
|
|
987
|
+
} else if (i < midEnd) {
|
|
988
|
+
mid += bandsData[i];
|
|
989
|
+
} else {
|
|
990
|
+
high += bandsData[i];
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
bass /= bassEnd || 1;
|
|
995
|
+
mid /= (midEnd - bassEnd) || 1;
|
|
996
|
+
high /= (this.bandCount - midEnd) || 1;
|
|
997
|
+
|
|
998
|
+
return { bands: bandsData, bass, mid, high };
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
// -----------------------------------------------------------------------
|
|
1002
|
+
// Cleanup
|
|
1003
|
+
// -----------------------------------------------------------------------
|
|
1004
|
+
|
|
1005
|
+
/**
|
|
1006
|
+
* Release all GPU resources.
|
|
1007
|
+
*/
|
|
1008
|
+
dispose() {
|
|
1009
|
+
const buffers = [
|
|
1010
|
+
this.particleBuffer,
|
|
1011
|
+
this.particleParamsBuffer,
|
|
1012
|
+
this.particleReadBuffer,
|
|
1013
|
+
this.fftInputBuffer,
|
|
1014
|
+
this.fftWorkBuffer,
|
|
1015
|
+
this.fftBandsBuffer,
|
|
1016
|
+
this.fftParamsBuffer,
|
|
1017
|
+
this.fftReadBuffer
|
|
1018
|
+
];
|
|
1019
|
+
|
|
1020
|
+
for (const buf of buffers) {
|
|
1021
|
+
if (buf) {
|
|
1022
|
+
try {
|
|
1023
|
+
buf.destroy();
|
|
1024
|
+
} catch (_e) {
|
|
1025
|
+
// Buffer may already be destroyed
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
|
|
1030
|
+
this.particleBuffer = null;
|
|
1031
|
+
this.particleParamsBuffer = null;
|
|
1032
|
+
this.particleReadBuffer = null;
|
|
1033
|
+
this.fftInputBuffer = null;
|
|
1034
|
+
this.fftWorkBuffer = null;
|
|
1035
|
+
this.fftBandsBuffer = null;
|
|
1036
|
+
this.fftParamsBuffer = null;
|
|
1037
|
+
this.fftReadBuffer = null;
|
|
1038
|
+
|
|
1039
|
+
this.particlePipeline = null;
|
|
1040
|
+
this.fftBitReversePipeline = null;
|
|
1041
|
+
this.fftButterflyPipeline = null;
|
|
1042
|
+
this.fftBandsPipeline = null;
|
|
1043
|
+
|
|
1044
|
+
this.particleBindGroup = null;
|
|
1045
|
+
this.fftBindGroup = null;
|
|
1046
|
+
|
|
1047
|
+
this.device = null;
|
|
1048
|
+
this.adapter = null;
|
|
1049
|
+
this._initialized = false;
|
|
1050
|
+
}
|
|
1051
|
+
}
|