@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.
Files changed (258) hide show
  1. package/CHANGELOG.md +118 -0
  2. package/DOCS/BLUEPRINT_EXECUTION_PLAN_2026-01-07.md +34 -0
  3. package/DOCS/CI_TESTING.md +38 -0
  4. package/DOCS/CLI_ONBOARDING.md +75 -0
  5. package/DOCS/CONTROL_REFERENCE.md +64 -0
  6. package/DOCS/DEV_TRACK_ANALYSIS.md +77 -0
  7. package/DOCS/DEV_TRACK_PLAN_2026-01-07.md +42 -0
  8. package/DOCS/DEV_TRACK_SESSION_2026-01-31.md +220 -0
  9. package/DOCS/ENV_SETUP.md +189 -0
  10. package/DOCS/EXPORT_FORMATS.md +417 -0
  11. package/DOCS/GPU_DISPOSAL_GUIDE.md +21 -0
  12. package/DOCS/LICENSING_TIERS.md +275 -0
  13. package/DOCS/MASTER_PLAN_2026-01-31.md +570 -0
  14. package/DOCS/OBS_SETUP_GUIDE.md +98 -0
  15. package/DOCS/PROJECT_SETUP.md +66 -0
  16. package/DOCS/RENDERER_LIFECYCLE.md +40 -0
  17. package/DOCS/REPO_MANIFEST.md +121 -0
  18. package/DOCS/SESSION_014_PLAN.md +195 -0
  19. package/DOCS/SESSION_LOG_2026-01-07.md +56 -0
  20. package/DOCS/STRATEGIC_BLUEPRINT_2026-01-07.md +72 -0
  21. package/DOCS/SYSTEM_AUDIT_2026-01-30.md +738 -0
  22. package/DOCS/SYSTEM_INVENTORY.md +520 -0
  23. package/DOCS/TELEMETRY_EXPORTS.md +34 -0
  24. package/DOCS/WEBGPU_STATUS.md +38 -0
  25. package/DOCS/XR_BENCHMARKS.md +608 -0
  26. package/LICENSE +21 -0
  27. package/README.md +426 -0
  28. package/docs/.nojekyll +0 -0
  29. package/docs/01-dissolution_of_euclidean_hegemony.html +346 -0
  30. package/docs/02-hyperspatial_ego_death.html +346 -0
  31. package/docs/03-post_cartesian_sublime.html +346 -0
  32. package/docs/04-crystalline_void_meditation.html +346 -0
  33. package/docs/05-quantum_decoherence_ballet.html +346 -0
  34. package/docs/06-dissolution_of_euclidean_hegemony.html +346 -0
  35. package/docs/07-hyperspatial_ego_death.html +346 -0
  36. package/docs/08-post_cartesian_sublime.html +346 -0
  37. package/docs/09-crystalline_void_meditation.html +346 -0
  38. package/docs/10-quantum_decoherence_ballet.html +346 -0
  39. package/docs/11-dissolution_of_euclidean_hegemony.html +346 -0
  40. package/docs/12-hyperspatial_ego_death.html +346 -0
  41. package/docs/13-post_cartesian_sublime.html +346 -0
  42. package/docs/index.html +794 -0
  43. package/docs/test-hub.html +441 -0
  44. package/docs/url-state.js +102 -0
  45. package/docs/vib3-exports/01-quantum-quantum-tetrahedron-lattice.html +489 -0
  46. package/docs/vib3-exports/02-quantum-quantum-hypersphere-matrix.html +489 -0
  47. package/docs/vib3-exports/03-quantum-quantum-hypertetra-fractal.html +489 -0
  48. package/docs/vib3-exports/04-faceted-faceted-crystal-structure.html +407 -0
  49. package/docs/vib3-exports/05-faceted-faceted-klein-bottle.html +407 -0
  50. package/docs/vib3-exports/06-faceted-faceted-hypertetra-torus.html +407 -0
  51. package/docs/vib3-exports/07-holographic-holographic-wave-field.html +457 -0
  52. package/docs/vib3-exports/08-holographic-holographic-hypersphere-sphere.html +457 -0
  53. package/docs/vib3-exports/09-holographic-holographic-hypertetra-crystal.html +457 -0
  54. package/docs/vib3-exports/index.html +238 -0
  55. package/docs/webgpu-live.html +702 -0
  56. package/package.json +367 -0
  57. package/src/advanced/AIPresetGenerator.js +777 -0
  58. package/src/advanced/MIDIController.js +703 -0
  59. package/src/advanced/OffscreenWorker.js +1051 -0
  60. package/src/advanced/WebGPUCompute.js +1051 -0
  61. package/src/advanced/WebXRRenderer.js +680 -0
  62. package/src/agent/cli/AgentCLI.js +615 -0
  63. package/src/agent/cli/index.js +14 -0
  64. package/src/agent/index.js +73 -0
  65. package/src/agent/mcp/MCPServer.js +950 -0
  66. package/src/agent/mcp/index.js +9 -0
  67. package/src/agent/mcp/tools.js +548 -0
  68. package/src/agent/telemetry/EventStream.js +669 -0
  69. package/src/agent/telemetry/Instrumentation.js +618 -0
  70. package/src/agent/telemetry/TelemetryExporters.js +427 -0
  71. package/src/agent/telemetry/TelemetryService.js +464 -0
  72. package/src/agent/telemetry/index.js +52 -0
  73. package/src/benchmarks/BenchmarkRunner.js +381 -0
  74. package/src/benchmarks/MetricsCollector.js +299 -0
  75. package/src/benchmarks/index.js +9 -0
  76. package/src/benchmarks/scenes.js +259 -0
  77. package/src/cli/index.js +675 -0
  78. package/src/config/ApiConfig.js +88 -0
  79. package/src/core/CanvasManager.js +217 -0
  80. package/src/core/ErrorReporter.js +117 -0
  81. package/src/core/ParameterMapper.js +333 -0
  82. package/src/core/Parameters.js +396 -0
  83. package/src/core/RendererContracts.js +200 -0
  84. package/src/core/UnifiedResourceManager.js +370 -0
  85. package/src/core/VIB3Engine.js +636 -0
  86. package/src/core/renderers/FacetedRendererAdapter.js +32 -0
  87. package/src/core/renderers/HolographicRendererAdapter.js +29 -0
  88. package/src/core/renderers/QuantumRendererAdapter.js +29 -0
  89. package/src/core/renderers/RendererLifecycleManager.js +63 -0
  90. package/src/creative/ColorPresetsSystem.js +980 -0
  91. package/src/creative/ParameterTimeline.js +1061 -0
  92. package/src/creative/PostProcessingPipeline.js +1113 -0
  93. package/src/creative/TransitionAnimator.js +683 -0
  94. package/src/export/CSSExporter.js +226 -0
  95. package/src/export/CardGeneratorBase.js +279 -0
  96. package/src/export/ExportManager.js +580 -0
  97. package/src/export/FacetedCardGenerator.js +279 -0
  98. package/src/export/HolographicCardGenerator.js +543 -0
  99. package/src/export/LottieExporter.js +552 -0
  100. package/src/export/QuantumCardGenerator.js +315 -0
  101. package/src/export/SVGExporter.js +519 -0
  102. package/src/export/ShaderExporter.js +903 -0
  103. package/src/export/TradingCardGenerator.js +3055 -0
  104. package/src/export/TradingCardManager.js +181 -0
  105. package/src/export/VIB3PackageExporter.js +559 -0
  106. package/src/export/index.js +14 -0
  107. package/src/export/systems/TradingCardSystemFaceted.js +494 -0
  108. package/src/export/systems/TradingCardSystemHolographic.js +452 -0
  109. package/src/export/systems/TradingCardSystemQuantum.js +411 -0
  110. package/src/faceted/FacetedSystem.js +963 -0
  111. package/src/features/CollectionManager.js +433 -0
  112. package/src/gallery/CollectionManager.js +240 -0
  113. package/src/gallery/GallerySystem.js +485 -0
  114. package/src/geometry/GeometryFactory.js +314 -0
  115. package/src/geometry/GeometryLibrary.js +72 -0
  116. package/src/geometry/buffers/BufferBuilder.js +338 -0
  117. package/src/geometry/buffers/index.js +18 -0
  118. package/src/geometry/generators/Crystal.js +420 -0
  119. package/src/geometry/generators/Fractal.js +298 -0
  120. package/src/geometry/generators/KleinBottle.js +197 -0
  121. package/src/geometry/generators/Sphere.js +192 -0
  122. package/src/geometry/generators/Tesseract.js +160 -0
  123. package/src/geometry/generators/Tetrahedron.js +225 -0
  124. package/src/geometry/generators/Torus.js +304 -0
  125. package/src/geometry/generators/Wave.js +341 -0
  126. package/src/geometry/index.js +142 -0
  127. package/src/geometry/warp/HypersphereCore.js +211 -0
  128. package/src/geometry/warp/HypertetraCore.js +386 -0
  129. package/src/geometry/warp/index.js +57 -0
  130. package/src/holograms/HolographicVisualizer.js +1073 -0
  131. package/src/holograms/RealHolographicSystem.js +966 -0
  132. package/src/holograms/variantRegistry.js +69 -0
  133. package/src/integrations/FigmaPlugin.js +854 -0
  134. package/src/integrations/OBSMode.js +754 -0
  135. package/src/integrations/ThreeJsPackage.js +660 -0
  136. package/src/integrations/TouchDesignerExport.js +552 -0
  137. package/src/integrations/frameworks/Vib3React.js +591 -0
  138. package/src/integrations/frameworks/Vib3Svelte.js +654 -0
  139. package/src/integrations/frameworks/Vib3Vue.js +628 -0
  140. package/src/llm/LLMParameterInterface.js +240 -0
  141. package/src/llm/LLMParameterUI.js +577 -0
  142. package/src/math/Mat4x4.js +708 -0
  143. package/src/math/Projection.js +341 -0
  144. package/src/math/Rotor4D.js +637 -0
  145. package/src/math/Vec4.js +476 -0
  146. package/src/math/constants.js +164 -0
  147. package/src/math/index.js +68 -0
  148. package/src/math/projections.js +54 -0
  149. package/src/math/rotations.js +196 -0
  150. package/src/quantum/QuantumEngine.js +906 -0
  151. package/src/quantum/QuantumVisualizer.js +1103 -0
  152. package/src/reactivity/ReactivityConfig.js +499 -0
  153. package/src/reactivity/ReactivityManager.js +586 -0
  154. package/src/reactivity/SpatialInputSystem.js +1783 -0
  155. package/src/reactivity/index.js +93 -0
  156. package/src/render/CommandBuffer.js +465 -0
  157. package/src/render/MultiCanvasBridge.js +340 -0
  158. package/src/render/RenderCommand.js +514 -0
  159. package/src/render/RenderResourceRegistry.js +523 -0
  160. package/src/render/RenderState.js +552 -0
  161. package/src/render/RenderTarget.js +512 -0
  162. package/src/render/ShaderLoader.js +253 -0
  163. package/src/render/ShaderProgram.js +599 -0
  164. package/src/render/UnifiedRenderBridge.js +496 -0
  165. package/src/render/backends/WebGLBackend.js +1108 -0
  166. package/src/render/backends/WebGPUBackend.js +1409 -0
  167. package/src/render/commands/CommandBufferExecutor.js +607 -0
  168. package/src/render/commands/RenderCommandBuffer.js +661 -0
  169. package/src/render/commands/index.js +17 -0
  170. package/src/render/index.js +367 -0
  171. package/src/scene/Disposable.js +498 -0
  172. package/src/scene/MemoryPool.js +618 -0
  173. package/src/scene/Node4D.js +697 -0
  174. package/src/scene/ResourceManager.js +599 -0
  175. package/src/scene/Scene4D.js +540 -0
  176. package/src/scene/index.js +98 -0
  177. package/src/schemas/error.schema.json +84 -0
  178. package/src/schemas/extension.schema.json +88 -0
  179. package/src/schemas/index.js +214 -0
  180. package/src/schemas/parameters.schema.json +142 -0
  181. package/src/schemas/tool-pack.schema.json +44 -0
  182. package/src/schemas/tool-response.schema.json +127 -0
  183. package/src/shaders/common/fullscreen.vert.glsl +5 -0
  184. package/src/shaders/common/fullscreen.vert.wgsl +17 -0
  185. package/src/shaders/common/geometry24.glsl +65 -0
  186. package/src/shaders/common/geometry24.wgsl +54 -0
  187. package/src/shaders/common/rotation4d.glsl +85 -0
  188. package/src/shaders/common/rotation4d.wgsl +86 -0
  189. package/src/shaders/common/uniforms.glsl +44 -0
  190. package/src/shaders/common/uniforms.wgsl +48 -0
  191. package/src/shaders/faceted/faceted.frag.glsl +129 -0
  192. package/src/shaders/faceted/faceted.frag.wgsl +164 -0
  193. package/src/shaders/holographic/holographic.frag.glsl +406 -0
  194. package/src/shaders/holographic/holographic.frag.wgsl +185 -0
  195. package/src/shaders/quantum/quantum.frag.glsl +513 -0
  196. package/src/shaders/quantum/quantum.frag.wgsl +361 -0
  197. package/src/testing/ParallelTestFramework.js +519 -0
  198. package/src/testing/__snapshots__/exportFormats.test.js.snap +24 -0
  199. package/src/testing/exportFormats.test.js +8 -0
  200. package/src/testing/projections.test.js +14 -0
  201. package/src/testing/rotations.test.js +37 -0
  202. package/src/ui/InteractivityMenu.js +516 -0
  203. package/src/ui/StatusManager.js +96 -0
  204. package/src/ui/adaptive/renderers/webgpu/BufferLayout.ts +252 -0
  205. package/src/ui/adaptive/renderers/webgpu/PolytopeInstanceBuffer.ts +144 -0
  206. package/src/ui/adaptive/renderers/webgpu/TripleBufferedUniform.ts +170 -0
  207. package/src/ui/adaptive/renderers/webgpu/WebGPURenderer.ts +735 -0
  208. package/src/ui/adaptive/renderers/webgpu/index.ts +112 -0
  209. package/src/variations/VariationManager.js +431 -0
  210. package/src/viewer/AudioReactivity.js +505 -0
  211. package/src/viewer/CardBending.js +481 -0
  212. package/src/viewer/GalleryUI.js +832 -0
  213. package/src/viewer/ReactivityManager.js +590 -0
  214. package/src/viewer/TradingCardExporter.js +600 -0
  215. package/src/viewer/ViewerPortal.js +374 -0
  216. package/src/viewer/index.js +12 -0
  217. package/src/wasm/WasmLoader.js +296 -0
  218. package/src/wasm/index.js +132 -0
  219. package/tools/agentic/mcpTools.js +88 -0
  220. package/tools/cli/agent-cli.js +92 -0
  221. package/tools/export/formats.js +24 -0
  222. package/tools/math/rotation-baseline.mjs +64 -0
  223. package/tools/shader-sync-verify.js +937 -0
  224. package/tools/telemetry/manifestPipeline.js +141 -0
  225. package/tools/telemetry/telemetryEvents.js +35 -0
  226. package/types/adaptive-sdk.d.ts +185 -0
  227. package/types/advanced/AIPresetGenerator.d.ts +81 -0
  228. package/types/advanced/MIDIController.d.ts +100 -0
  229. package/types/advanced/OffscreenWorker.d.ts +82 -0
  230. package/types/advanced/WebGPUCompute.d.ts +52 -0
  231. package/types/advanced/WebXRRenderer.d.ts +77 -0
  232. package/types/advanced/index.d.ts +46 -0
  233. package/types/core/ErrorReporter.d.ts +50 -0
  234. package/types/core/VIB3Engine.d.ts +204 -0
  235. package/types/creative/ColorPresetsSystem.d.ts +91 -0
  236. package/types/creative/ParameterTimeline.d.ts +74 -0
  237. package/types/creative/PostProcessingPipeline.d.ts +109 -0
  238. package/types/creative/TransitionAnimator.d.ts +71 -0
  239. package/types/creative/index.d.ts +35 -0
  240. package/types/integrations/FigmaPlugin.d.ts +46 -0
  241. package/types/integrations/OBSMode.d.ts +74 -0
  242. package/types/integrations/ThreeJsPackage.d.ts +62 -0
  243. package/types/integrations/TouchDesignerExport.d.ts +36 -0
  244. package/types/integrations/Vib3React.d.ts +74 -0
  245. package/types/integrations/Vib3Svelte.d.ts +63 -0
  246. package/types/integrations/Vib3Vue.d.ts +55 -0
  247. package/types/integrations/index.d.ts +52 -0
  248. package/types/reactivity/SpatialInputSystem.d.ts +173 -0
  249. package/types/reactivity/index.d.ts +394 -0
  250. package/types/render/CommandBuffer.d.ts +169 -0
  251. package/types/render/RenderCommand.d.ts +312 -0
  252. package/types/render/RenderState.d.ts +279 -0
  253. package/types/render/RenderTarget.d.ts +254 -0
  254. package/types/render/ShaderProgram.d.ts +277 -0
  255. package/types/render/UnifiedRenderBridge.d.ts +143 -0
  256. package/types/render/WebGLBackend.d.ts +168 -0
  257. package/types/render/WebGPUBackend.d.ts +186 -0
  258. package/types/render/index.d.ts +141 -0
@@ -0,0 +1,680 @@
1
+ /**
2
+ * VIB3+ WebXR Immersive Renderer
3
+ * Renders 4D visualizations in VR/AR headsets via WebXR API.
4
+ *
5
+ * Provides stereoscopic rendering of VIB3 visualization systems
6
+ * inside immersive WebXR sessions (VR, AR, or inline). Integrates
7
+ * 6DOF head and controller tracking into the VIB3 parameter space,
8
+ * mapping physical orientation to 4D rotation planes.
9
+ *
10
+ * @module advanced/WebXRRenderer
11
+ */
12
+
13
+ /**
14
+ * Supported XR session modes.
15
+ * @typedef {'immersive-vr'|'immersive-ar'|'inline'} XRSessionMode
16
+ */
17
+
18
+ /**
19
+ * 6DOF pose data extracted from an XR frame.
20
+ * @typedef {Object} SixDOFPose
21
+ * @property {Float32Array} position - [x, y, z] head position in meters
22
+ * @property {Float32Array} orientation - [x, y, z, w] quaternion
23
+ * @property {number} rotXY - Derived rotation in XY plane (radians)
24
+ * @property {number} rotXZ - Derived rotation in XZ plane (radians)
25
+ * @property {number} rotYZ - Derived rotation in YZ plane (radians)
26
+ * @property {number} rotXW - Mapped 4D rotation in XW plane (radians)
27
+ * @property {number} rotYW - Mapped 4D rotation in YW plane (radians)
28
+ * @property {number} rotZW - Mapped 4D rotation in ZW plane (radians)
29
+ */
30
+
31
+ export class WebXRRenderer {
32
+ /**
33
+ * @param {Object} engine - VIB3Engine instance
34
+ */
35
+ constructor(engine) {
36
+ /** @type {Object} VIB3Engine reference */
37
+ this.engine = engine;
38
+
39
+ /** @type {XRSession|null} Active XR session */
40
+ this.xrSession = null;
41
+
42
+ /** @type {XRReferenceSpace|null} Reference space for pose calculations */
43
+ this.xrRefSpace = null;
44
+
45
+ /** @type {boolean} Whether an immersive session is active */
46
+ this.isImmersive = false;
47
+
48
+ /** @type {XRSessionMode} Current session mode */
49
+ this.mode = 'immersive-vr';
50
+
51
+ /** @type {WebGLRenderingContext|null} XR-compatible GL context */
52
+ this.gl = null;
53
+
54
+ /** @type {XRWebGLLayer|null} XR rendering layer */
55
+ this.xrGLLayer = null;
56
+
57
+ /** @type {number} Animation frame handle for cancellation */
58
+ this._animFrameHandle = 0;
59
+
60
+ /** @type {Map<string, XRInputSource>} Connected XR input sources */
61
+ this._inputSources = new Map();
62
+
63
+ /** @type {SixDOFPose|null} Most recent 6DOF pose data */
64
+ this._lastPose = null;
65
+
66
+ /** @type {Function|null} External frame callback */
67
+ this._onFrameCallback = null;
68
+
69
+ /** @type {boolean} Internal disposed flag */
70
+ this._disposed = false;
71
+
72
+ /** @type {Object} Support status cache */
73
+ this._supportCache = {};
74
+ }
75
+
76
+ // -----------------------------------------------------------------------
77
+ // Capability Detection
78
+ // -----------------------------------------------------------------------
79
+
80
+ /**
81
+ * Check whether WebXR is available and which session modes are supported.
82
+ * @returns {Promise<{available: boolean, vr: boolean, ar: boolean, inline: boolean}>}
83
+ */
84
+ async checkSupport() {
85
+ const result = { available: false, vr: false, ar: false, inline: false };
86
+
87
+ if (!navigator.xr) {
88
+ return result;
89
+ }
90
+
91
+ result.available = true;
92
+
93
+ try {
94
+ result.vr = await navigator.xr.isSessionSupported('immersive-vr');
95
+ } catch (_e) {
96
+ result.vr = false;
97
+ }
98
+
99
+ try {
100
+ result.ar = await navigator.xr.isSessionSupported('immersive-ar');
101
+ } catch (_e) {
102
+ result.ar = false;
103
+ }
104
+
105
+ try {
106
+ result.inline = await navigator.xr.isSessionSupported('inline');
107
+ } catch (_e) {
108
+ result.inline = false;
109
+ }
110
+
111
+ this._supportCache = result;
112
+ return result;
113
+ }
114
+
115
+ // -----------------------------------------------------------------------
116
+ // Session Management
117
+ // -----------------------------------------------------------------------
118
+
119
+ /**
120
+ * Start an immersive XR session.
121
+ * Creates an XR-compatible WebGL context, binds framebuffers, and begins
122
+ * the XR render loop.
123
+ *
124
+ * @param {XRSessionMode} [mode='immersive-vr'] - Session mode
125
+ * @returns {Promise<XRSession>} The active XR session
126
+ * @throws {Error} If WebXR is not supported or session request fails
127
+ */
128
+ async startSession(mode = 'immersive-vr') {
129
+ if (this.xrSession) {
130
+ throw new Error('WebXRRenderer: Session already active. Call endSession() first.');
131
+ }
132
+
133
+ if (!navigator.xr) {
134
+ throw new Error('WebXR API not available in this browser.');
135
+ }
136
+
137
+ const supported = await navigator.xr.isSessionSupported(mode);
138
+ if (!supported) {
139
+ throw new Error(`WebXR mode "${mode}" is not supported on this device.`);
140
+ }
141
+
142
+ this.mode = mode;
143
+
144
+ // Request session with required features
145
+ const sessionInit = {
146
+ requiredFeatures: ['local-floor'],
147
+ optionalFeatures: ['bounded-floor', 'hand-tracking']
148
+ };
149
+
150
+ this.xrSession = await navigator.xr.requestSession(mode, sessionInit);
151
+ this.isImmersive = mode.startsWith('immersive');
152
+
153
+ // Create XR-compatible WebGL context
154
+ this.gl = this._createXRGLContext();
155
+
156
+ // Create XR WebGL layer
157
+ this.xrGLLayer = new XRWebGLLayer(this.xrSession, this.gl);
158
+
159
+ // Apply render state
160
+ await this.xrSession.updateRenderState({
161
+ baseLayer: this.xrGLLayer,
162
+ depthNear: 0.1,
163
+ depthFar: 1000.0
164
+ });
165
+
166
+ // Obtain reference space
167
+ try {
168
+ this.xrRefSpace = await this.xrSession.requestReferenceSpace('local-floor');
169
+ } catch (_e) {
170
+ // Fall back to 'local' if 'local-floor' unavailable
171
+ this.xrRefSpace = await this.xrSession.requestReferenceSpace('local');
172
+ }
173
+
174
+ // Listen for input source changes
175
+ this.xrSession.addEventListener('inputsourceschange', (e) => {
176
+ this._onInputSourcesChange(e);
177
+ });
178
+
179
+ // Listen for session end
180
+ this.xrSession.addEventListener('end', () => {
181
+ this._onSessionEnd();
182
+ });
183
+
184
+ // Start render loop
185
+ this._animFrameHandle = this.xrSession.requestAnimationFrame(
186
+ (time, frame) => this.onXRFrame(time, frame)
187
+ );
188
+
189
+ return this.xrSession;
190
+ }
191
+
192
+ /**
193
+ * End the current XR session gracefully.
194
+ * @returns {Promise<void>}
195
+ */
196
+ async endSession() {
197
+ if (!this.xrSession) {
198
+ return;
199
+ }
200
+
201
+ try {
202
+ await this.xrSession.end();
203
+ } catch (_e) {
204
+ // Session may already be ended
205
+ }
206
+
207
+ this._onSessionEnd();
208
+ }
209
+
210
+ /**
211
+ * Register a callback to be invoked each XR frame after rendering.
212
+ * Receives (time, frame, pose) arguments.
213
+ * @param {Function} callback
214
+ */
215
+ onFrame(callback) {
216
+ this._onFrameCallback = callback;
217
+ }
218
+
219
+ // -----------------------------------------------------------------------
220
+ // Render Loop
221
+ // -----------------------------------------------------------------------
222
+
223
+ /**
224
+ * XR frame callback. Retrieves viewer pose, renders each eye, and
225
+ * extracts 6DOF data for SpatialInputSystem.
226
+ *
227
+ * @param {DOMHighResTimeStamp} time - Current timestamp
228
+ * @param {XRFrame} frame - XR frame object
229
+ */
230
+ onXRFrame(time, frame) {
231
+ if (this._disposed || !this.xrSession) {
232
+ return;
233
+ }
234
+
235
+ // Request next frame first for smooth scheduling
236
+ this._animFrameHandle = this.xrSession.requestAnimationFrame(
237
+ (t, f) => this.onXRFrame(t, f)
238
+ );
239
+
240
+ const session = this.xrSession;
241
+ const glLayer = this.xrGLLayer;
242
+ const gl = this.gl;
243
+
244
+ if (!gl || !glLayer || !this.xrRefSpace) {
245
+ return;
246
+ }
247
+
248
+ const pose = frame.getViewerPose(this.xrRefSpace);
249
+ if (!pose) {
250
+ return;
251
+ }
252
+
253
+ // Extract and store 6DOF data
254
+ this._lastPose = this._extract6DOF(pose);
255
+
256
+ // Bind XR framebuffer
257
+ gl.bindFramebuffer(gl.FRAMEBUFFER, glLayer.framebuffer);
258
+ gl.clearColor(0.0, 0.0, 0.0, 1.0);
259
+ gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
260
+
261
+ // Render each eye view
262
+ for (const view of pose.views) {
263
+ const viewport = glLayer.getViewport(view);
264
+ if (viewport) {
265
+ this.renderEye(view, viewport);
266
+ }
267
+ }
268
+
269
+ // Process controller inputs
270
+ this._processInputSources(frame);
271
+
272
+ // Invoke external callback
273
+ if (this._onFrameCallback) {
274
+ try {
275
+ this._onFrameCallback(time, frame, this._lastPose);
276
+ } catch (_e) {
277
+ // Swallow errors in external callback to preserve render loop
278
+ }
279
+ }
280
+ }
281
+
282
+ /**
283
+ * Render the VIB3 visualization for a single eye.
284
+ *
285
+ * @param {XRView} view - XR view containing projection and transform matrices
286
+ * @param {XRViewport} viewport - Target viewport rectangle
287
+ */
288
+ renderEye(view, viewport) {
289
+ const gl = this.gl;
290
+ if (!gl) return;
291
+
292
+ gl.viewport(viewport.x, viewport.y, viewport.width, viewport.height);
293
+
294
+ // Extract matrices
295
+ const projectionMatrix = view.projectionMatrix;
296
+ const viewMatrix = view.transform.inverse.matrix;
297
+
298
+ // Apply 6DOF pose to VIB3 rotation parameters
299
+ if (this._lastPose && this.engine) {
300
+ this._applyPoseToEngine(this._lastPose);
301
+ }
302
+
303
+ // Render the current VIB3 system into this viewport
304
+ if (this.engine && typeof this.engine.renderFrame === 'function') {
305
+ this.engine.renderFrame({
306
+ gl,
307
+ projectionMatrix,
308
+ viewMatrix,
309
+ viewport: {
310
+ x: viewport.x,
311
+ y: viewport.y,
312
+ width: viewport.width,
313
+ height: viewport.height
314
+ },
315
+ isXR: true
316
+ });
317
+ } else {
318
+ // Fallback: draw a simple quad to confirm XR is active
319
+ this._renderFallbackQuad(gl, projectionMatrix, viewMatrix);
320
+ }
321
+ }
322
+
323
+ // -----------------------------------------------------------------------
324
+ // 6DOF Extraction
325
+ // -----------------------------------------------------------------------
326
+
327
+ /**
328
+ * Extract 6DOF pose data and derive VIB3-compatible rotation angles.
329
+ * Maps physical head orientation to 3D rotation planes (XY, XZ, YZ)
330
+ * and position displacement to 4D rotation planes (XW, YW, ZW).
331
+ *
332
+ * @param {XRViewerPose} pose - Viewer pose from XRFrame
333
+ * @returns {SixDOFPose} Extracted and mapped pose data
334
+ */
335
+ get6DOFInput() {
336
+ return this._lastPose;
337
+ }
338
+
339
+ /**
340
+ * @param {XRViewerPose} pose
341
+ * @returns {SixDOFPose}
342
+ * @private
343
+ */
344
+ _extract6DOF(pose) {
345
+ const transform = pose.transform;
346
+ const pos = transform.position;
347
+ const ori = transform.orientation;
348
+
349
+ const position = new Float32Array([pos.x, pos.y, pos.z]);
350
+ const orientation = new Float32Array([ori.x, ori.y, ori.z, ori.w]);
351
+
352
+ // Convert quaternion to Euler angles for 3D rotation planes
353
+ const euler = this._quaternionToEuler(ori.x, ori.y, ori.z, ori.w);
354
+
355
+ // Map position displacement to 4D rotations
356
+ // Scale: 1 meter of movement = 1 radian of 4D rotation
357
+ const posScale = 1.0;
358
+ const rotXW = pos.x * posScale;
359
+ const rotYW = pos.y * posScale;
360
+ const rotZW = pos.z * posScale;
361
+
362
+ return {
363
+ position,
364
+ orientation,
365
+ rotXY: euler.yaw, // Yaw -> XY plane
366
+ rotXZ: euler.pitch, // Pitch -> XZ plane
367
+ rotYZ: euler.roll, // Roll -> YZ plane
368
+ rotXW: this._wrapAngle(rotXW),
369
+ rotYW: this._wrapAngle(rotYW),
370
+ rotZW: this._wrapAngle(rotZW)
371
+ };
372
+ }
373
+
374
+ /**
375
+ * Convert quaternion (x, y, z, w) to Euler angles (yaw, pitch, roll).
376
+ * Uses ZYX convention.
377
+ *
378
+ * @param {number} qx
379
+ * @param {number} qy
380
+ * @param {number} qz
381
+ * @param {number} qw
382
+ * @returns {{yaw: number, pitch: number, roll: number}}
383
+ * @private
384
+ */
385
+ _quaternionToEuler(qx, qy, qz, qw) {
386
+ // Roll (X-axis)
387
+ const sinr_cosp = 2.0 * (qw * qx + qy * qz);
388
+ const cosr_cosp = 1.0 - 2.0 * (qx * qx + qy * qy);
389
+ const roll = Math.atan2(sinr_cosp, cosr_cosp);
390
+
391
+ // Pitch (Y-axis)
392
+ const sinp = 2.0 * (qw * qy - qz * qx);
393
+ let pitch;
394
+ if (Math.abs(sinp) >= 1.0) {
395
+ pitch = Math.sign(sinp) * (Math.PI / 2.0); // Clamp at poles
396
+ } else {
397
+ pitch = Math.asin(sinp);
398
+ }
399
+
400
+ // Yaw (Z-axis)
401
+ const siny_cosp = 2.0 * (qw * qz + qx * qy);
402
+ const cosy_cosp = 1.0 - 2.0 * (qy * qy + qz * qz);
403
+ const yaw = Math.atan2(siny_cosp, cosy_cosp);
404
+
405
+ return { yaw, pitch, roll };
406
+ }
407
+
408
+ /**
409
+ * Wrap angle into [0, 2*PI) range.
410
+ * @param {number} angle - Angle in radians
411
+ * @returns {number}
412
+ * @private
413
+ */
414
+ _wrapAngle(angle) {
415
+ const TWO_PI = Math.PI * 2.0;
416
+ let a = angle % TWO_PI;
417
+ if (a < 0) a += TWO_PI;
418
+ return a;
419
+ }
420
+
421
+ /**
422
+ * Apply extracted 6DOF pose to the VIB3 engine parameters.
423
+ * @param {SixDOFPose} pose
424
+ * @private
425
+ */
426
+ _applyPoseToEngine(pose) {
427
+ if (!this.engine || typeof this.engine.setParameter !== 'function') {
428
+ return;
429
+ }
430
+
431
+ this.engine.setParameter('rot4dXY', pose.rotXY);
432
+ this.engine.setParameter('rot4dXZ', pose.rotXZ);
433
+ this.engine.setParameter('rot4dYZ', pose.rotYZ);
434
+ this.engine.setParameter('rot4dXW', pose.rotXW);
435
+ this.engine.setParameter('rot4dYW', pose.rotYW);
436
+ this.engine.setParameter('rot4dZW', pose.rotZW);
437
+ }
438
+
439
+ // -----------------------------------------------------------------------
440
+ // Input Sources (Controllers / Hands)
441
+ // -----------------------------------------------------------------------
442
+
443
+ /**
444
+ * Handle XR input source changes.
445
+ * @param {XRInputSourcesChangeEvent} event
446
+ * @private
447
+ */
448
+ _onInputSourcesChange(event) {
449
+ for (const source of event.added) {
450
+ this._inputSources.set(source.handedness || 'none', source);
451
+ }
452
+
453
+ for (const source of event.removed) {
454
+ this._inputSources.delete(source.handedness || 'none');
455
+ }
456
+ }
457
+
458
+ /**
459
+ * Process input sources each frame for controller data.
460
+ * Maps controller axes/buttons to VIB3 parameters.
461
+ *
462
+ * @param {XRFrame} frame
463
+ * @private
464
+ */
465
+ _processInputSources(frame) {
466
+ if (!this.engine || typeof this.engine.setParameter !== 'function') {
467
+ return;
468
+ }
469
+
470
+ for (const [hand, source] of this._inputSources) {
471
+ if (!source.gamepad) continue;
472
+
473
+ const gp = source.gamepad;
474
+
475
+ // Map thumbstick axes to additional parameters
476
+ if (gp.axes.length >= 2) {
477
+ if (hand === 'left') {
478
+ // Left stick: morph and chaos
479
+ const morphValue = (gp.axes[0] + 1.0) * 1.0; // 0..2
480
+ const chaosValue = (gp.axes[1] + 1.0) * 0.5; // 0..1
481
+ this.engine.setParameter('morphFactor', morphValue);
482
+ this.engine.setParameter('chaos', chaosValue);
483
+ } else if (hand === 'right') {
484
+ // Right stick: gridDensity and speed
485
+ const densityValue = 4.0 + (gp.axes[0] + 1.0) * 48.0; // 4..100
486
+ const speedValue = 0.1 + (gp.axes[1] + 1.0) * 1.45; // 0.1..3
487
+ this.engine.setParameter('gridDensity', densityValue);
488
+ this.engine.setParameter('speed', speedValue);
489
+ }
490
+ }
491
+
492
+ // Map trigger to intensity
493
+ if (gp.buttons.length > 0 && gp.buttons[0]) {
494
+ const triggerVal = gp.buttons[0].value; // 0..1
495
+ if (hand === 'right') {
496
+ this.engine.setParameter('intensity', triggerVal);
497
+ }
498
+ }
499
+
500
+ // Map grip to dimension
501
+ if (gp.buttons.length > 1 && gp.buttons[1]) {
502
+ const gripVal = gp.buttons[1].value;
503
+ if (hand === 'left') {
504
+ const dimValue = 3.0 + gripVal * 1.5; // 3.0..4.5
505
+ this.engine.setParameter('dimension', dimValue);
506
+ }
507
+ }
508
+
509
+ // A/B or X/Y buttons for geometry cycling
510
+ if (gp.buttons.length > 4 && gp.buttons[4] && gp.buttons[4].pressed) {
511
+ if (this.engine.getParameter) {
512
+ const currentGeom = this.engine.getParameter('geometry') || 0;
513
+ const nextGeom = (currentGeom + 1) % 24;
514
+ this.engine.setParameter('geometry', nextGeom);
515
+ }
516
+ }
517
+ }
518
+ }
519
+
520
+ // -----------------------------------------------------------------------
521
+ // GL Context & Fallback
522
+ // -----------------------------------------------------------------------
523
+
524
+ /**
525
+ * Create a WebGL rendering context compatible with WebXR.
526
+ * @returns {WebGLRenderingContext}
527
+ * @private
528
+ */
529
+ _createXRGLContext() {
530
+ const canvas = document.createElement('canvas');
531
+ const gl = canvas.getContext('webgl2', { xrCompatible: true })
532
+ || canvas.getContext('webgl', { xrCompatible: true });
533
+
534
+ if (!gl) {
535
+ throw new Error('Failed to create XR-compatible WebGL context.');
536
+ }
537
+
538
+ gl.enable(gl.DEPTH_TEST);
539
+ gl.enable(gl.BLEND);
540
+ gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
541
+
542
+ return gl;
543
+ }
544
+
545
+ /**
546
+ * Render a simple colored quad as a fallback when the VIB3 engine
547
+ * does not provide a renderFrame method.
548
+ *
549
+ * @param {WebGLRenderingContext} gl
550
+ * @param {Float32Array} projectionMatrix
551
+ * @param {Float32Array} viewMatrix
552
+ * @private
553
+ */
554
+ _renderFallbackQuad(gl, projectionMatrix, viewMatrix) {
555
+ // Simple pass-through vertex shader
556
+ const vsSource = `
557
+ attribute vec4 a_position;
558
+ void main() {
559
+ gl_Position = a_position;
560
+ }
561
+ `;
562
+
563
+ // Animated color fragment shader
564
+ const fsSource = `
565
+ precision mediump float;
566
+ void main() {
567
+ gl_FragColor = vec4(0.2, 0.6, 1.0, 1.0);
568
+ }
569
+ `;
570
+
571
+ if (!this._fallbackProgram) {
572
+ const vs = this._compileShader(gl, gl.VERTEX_SHADER, vsSource);
573
+ const fs = this._compileShader(gl, gl.FRAGMENT_SHADER, fsSource);
574
+ if (!vs || !fs) return;
575
+
576
+ const program = gl.createProgram();
577
+ gl.attachShader(program, vs);
578
+ gl.attachShader(program, fs);
579
+ gl.linkProgram(program);
580
+
581
+ if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
582
+ return;
583
+ }
584
+
585
+ this._fallbackProgram = program;
586
+
587
+ // Full-screen triangle pair
588
+ const verts = new Float32Array([
589
+ -1, -1, 0, 1,
590
+ 1, -1, 0, 1,
591
+ -1, 1, 0, 1,
592
+ 1, 1, 0, 1
593
+ ]);
594
+
595
+ this._fallbackVBO = gl.createBuffer();
596
+ gl.bindBuffer(gl.ARRAY_BUFFER, this._fallbackVBO);
597
+ gl.bufferData(gl.ARRAY_BUFFER, verts, gl.STATIC_DRAW);
598
+ }
599
+
600
+ gl.useProgram(this._fallbackProgram);
601
+ gl.bindBuffer(gl.ARRAY_BUFFER, this._fallbackVBO);
602
+
603
+ const posLoc = gl.getAttribLocation(this._fallbackProgram, 'a_position');
604
+ gl.enableVertexAttribArray(posLoc);
605
+ gl.vertexAttribPointer(posLoc, 4, gl.FLOAT, false, 0, 0);
606
+
607
+ gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
608
+ }
609
+
610
+ /**
611
+ * Compile a GLSL shader.
612
+ * @param {WebGLRenderingContext} gl
613
+ * @param {number} type
614
+ * @param {string} source
615
+ * @returns {WebGLShader|null}
616
+ * @private
617
+ */
618
+ _compileShader(gl, type, source) {
619
+ const shader = gl.createShader(type);
620
+ gl.shaderSource(shader, source);
621
+ gl.compileShader(shader);
622
+
623
+ if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
624
+ gl.deleteShader(shader);
625
+ return null;
626
+ }
627
+
628
+ return shader;
629
+ }
630
+
631
+ // -----------------------------------------------------------------------
632
+ // Session End & Cleanup
633
+ // -----------------------------------------------------------------------
634
+
635
+ /**
636
+ * Internal handler for session termination.
637
+ * @private
638
+ */
639
+ _onSessionEnd() {
640
+ this.xrSession = null;
641
+ this.xrRefSpace = null;
642
+ this.xrGLLayer = null;
643
+ this.isImmersive = false;
644
+ this._inputSources.clear();
645
+ this._lastPose = null;
646
+ this._animFrameHandle = 0;
647
+ }
648
+
649
+ /**
650
+ * Dispose of all resources. After calling, this instance should not be reused.
651
+ */
652
+ dispose() {
653
+ this._disposed = true;
654
+
655
+ if (this.xrSession) {
656
+ try {
657
+ this.xrSession.end();
658
+ } catch (_e) {
659
+ // Ignore
660
+ }
661
+ }
662
+
663
+ this._onSessionEnd();
664
+
665
+ if (this.gl) {
666
+ if (this._fallbackProgram) {
667
+ this.gl.deleteProgram(this._fallbackProgram);
668
+ this._fallbackProgram = null;
669
+ }
670
+ if (this._fallbackVBO) {
671
+ this.gl.deleteBuffer(this._fallbackVBO);
672
+ this._fallbackVBO = null;
673
+ }
674
+ this.gl = null;
675
+ }
676
+
677
+ this.engine = null;
678
+ this._onFrameCallback = null;
679
+ }
680
+ }