@viji-dev/core 0.2.7 → 0.2.8

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.
@@ -1,1428 +0,0 @@
1
- (function() {
2
- "use strict";
3
- class ParameterSystem {
4
- // Parameter system for Phase 2 (new object-based approach)
5
- parameterDefinitions = /* @__PURE__ */ new Map();
6
- parameterGroups = /* @__PURE__ */ new Map();
7
- parameterValues = /* @__PURE__ */ new Map();
8
- parameterObjects = /* @__PURE__ */ new Map();
9
- // Maps parameter names to their objects
10
- parametersDefined = false;
11
- initialValuesSynced = false;
12
- // Track if initial values have been synced from host
13
- // Debug logging control
14
- debugMode = false;
15
- /**
16
- * Enable or disable debug logging
17
- */
18
- setDebugMode(enabled) {
19
- this.debugMode = enabled;
20
- }
21
- /**
22
- * Debug logging helper
23
- */
24
- debugLog(message, ...args) {
25
- if (this.debugMode) {
26
- console.log(message, ...args);
27
- }
28
- }
29
- // Message posting callback
30
- postMessageCallback;
31
- constructor(postMessageCallback) {
32
- this.postMessageCallback = postMessageCallback;
33
- }
34
- // Parameter helper function implementations (return parameter objects)
35
- createSliderParameter(defaultValue, config) {
36
- const paramName = config.label;
37
- const sliderObject = {
38
- value: defaultValue,
39
- min: config.min ?? 0,
40
- max: config.max ?? 100,
41
- step: config.step ?? 1,
42
- label: config.label,
43
- description: config.description ?? "",
44
- group: config.group ?? "general",
45
- category: config.category ?? "general"
46
- };
47
- const definition = {
48
- type: "slider",
49
- defaultValue,
50
- label: sliderObject.label,
51
- description: sliderObject.description,
52
- group: sliderObject.group,
53
- category: sliderObject.category,
54
- config: {
55
- min: sliderObject.min,
56
- max: sliderObject.max,
57
- step: sliderObject.step
58
- }
59
- };
60
- this.storeParameterDefinition(paramName, definition);
61
- this.parameterObjects.set(paramName, sliderObject);
62
- return sliderObject;
63
- }
64
- createColorParameter(defaultValue, config) {
65
- const paramName = config.label;
66
- const colorObject = {
67
- value: defaultValue,
68
- label: config.label,
69
- description: config.description ?? "",
70
- group: config.group ?? "general",
71
- category: config.category ?? "general"
72
- };
73
- const definition = {
74
- type: "color",
75
- defaultValue,
76
- label: colorObject.label,
77
- description: colorObject.description,
78
- group: colorObject.group,
79
- category: colorObject.category
80
- };
81
- this.storeParameterDefinition(paramName, definition);
82
- this.parameterObjects.set(paramName, colorObject);
83
- return colorObject;
84
- }
85
- createToggleParameter(defaultValue, config) {
86
- const paramName = config.label;
87
- const toggleObject = {
88
- value: defaultValue,
89
- label: config.label,
90
- description: config.description ?? "",
91
- group: config.group ?? "general",
92
- category: config.category ?? "general"
93
- };
94
- const definition = {
95
- type: "toggle",
96
- defaultValue,
97
- label: toggleObject.label,
98
- description: toggleObject.description,
99
- group: toggleObject.group,
100
- category: toggleObject.category
101
- };
102
- this.storeParameterDefinition(paramName, definition);
103
- this.parameterObjects.set(paramName, toggleObject);
104
- return toggleObject;
105
- }
106
- createSelectParameter(defaultValue, config) {
107
- const paramName = config.label;
108
- const selectObject = {
109
- value: defaultValue,
110
- options: config.options,
111
- label: config.label,
112
- description: config.description ?? "",
113
- group: config.group ?? "general",
114
- category: config.category ?? "general"
115
- };
116
- const definition = {
117
- type: "select",
118
- defaultValue,
119
- label: selectObject.label,
120
- description: selectObject.description,
121
- group: selectObject.group,
122
- category: selectObject.category,
123
- config: {
124
- options: selectObject.options
125
- }
126
- };
127
- this.storeParameterDefinition(paramName, definition);
128
- this.parameterObjects.set(paramName, selectObject);
129
- return selectObject;
130
- }
131
- createTextParameter(defaultValue, config) {
132
- const paramName = config.label;
133
- const textObject = {
134
- value: defaultValue,
135
- maxLength: config.maxLength ?? 1e3,
136
- label: config.label,
137
- description: config.description ?? "",
138
- group: config.group ?? "general",
139
- category: config.category ?? "general"
140
- };
141
- const definition = {
142
- type: "text",
143
- defaultValue,
144
- label: textObject.label,
145
- description: textObject.description,
146
- group: textObject.group,
147
- category: textObject.category,
148
- config: {
149
- maxLength: textObject.maxLength
150
- }
151
- };
152
- this.storeParameterDefinition(paramName, definition);
153
- this.parameterObjects.set(paramName, textObject);
154
- return textObject;
155
- }
156
- createNumberParameter(defaultValue, config) {
157
- const paramName = config.label;
158
- const numberObject = {
159
- value: defaultValue,
160
- min: config.min ?? 0,
161
- max: config.max ?? 100,
162
- step: config.step ?? 1,
163
- label: config.label,
164
- description: config.description ?? "",
165
- group: config.group ?? "general",
166
- category: config.category ?? "general"
167
- };
168
- const definition = {
169
- type: "number",
170
- defaultValue,
171
- label: numberObject.label,
172
- description: numberObject.description,
173
- group: numberObject.group,
174
- category: numberObject.category,
175
- config: {
176
- min: numberObject.min,
177
- max: numberObject.max,
178
- step: numberObject.step
179
- }
180
- };
181
- this.storeParameterDefinition(paramName, definition);
182
- this.parameterObjects.set(paramName, numberObject);
183
- return numberObject;
184
- }
185
- storeParameterDefinition(name, definition) {
186
- this.parameterDefinitions.set(name, definition);
187
- this.parameterValues.set(name, definition.defaultValue);
188
- }
189
- updateParameterValue(name, value) {
190
- const definition = this.parameterDefinitions.get(name);
191
- if (!definition) {
192
- console.warn(`Unknown parameter: ${name}. Available parameters:`, Array.from(this.parameterDefinitions.keys()));
193
- return false;
194
- }
195
- if (!this.validateParameterValue(name, value, definition)) {
196
- console.warn(`Validation failed for parameter ${name} = ${value}`);
197
- return false;
198
- }
199
- const currentValue = this.parameterValues.get(name);
200
- const isInitialSync = !this.initialValuesSynced;
201
- if (currentValue === value && !isInitialSync) {
202
- return false;
203
- }
204
- this.parameterValues.set(name, value);
205
- const parameterObject = this.parameterObjects.get(name);
206
- if (parameterObject) {
207
- parameterObject.value = value;
208
- }
209
- return true;
210
- }
211
- validateParameterValue(name, value, definition) {
212
- if (definition.validate && !definition.validate(value)) {
213
- console.error(`Custom validation failed for parameter '${name}': ${value}`);
214
- return false;
215
- }
216
- switch (definition.type) {
217
- case "slider":
218
- case "number":
219
- if (typeof value !== "number" || isNaN(value)) {
220
- console.error(`Parameter '${name}' must be a number, got: ${value}`);
221
- return false;
222
- }
223
- if (definition.config?.min !== void 0 && value < definition.config.min) {
224
- console.error(`Parameter '${name}' value ${value} is below minimum ${definition.config.min}`);
225
- return false;
226
- }
227
- if (definition.config?.max !== void 0 && value > definition.config.max) {
228
- console.error(`Parameter '${name}' value ${value} is above maximum ${definition.config.max}`);
229
- return false;
230
- }
231
- break;
232
- case "color":
233
- if (typeof value !== "string" || !/^#[0-9A-Fa-f]{6}$/.test(value)) {
234
- console.error(`Parameter '${name}' must be a valid hex color, got: ${value}`);
235
- return false;
236
- }
237
- break;
238
- case "toggle":
239
- if (typeof value !== "boolean") {
240
- console.error(`Parameter '${name}' must be a boolean, got: ${value}`);
241
- return false;
242
- }
243
- break;
244
- case "select":
245
- if (!definition.config?.options || !definition.config.options.includes(value)) {
246
- console.error(`Parameter '${name}' value ${value} is not in options: ${definition.config?.options}`);
247
- return false;
248
- }
249
- break;
250
- case "text":
251
- if (typeof value !== "string") {
252
- console.error(`Parameter '${name}' must be a string, got: ${value}`);
253
- return false;
254
- }
255
- if (definition.config?.maxLength && value.length > definition.config.maxLength) {
256
- console.error(`Parameter '${name}' text too long: ${value.length} > ${definition.config.maxLength}`);
257
- return false;
258
- }
259
- break;
260
- }
261
- return true;
262
- }
263
- // Reset parameter state (called when loading new scene)
264
- resetParameterState() {
265
- this.parametersDefined = false;
266
- this.initialValuesSynced = false;
267
- this.parameterDefinitions.clear();
268
- this.parameterGroups.clear();
269
- this.parameterValues.clear();
270
- this.parameterObjects.clear();
271
- }
272
- // Send all parameters (from helper functions) to host
273
- sendAllParametersToHost() {
274
- if (this.parametersDefined || this.parameterDefinitions.size === 0) {
275
- return;
276
- }
277
- try {
278
- const groups = /* @__PURE__ */ new Map();
279
- for (const [paramName, paramDef] of this.parameterDefinitions) {
280
- const groupName = paramDef.group || "general";
281
- if (!groups.has(groupName)) {
282
- const category = paramDef.category || "general";
283
- groups.set(groupName, {
284
- groupName,
285
- category,
286
- parameters: {}
287
- });
288
- }
289
- const group = groups.get(groupName);
290
- group.parameters[paramName] = paramDef;
291
- }
292
- this.parametersDefined = true;
293
- this.postMessageCallback("parameters-defined", {
294
- groups: Array.from(groups.values()),
295
- timestamp: performance.now()
296
- });
297
- this.debugLog(`All parameters sent to host: ${this.parameterDefinitions.size} parameters in ${groups.size} groups`);
298
- } catch (error) {
299
- this.postMessageCallback("parameter-validation-error", {
300
- message: `Failed to send parameters to host: ${error.message}`,
301
- code: "PARAMETER_SENDING_ERROR"
302
- });
303
- }
304
- }
305
- // Mark initial values as synced
306
- markInitialValuesSynced() {
307
- this.initialValuesSynced = true;
308
- }
309
- // Get parameter count for performance reporting
310
- getParameterCount() {
311
- return this.parameterDefinitions.size;
312
- }
313
- }
314
- class InteractionSystem {
315
- // Interaction enabled state
316
- isEnabled = true;
317
- // Mouse interaction state
318
- mouseState = {
319
- x: 0,
320
- y: 0,
321
- isInCanvas: false,
322
- isPressed: false,
323
- leftButton: false,
324
- rightButton: false,
325
- middleButton: false,
326
- velocity: { x: 0, y: 0 },
327
- deltaX: 0,
328
- deltaY: 0,
329
- wheelDelta: 0,
330
- wheelX: 0,
331
- wheelY: 0,
332
- wasPressed: false,
333
- wasReleased: false,
334
- wasMoved: false
335
- };
336
- // Keyboard interaction state
337
- keyboardState = {
338
- isPressed: (key) => this.keyboardState.activeKeys.has(key.toLowerCase()),
339
- wasPressed: (key) => this.keyboardState.pressedThisFrame.has(key.toLowerCase()),
340
- wasReleased: (key) => this.keyboardState.releasedThisFrame.has(key.toLowerCase()),
341
- activeKeys: /* @__PURE__ */ new Set(),
342
- pressedThisFrame: /* @__PURE__ */ new Set(),
343
- releasedThisFrame: /* @__PURE__ */ new Set(),
344
- lastKeyPressed: "",
345
- lastKeyReleased: "",
346
- shift: false,
347
- ctrl: false,
348
- alt: false,
349
- meta: false
350
- };
351
- // Touch interaction state
352
- touchState = {
353
- points: [],
354
- count: 0,
355
- started: [],
356
- moved: [],
357
- ended: [],
358
- primary: null,
359
- gestures: {
360
- isPinching: false,
361
- isRotating: false,
362
- isPanning: false,
363
- isTapping: false,
364
- pinchScale: 1,
365
- pinchDelta: 0,
366
- rotationAngle: 0,
367
- rotationDelta: 0,
368
- panDelta: { x: 0, y: 0 },
369
- tapCount: 0,
370
- lastTapTime: 0,
371
- tapPosition: null
372
- }
373
- };
374
- constructor() {
375
- this.handleMouseUpdate = this.handleMouseUpdate.bind(this);
376
- this.handleKeyboardUpdate = this.handleKeyboardUpdate.bind(this);
377
- this.handleTouchUpdate = this.handleTouchUpdate.bind(this);
378
- this.frameStart = this.frameStart.bind(this);
379
- }
380
- /**
381
- * Get the interaction APIs for inclusion in the viji object
382
- */
383
- getInteractionAPIs() {
384
- return {
385
- mouse: this.mouseState,
386
- keyboard: this.keyboardState,
387
- touches: this.touchState
388
- };
389
- }
390
- /**
391
- * Called at the start of each frame to reset frame-based events
392
- */
393
- frameStart() {
394
- this.mouseState.wasPressed = false;
395
- this.mouseState.wasReleased = false;
396
- this.mouseState.wasMoved = false;
397
- this.mouseState.wheelDelta = 0;
398
- this.mouseState.wheelX = 0;
399
- this.mouseState.wheelY = 0;
400
- this.keyboardState.pressedThisFrame.clear();
401
- this.keyboardState.releasedThisFrame.clear();
402
- this.touchState.started = [];
403
- this.touchState.moved = [];
404
- this.touchState.ended = [];
405
- this.touchState.gestures.isTapping = false;
406
- this.touchState.gestures.pinchDelta = 0;
407
- this.touchState.gestures.rotationDelta = 0;
408
- }
409
- /**
410
- * Handle mouse update messages from the host
411
- */
412
- handleMouseUpdate(data) {
413
- if (!this.isEnabled) return;
414
- this.mouseState.x = data.x;
415
- this.mouseState.y = data.y;
416
- this.mouseState.isInCanvas = data.isInCanvas !== void 0 ? data.isInCanvas : true;
417
- this.mouseState.leftButton = (data.buttons & 1) !== 0;
418
- this.mouseState.rightButton = (data.buttons & 2) !== 0;
419
- this.mouseState.middleButton = (data.buttons & 4) !== 0;
420
- this.mouseState.isPressed = data.buttons > 0;
421
- this.mouseState.deltaX = data.deltaX || 0;
422
- this.mouseState.deltaY = data.deltaY || 0;
423
- this.mouseState.wheelDelta = data.wheelDeltaY || 0;
424
- this.mouseState.wheelX = data.wheelDeltaX || 0;
425
- this.mouseState.wheelY = data.wheelDeltaY || 0;
426
- this.mouseState.velocity.x = data.deltaX || 0;
427
- this.mouseState.velocity.y = data.deltaY || 0;
428
- this.mouseState.wasPressed = data.wasPressed || false;
429
- this.mouseState.wasReleased = data.wasReleased || false;
430
- this.mouseState.wasMoved = data.deltaX !== 0 || data.deltaY !== 0;
431
- }
432
- /**
433
- * Handle keyboard update messages from the host
434
- */
435
- handleKeyboardUpdate(data) {
436
- if (!this.isEnabled) return;
437
- const key = data.key.toLowerCase();
438
- if (data.type === "keydown") {
439
- if (!this.keyboardState.activeKeys.has(key)) {
440
- this.keyboardState.activeKeys.add(key);
441
- this.keyboardState.pressedThisFrame.add(key);
442
- this.keyboardState.lastKeyPressed = data.key;
443
- }
444
- } else if (data.type === "keyup") {
445
- this.keyboardState.activeKeys.delete(key);
446
- this.keyboardState.releasedThisFrame.add(key);
447
- this.keyboardState.lastKeyReleased = data.key;
448
- }
449
- this.keyboardState.shift = data.shiftKey;
450
- this.keyboardState.ctrl = data.ctrlKey;
451
- this.keyboardState.alt = data.altKey;
452
- this.keyboardState.meta = data.metaKey;
453
- }
454
- /**
455
- * Handle touch update messages from the host
456
- */
457
- handleTouchUpdate(data) {
458
- if (!this.isEnabled) return;
459
- this.touchState.started = [];
460
- this.touchState.moved = [];
461
- this.touchState.ended = [];
462
- const touches = data.touches.map((touch) => ({
463
- id: touch.identifier,
464
- x: touch.clientX,
465
- y: touch.clientY,
466
- pressure: touch.pressure || 0,
467
- radius: Math.max(touch.radiusX || 0, touch.radiusY || 0),
468
- radiusX: touch.radiusX || 0,
469
- radiusY: touch.radiusY || 0,
470
- rotationAngle: touch.rotationAngle || 0,
471
- force: touch.force || touch.pressure || 0,
472
- deltaX: 0,
473
- // Could be calculated if we track previous positions
474
- deltaY: 0,
475
- velocity: { x: 0, y: 0 },
476
- // Could be calculated if we track movement
477
- isNew: data.type === "touchstart",
478
- isActive: true,
479
- isEnding: data.type === "touchend" || data.type === "touchcancel"
480
- }));
481
- this.touchState.points = touches;
482
- this.touchState.count = touches.length;
483
- this.touchState.primary = touches[0] || null;
484
- if (data.type === "touchstart") {
485
- this.touchState.started = touches;
486
- } else if (data.type === "touchmove") {
487
- this.touchState.moved = touches;
488
- } else if (data.type === "touchend" || data.type === "touchcancel") {
489
- this.touchState.ended = touches;
490
- }
491
- this.touchState.gestures = {
492
- isPinching: false,
493
- isRotating: false,
494
- isPanning: false,
495
- isTapping: false,
496
- pinchScale: 1,
497
- pinchDelta: 0,
498
- rotationAngle: 0,
499
- rotationDelta: 0,
500
- panDelta: { x: 0, y: 0 },
501
- tapCount: 0,
502
- lastTapTime: 0,
503
- tapPosition: null
504
- };
505
- }
506
- /**
507
- * Reset all interaction state (called when loading new scene)
508
- */
509
- resetInteractionState() {
510
- Object.assign(this.mouseState, {
511
- x: 0,
512
- y: 0,
513
- isInCanvas: false,
514
- isPressed: false,
515
- leftButton: false,
516
- rightButton: false,
517
- middleButton: false,
518
- velocity: { x: 0, y: 0 },
519
- deltaX: 0,
520
- deltaY: 0,
521
- wheelDelta: 0,
522
- wheelX: 0,
523
- wheelY: 0,
524
- wasPressed: false,
525
- wasReleased: false,
526
- wasMoved: false
527
- });
528
- this.keyboardState.activeKeys.clear();
529
- this.keyboardState.pressedThisFrame.clear();
530
- this.keyboardState.releasedThisFrame.clear();
531
- this.keyboardState.lastKeyPressed = "";
532
- this.keyboardState.lastKeyReleased = "";
533
- this.keyboardState.shift = false;
534
- this.keyboardState.ctrl = false;
535
- this.keyboardState.alt = false;
536
- this.keyboardState.meta = false;
537
- this.touchState.points = [];
538
- this.touchState.count = 0;
539
- this.touchState.started = [];
540
- this.touchState.moved = [];
541
- this.touchState.ended = [];
542
- this.touchState.primary = null;
543
- Object.assign(this.touchState.gestures, {
544
- isPinching: false,
545
- isRotating: false,
546
- isPanning: false,
547
- isTapping: false,
548
- pinchScale: 1,
549
- pinchDelta: 0,
550
- rotationAngle: 0,
551
- rotationDelta: 0,
552
- panDelta: { x: 0, y: 0 },
553
- tapCount: 0,
554
- lastTapTime: 0,
555
- tapPosition: null
556
- });
557
- }
558
- /**
559
- * Enable or disable interaction processing
560
- */
561
- setInteractionEnabled(enabled) {
562
- this.isEnabled = enabled;
563
- if (!enabled) {
564
- this.resetInteractionStates();
565
- }
566
- }
567
- /**
568
- * Get current interaction enabled state
569
- */
570
- getInteractionEnabled() {
571
- return this.isEnabled;
572
- }
573
- /**
574
- * Reset all interaction states to default values
575
- */
576
- resetInteractionStates() {
577
- this.mouseState.x = 0;
578
- this.mouseState.y = 0;
579
- this.mouseState.isInCanvas = false;
580
- this.mouseState.isPressed = false;
581
- this.mouseState.leftButton = false;
582
- this.mouseState.rightButton = false;
583
- this.mouseState.middleButton = false;
584
- this.mouseState.velocity.x = 0;
585
- this.mouseState.velocity.y = 0;
586
- this.mouseState.deltaX = 0;
587
- this.mouseState.deltaY = 0;
588
- this.mouseState.wheelDelta = 0;
589
- this.mouseState.wheelX = 0;
590
- this.mouseState.wheelY = 0;
591
- this.mouseState.wasPressed = false;
592
- this.mouseState.wasReleased = false;
593
- this.mouseState.wasMoved = false;
594
- this.keyboardState.activeKeys.clear();
595
- this.keyboardState.pressedThisFrame.clear();
596
- this.keyboardState.releasedThisFrame.clear();
597
- this.keyboardState.lastKeyPressed = "";
598
- this.keyboardState.lastKeyReleased = "";
599
- this.keyboardState.shift = false;
600
- this.keyboardState.ctrl = false;
601
- this.keyboardState.alt = false;
602
- this.keyboardState.meta = false;
603
- this.touchState.points = [];
604
- this.touchState.count = 0;
605
- this.touchState.started = [];
606
- this.touchState.moved = [];
607
- this.touchState.ended = [];
608
- this.touchState.primary = null;
609
- this.touchState.gestures.isPinching = false;
610
- this.touchState.gestures.isRotating = false;
611
- this.touchState.gestures.isPanning = false;
612
- this.touchState.gestures.isTapping = false;
613
- this.touchState.gestures.pinchScale = 1;
614
- this.touchState.gestures.pinchDelta = 0;
615
- this.touchState.gestures.rotationAngle = 0;
616
- this.touchState.gestures.rotationDelta = 0;
617
- this.touchState.gestures.panDelta = { x: 0, y: 0 };
618
- this.touchState.gestures.tapCount = 0;
619
- this.touchState.gestures.lastTapTime = 0;
620
- this.touchState.gestures.tapPosition = null;
621
- }
622
- }
623
- class VideoSystem {
624
- // ✅ CORRECT: Worker-owned OffscreenCanvas (transferred from host)
625
- offscreenCanvas = null;
626
- ctx = null;
627
- gl = null;
628
- // Debug logging control
629
- debugMode = false;
630
- /**
631
- * Enable or disable debug logging
632
- */
633
- setDebugMode(enabled) {
634
- this.debugMode = enabled;
635
- }
636
- /**
637
- * Debug logging helper
638
- */
639
- debugLog(message, ...args) {
640
- if (this.debugMode) {
641
- console.log(message, ...args);
642
- }
643
- }
644
- // Frame processing configuration
645
- targetFrameRate = 30;
646
- // Default target FPS for video processing
647
- lastFrameTime = 0;
648
- frameInterval = 1e3 / this.targetFrameRate;
649
- // ms between frames
650
- // Processing state
651
- hasLoggedFirstFrame = false;
652
- frameCount = 0;
653
- // Video state for artist API
654
- videoState = {
655
- isConnected: false,
656
- currentFrame: null,
657
- frameWidth: 0,
658
- frameHeight: 0,
659
- frameRate: 0,
660
- frameData: null
661
- };
662
- // Phase 11 preparation - CV processing placeholder
663
- cvFeatures = {
664
- faceDetection: false,
665
- handTracking: false,
666
- bodySegmentation: false
667
- };
668
- cvResults = {
669
- faces: [],
670
- hands: [],
671
- bodySegmentation: null
672
- };
673
- constructor() {
674
- }
675
- /**
676
- * Get the video API for inclusion in the viji object
677
- */
678
- getVideoAPI() {
679
- return {
680
- isConnected: this.videoState.isConnected,
681
- currentFrame: this.videoState.currentFrame,
682
- frameWidth: this.videoState.frameWidth,
683
- frameHeight: this.videoState.frameHeight,
684
- frameRate: this.videoState.frameRate,
685
- getFrameData: () => this.videoState.frameData,
686
- faces: this.cvResults.faces,
687
- hands: this.cvResults.hands
688
- };
689
- }
690
- /**
691
- * ✅ CORRECT: Receive OffscreenCanvas transfer from host
692
- */
693
- handleCanvasSetup(data) {
694
- try {
695
- this.disconnectVideo();
696
- this.offscreenCanvas = data.offscreenCanvas;
697
- this.ctx = this.offscreenCanvas.getContext("2d", {
698
- willReadFrequently: true
699
- // Optimize for frequent getImageData calls
700
- });
701
- if (!this.ctx) {
702
- throw new Error("Failed to get 2D context from transferred OffscreenCanvas");
703
- }
704
- try {
705
- this.gl = this.offscreenCanvas.getContext("webgl2") || this.offscreenCanvas.getContext("webgl");
706
- } catch (e) {
707
- this.debugLog("WebGL not available, using 2D context only");
708
- }
709
- this.videoState.isConnected = true;
710
- this.videoState.currentFrame = this.offscreenCanvas;
711
- this.videoState.frameWidth = data.width;
712
- this.videoState.frameHeight = data.height;
713
- this.frameCount = 0;
714
- this.hasLoggedFirstFrame = false;
715
- this.debugLog("✅ OffscreenCanvas received and setup completed (worker-side)", {
716
- width: data.width,
717
- height: data.height,
718
- hasWebGL: !!this.gl,
719
- targetFrameRate: this.targetFrameRate
720
- });
721
- this.debugLog("🎬 CORRECT OffscreenCanvas approach - Worker has full GPU access!");
722
- } catch (error) {
723
- console.error("Failed to setup OffscreenCanvas in worker:", error);
724
- this.disconnectVideo();
725
- }
726
- }
727
- /**
728
- * ✅ CORRECT: Receive ImageBitmap frame and draw to worker's OffscreenCanvas
729
- */
730
- handleFrameUpdate(data) {
731
- if (!this.offscreenCanvas || !this.ctx) {
732
- console.warn("🔴 Received frame but OffscreenCanvas not setup");
733
- return;
734
- }
735
- try {
736
- if (this.frameCount % 150 === 0) {
737
- this.debugLog("✅ Worker received ImageBitmap frame:", {
738
- bitmapSize: `${data.imageBitmap.width}x${data.imageBitmap.height}`,
739
- canvasSize: `${this.offscreenCanvas.width}x${this.offscreenCanvas.height}`,
740
- frameCount: this.frameCount,
741
- timestamp: data.timestamp
742
- });
743
- }
744
- this.ctx.drawImage(data.imageBitmap, 0, 0, this.offscreenCanvas.width, this.offscreenCanvas.height);
745
- this.processCurrentFrame(data.timestamp);
746
- data.imageBitmap.close();
747
- this.frameCount++;
748
- } catch (error) {
749
- console.error("🔴 Error processing video frame (worker-side):", error);
750
- }
751
- }
752
- /**
753
- * Process current frame (called when new frame is drawn)
754
- */
755
- processCurrentFrame(timestamp) {
756
- if (!this.offscreenCanvas || !this.ctx) {
757
- return;
758
- }
759
- try {
760
- this.videoState.frameData = this.ctx.getImageData(
761
- 0,
762
- 0,
763
- this.offscreenCanvas.width,
764
- this.offscreenCanvas.height
765
- );
766
- const deltaTime = timestamp - this.lastFrameTime;
767
- this.videoState.frameRate = deltaTime > 0 ? 1e3 / deltaTime : 0;
768
- if (!this.hasLoggedFirstFrame) {
769
- this.debugLog(`🎯 Worker-side OffscreenCanvas processing active: ${this.videoState.frameRate.toFixed(1)} FPS (${this.offscreenCanvas.width}x${this.offscreenCanvas.height})`);
770
- this.debugLog("✅ Full GPU access available for custom effects and CV analysis");
771
- this.hasLoggedFirstFrame = true;
772
- }
773
- this.performCVAnalysis();
774
- this.lastFrameTime = timestamp;
775
- } catch (error) {
776
- console.error("Error processing video frame (worker-side):", error);
777
- }
778
- }
779
- /**
780
- * Handle video configuration updates (including disconnection and resize)
781
- */
782
- handleVideoConfigUpdate(data) {
783
- try {
784
- if (data.disconnect) {
785
- this.disconnectVideo();
786
- return;
787
- }
788
- if (data.width && data.height && this.offscreenCanvas) {
789
- this.resizeCanvas(data.width, data.height);
790
- }
791
- if (data.targetFrameRate) {
792
- this.updateProcessingConfig(data.targetFrameRate);
793
- }
794
- if (data.cvConfig) {
795
- this.updateCVConfig(data.cvConfig);
796
- }
797
- } catch (error) {
798
- console.error("Error handling video config update:", error);
799
- }
800
- }
801
- /**
802
- * Resize the OffscreenCanvas (when video dimensions change)
803
- */
804
- resizeCanvas(width, height) {
805
- if (!this.offscreenCanvas) return;
806
- try {
807
- this.offscreenCanvas.width = width;
808
- this.offscreenCanvas.height = height;
809
- this.videoState.frameWidth = width;
810
- this.videoState.frameHeight = height;
811
- if (this.gl) {
812
- this.gl.viewport(0, 0, width, height);
813
- }
814
- this.debugLog(`📐 OffscreenCanvas resized to ${width}x${height} (worker-side)`);
815
- } catch (error) {
816
- console.error("Error resizing OffscreenCanvas:", error);
817
- }
818
- }
819
- /**
820
- * Disconnect video and clean up resources
821
- */
822
- disconnectVideo() {
823
- if (this.offscreenCanvas && this.ctx) {
824
- this.ctx.clearRect(0, 0, this.offscreenCanvas.width, this.offscreenCanvas.height);
825
- this.debugLog("🧹 Cleared OffscreenCanvas on disconnect");
826
- }
827
- this.offscreenCanvas = null;
828
- this.ctx = null;
829
- this.gl = null;
830
- this.videoState.isConnected = false;
831
- this.videoState.currentFrame = null;
832
- this.videoState.frameWidth = 0;
833
- this.videoState.frameHeight = 0;
834
- this.videoState.frameRate = 0;
835
- this.videoState.frameData = null;
836
- this.resetCVResults();
837
- this.hasLoggedFirstFrame = false;
838
- this.frameCount = 0;
839
- this.debugLog("Video disconnected (worker-side)");
840
- }
841
- /**
842
- * Update video processing configuration
843
- */
844
- updateProcessingConfig(targetFrameRate) {
845
- this.targetFrameRate = Math.max(1, Math.min(60, targetFrameRate));
846
- this.frameInterval = 1e3 / this.targetFrameRate;
847
- this.debugLog(`Video processing frame rate updated to ${this.targetFrameRate} FPS (worker-side)`);
848
- }
849
- /**
850
- * Phase 11 preparation - Update CV configuration
851
- */
852
- updateCVConfig(cvConfig) {
853
- this.cvFeatures = {
854
- faceDetection: cvConfig.faceDetection || false,
855
- handTracking: cvConfig.handTracking || false,
856
- bodySegmentation: cvConfig.bodySegmentation || false
857
- };
858
- this.debugLog("CV configuration updated (Phase 11 preparation, worker-side):", this.cvFeatures);
859
- }
860
- /**
861
- * Phase 11 preparation - Perform computer vision analysis
862
- */
863
- performCVAnalysis() {
864
- if (this.cvFeatures.faceDetection) ;
865
- if (this.cvFeatures.handTracking) ;
866
- if (this.cvFeatures.bodySegmentation) ;
867
- }
868
- /**
869
- * Reset CV results
870
- */
871
- resetCVResults() {
872
- this.cvResults = {
873
- faces: [],
874
- hands: [],
875
- bodySegmentation: null
876
- };
877
- }
878
- /**
879
- * Reset all video state (called when loading new scene)
880
- */
881
- resetVideoState() {
882
- this.disconnectVideo();
883
- this.resetCVResults();
884
- }
885
- /**
886
- * Get current processing configuration
887
- */
888
- getProcessingConfig() {
889
- return {
890
- targetFrameRate: this.targetFrameRate,
891
- frameInterval: this.frameInterval,
892
- frameCount: this.frameCount
893
- };
894
- }
895
- /**
896
- * Get WebGL context for advanced effects (if available)
897
- */
898
- getWebGLContext() {
899
- return this.gl;
900
- }
901
- /**
902
- * ✅ WORKER API: Artists can access the OffscreenCanvas directly for custom effects
903
- */
904
- getCanvasForArtistEffects() {
905
- return this.offscreenCanvas;
906
- }
907
- }
908
- class VijiWorkerRuntime {
909
- canvas = null;
910
- ctx = null;
911
- gl = null;
912
- isRunning = false;
913
- frameCount = 0;
914
- lastTime = 0;
915
- startTime = 0;
916
- frameRateMode = "full";
917
- skipNextFrame = false;
918
- screenRefreshRate = 60;
919
- // Will be detected
920
- // Debug logging control
921
- debugMode = false;
922
- /**
923
- * Enable or disable debug logging
924
- */
925
- setDebugMode(enabled) {
926
- this.debugMode = enabled;
927
- if (this.videoSystem) this.videoSystem.setDebugMode(enabled);
928
- if (this.parameterSystem && "setDebugMode" in this.parameterSystem) {
929
- this.parameterSystem.setDebugMode(enabled);
930
- }
931
- if (this.interactionSystem && "setDebugMode" in this.interactionSystem) {
932
- this.interactionSystem.setDebugMode(enabled);
933
- }
934
- }
935
- /**
936
- * Debug logging helper
937
- */
938
- debugLog(message, ...args) {
939
- if (this.debugMode) {
940
- console.log(message, ...args);
941
- }
942
- }
943
- // Effective refresh rate tracking
944
- effectiveFrameTimes = [];
945
- lastEffectiveRateReport = 0;
946
- effectiveRateReportInterval = 1e3;
947
- // Report every 1 second
948
- // Parameter system
949
- parameterSystem;
950
- // Interaction system (Phase 7)
951
- interactionSystem;
952
- // Video system (Phase 10) - worker-side video processing
953
- videoSystem;
954
- // Audio state (Phase 5) - receives analysis results from host
955
- audioState = {
956
- isConnected: false,
957
- volume: { rms: 0, peak: 0 },
958
- bands: {
959
- bass: 0,
960
- mid: 0,
961
- treble: 0,
962
- subBass: 0,
963
- lowMid: 0,
964
- highMid: 0,
965
- presence: 0,
966
- brilliance: 0
967
- },
968
- frequencyData: new Uint8Array(0)
969
- };
970
- // Video state is now managed by the worker-side VideoSystem
971
- // Artist API object
972
- viji = {
973
- // Canvas (will be set during init)
974
- canvas: null,
975
- ctx: null,
976
- gl: null,
977
- width: 0,
978
- height: 0,
979
- pixelRatio: 1,
980
- // Timing
981
- time: 0,
982
- deltaTime: 0,
983
- frameCount: 0,
984
- fps: 60,
985
- // Audio API (Phase 5) - will be set in constructor
986
- audio: {},
987
- video: {
988
- isConnected: false,
989
- currentFrame: null,
990
- frameWidth: 0,
991
- frameHeight: 0,
992
- frameRate: 0,
993
- getFrameData: () => null,
994
- faces: [],
995
- hands: []
996
- },
997
- // Interaction APIs will be added during construction
998
- mouse: {},
999
- keyboard: {},
1000
- touches: {},
1001
- // Parameter helper functions (return parameter objects) - delegate to parameter system
1002
- slider: (defaultValue, config) => {
1003
- return this.parameterSystem.createSliderParameter(defaultValue, config);
1004
- },
1005
- color: (defaultValue, config) => {
1006
- return this.parameterSystem.createColorParameter(defaultValue, config);
1007
- },
1008
- toggle: (defaultValue, config) => {
1009
- return this.parameterSystem.createToggleParameter(defaultValue, config);
1010
- },
1011
- select: (defaultValue, config) => {
1012
- return this.parameterSystem.createSelectParameter(defaultValue, config);
1013
- },
1014
- text: (defaultValue, config) => {
1015
- return this.parameterSystem.createTextParameter(defaultValue, config);
1016
- },
1017
- number: (defaultValue, config) => {
1018
- return this.parameterSystem.createNumberParameter(defaultValue, config);
1019
- },
1020
- // Context selection
1021
- useContext: (type) => {
1022
- if (type === "2d") {
1023
- if (!this.ctx && this.canvas) {
1024
- this.ctx = this.canvas.getContext("2d");
1025
- this.viji.ctx = this.ctx;
1026
- }
1027
- return this.ctx;
1028
- } else if (type === "webgl") {
1029
- if (!this.gl && this.canvas) {
1030
- this.gl = this.canvas.getContext("webgl2") || this.canvas.getContext("webgl");
1031
- this.viji.gl = this.gl;
1032
- if (this.gl) {
1033
- this.gl.viewport(0, 0, this.viji.width, this.viji.height);
1034
- }
1035
- }
1036
- return this.gl;
1037
- }
1038
- return null;
1039
- }
1040
- };
1041
- constructor() {
1042
- this.parameterSystem = new ParameterSystem((type, data) => {
1043
- this.postMessage(type, data);
1044
- });
1045
- this.interactionSystem = new InteractionSystem();
1046
- this.videoSystem = new VideoSystem();
1047
- Object.assign(this.viji, this.interactionSystem.getInteractionAPIs());
1048
- Object.assign(this.viji.video, this.videoSystem.getVideoAPI());
1049
- this.viji.audio = {
1050
- ...this.audioState,
1051
- getFrequencyData: () => this.audioState.frequencyData
1052
- };
1053
- this.setupMessageHandling();
1054
- }
1055
- // Reset parameter state (called when loading new scene)
1056
- resetParameterState() {
1057
- this.parameterSystem.resetParameterState();
1058
- this.interactionSystem.resetInteractionState();
1059
- this.audioState = {
1060
- isConnected: false,
1061
- volume: { rms: 0, peak: 0 },
1062
- bands: {
1063
- bass: 0,
1064
- mid: 0,
1065
- treble: 0,
1066
- subBass: 0,
1067
- lowMid: 0,
1068
- highMid: 0,
1069
- presence: 0,
1070
- brilliance: 0
1071
- },
1072
- frequencyData: new Uint8Array(0)
1073
- };
1074
- this.viji.audio = {
1075
- ...this.audioState,
1076
- getFrequencyData: () => this.audioState.frequencyData
1077
- };
1078
- this.videoSystem.resetVideoState();
1079
- Object.assign(this.viji.video, this.videoSystem.getVideoAPI());
1080
- }
1081
- // Send all parameters (from helper functions) to host
1082
- sendAllParametersToHost() {
1083
- this.parameterSystem.sendAllParametersToHost();
1084
- }
1085
- setupMessageHandling() {
1086
- self.onmessage = (event) => {
1087
- const message = event.data;
1088
- switch (message.type) {
1089
- case "init":
1090
- this.handleInit(message);
1091
- break;
1092
- case "frame-rate-update":
1093
- this.handleFrameRateUpdate(message);
1094
- break;
1095
- case "refresh-rate-update":
1096
- this.handleRefreshRateUpdate(message);
1097
- break;
1098
- case "resolution-update":
1099
- this.handleResolutionUpdate(message);
1100
- break;
1101
- case "set-scene-code":
1102
- this.handleSetSceneCode(message);
1103
- break;
1104
- case "debug-mode":
1105
- this.setDebugMode(message.data.enabled);
1106
- break;
1107
- case "parameter-update":
1108
- this.handleParameterUpdate(message);
1109
- break;
1110
- case "parameter-batch-update":
1111
- this.handleParameterBatchUpdate(message);
1112
- break;
1113
- case "stream-update":
1114
- this.handleStreamUpdate(message);
1115
- break;
1116
- case "audio-analysis-update":
1117
- this.handleAudioAnalysisUpdate(message);
1118
- break;
1119
- case "video-canvas-setup":
1120
- this.handleVideoCanvasSetup(message);
1121
- break;
1122
- case "video-frame-update":
1123
- this.handleVideoFrameUpdate(message);
1124
- break;
1125
- case "video-config-update":
1126
- this.handleVideoConfigUpdate(message);
1127
- break;
1128
- case "mouse-update":
1129
- this.handleMouseUpdate(message);
1130
- break;
1131
- case "keyboard-update":
1132
- this.handleKeyboardUpdate(message);
1133
- break;
1134
- case "touch-update":
1135
- this.handleTouchUpdate(message);
1136
- break;
1137
- case "interaction-enabled":
1138
- this.handleInteractionEnabled(message);
1139
- break;
1140
- case "performance-update":
1141
- this.handlePerformanceUpdate(message);
1142
- break;
1143
- case "capture-frame":
1144
- this.handleCaptureFrame(message);
1145
- break;
1146
- }
1147
- };
1148
- }
1149
- handleInit(message) {
1150
- try {
1151
- this.canvas = message.data.canvas;
1152
- this.viji.canvas = this.canvas;
1153
- this.viji.width = this.canvas.width;
1154
- this.viji.height = this.canvas.height;
1155
- this.startRenderLoop();
1156
- this.postMessage("ready", {
1157
- id: message.id,
1158
- canvasSize: { width: this.canvas.width, height: this.canvas.height }
1159
- });
1160
- } catch (error) {
1161
- this.postMessage("error", {
1162
- id: message.id,
1163
- message: error.message,
1164
- code: "INIT_ERROR"
1165
- });
1166
- }
1167
- }
1168
- handleFrameRateUpdate(message) {
1169
- if (message.data && message.data.mode) {
1170
- this.frameRateMode = message.data.mode;
1171
- this.debugLog("Frame rate mode updated to:", message.data.mode);
1172
- }
1173
- }
1174
- handleRefreshRateUpdate(message) {
1175
- if (message.data && message.data.screenRefreshRate) {
1176
- this.screenRefreshRate = message.data.screenRefreshRate;
1177
- this.debugLog("Screen refresh rate updated to:", message.data.screenRefreshRate + "Hz");
1178
- }
1179
- }
1180
- trackEffectiveFrameTime(currentTime) {
1181
- this.effectiveFrameTimes.push(currentTime);
1182
- if (this.effectiveFrameTimes.length > 60) {
1183
- this.effectiveFrameTimes.shift();
1184
- }
1185
- }
1186
- reportEffectiveRefreshRate(currentTime) {
1187
- if (currentTime - this.lastEffectiveRateReport >= this.effectiveRateReportInterval) {
1188
- if (this.effectiveFrameTimes.length >= 2) {
1189
- const totalTime = this.effectiveFrameTimes[this.effectiveFrameTimes.length - 1] - this.effectiveFrameTimes[0];
1190
- const frameCount = this.effectiveFrameTimes.length - 1;
1191
- const effectiveRefreshRate = Math.round(frameCount / totalTime * 1e3);
1192
- this.postMessage("performance-update", {
1193
- effectiveRefreshRate,
1194
- frameRateMode: this.frameRateMode,
1195
- screenRefreshRate: this.screenRefreshRate,
1196
- parameterCount: this.parameterSystem.getParameterCount()
1197
- });
1198
- }
1199
- this.lastEffectiveRateReport = currentTime;
1200
- }
1201
- }
1202
- handleResolutionUpdate(message) {
1203
- if (message.data) {
1204
- if (this.canvas) {
1205
- this.canvas.width = Math.round(message.data.effectiveWidth);
1206
- this.canvas.height = Math.round(message.data.effectiveHeight);
1207
- }
1208
- this.viji.width = Math.round(message.data.effectiveWidth);
1209
- this.viji.height = Math.round(message.data.effectiveHeight);
1210
- if (this.gl) {
1211
- this.gl.viewport(0, 0, this.viji.width, this.viji.height);
1212
- }
1213
- this.debugLog("Canvas resolution updated to:", this.viji.width + "x" + this.viji.height);
1214
- }
1215
- }
1216
- handleParameterUpdate(message) {
1217
- if (message.data && message.data.name !== void 0 && message.data.value !== void 0) {
1218
- this.parameterSystem.updateParameterValue(message.data.name, message.data.value);
1219
- }
1220
- }
1221
- handleParameterBatchUpdate(message) {
1222
- if (message.data && message.data.updates) {
1223
- for (const update of message.data.updates) {
1224
- this.parameterSystem.updateParameterValue(update.name, update.value);
1225
- }
1226
- this.parameterSystem.markInitialValuesSynced();
1227
- this.debugLog("Parameter system initialized successfully");
1228
- }
1229
- }
1230
- handleStreamUpdate(message) {
1231
- this.debugLog("Stream update:", message.data);
1232
- }
1233
- handleAudioAnalysisUpdate(message) {
1234
- this.audioState = {
1235
- isConnected: message.data.isConnected,
1236
- volume: message.data.volume,
1237
- bands: message.data.bands,
1238
- frequencyData: message.data.frequencyData
1239
- };
1240
- this.viji.audio = {
1241
- ...this.audioState,
1242
- getFrequencyData: () => this.audioState.frequencyData
1243
- };
1244
- }
1245
- handleVideoCanvasSetup(message) {
1246
- this.videoSystem.handleCanvasSetup({
1247
- offscreenCanvas: message.data.offscreenCanvas,
1248
- width: message.data.width,
1249
- height: message.data.height,
1250
- timestamp: message.data.timestamp
1251
- });
1252
- Object.assign(this.viji.video, this.videoSystem.getVideoAPI());
1253
- }
1254
- handleVideoFrameUpdate(message) {
1255
- this.videoSystem.handleFrameUpdate({
1256
- imageBitmap: message.data.imageBitmap,
1257
- timestamp: message.data.timestamp
1258
- });
1259
- Object.assign(this.viji.video, this.videoSystem.getVideoAPI());
1260
- }
1261
- handleVideoConfigUpdate(message) {
1262
- this.videoSystem.handleVideoConfigUpdate({
1263
- ...message.data.targetFrameRate && { targetFrameRate: message.data.targetFrameRate },
1264
- ...message.data.cvConfig && { cvConfig: message.data.cvConfig },
1265
- ...message.data.width && { width: message.data.width },
1266
- ...message.data.height && { height: message.data.height },
1267
- ...message.data.disconnect && { disconnect: message.data.disconnect },
1268
- timestamp: message.data.timestamp
1269
- });
1270
- Object.assign(this.viji.video, this.videoSystem.getVideoAPI());
1271
- }
1272
- handlePerformanceUpdate(message) {
1273
- this.debugLog("Performance update:", message.data);
1274
- }
1275
- /**
1276
- * Handle capture-frame request from host.
1277
- * Produces an ArrayBuffer (image bytes) to send back as transferable.
1278
- */
1279
- async handleCaptureFrame(message) {
1280
- try {
1281
- if (!this.canvas) {
1282
- throw new Error("Canvas not initialized");
1283
- }
1284
- const mimeType = message.data.type || "image/jpeg";
1285
- const srcWidth = this.canvas.width;
1286
- const srcHeight = this.canvas.height;
1287
- let targetWidth = srcWidth;
1288
- let targetHeight = srcHeight;
1289
- if (typeof message.data.resolution === "number") {
1290
- const scale = message.data.resolution > 0 ? message.data.resolution : 1;
1291
- targetWidth = Math.max(1, Math.floor(srcWidth * scale));
1292
- targetHeight = Math.max(1, Math.floor(srcHeight * scale));
1293
- } else if (message.data.resolution && typeof message.data.resolution === "object") {
1294
- targetWidth = Math.max(1, Math.floor(message.data.resolution.width));
1295
- targetHeight = Math.max(1, Math.floor(message.data.resolution.height));
1296
- }
1297
- const srcAspect = srcWidth / srcHeight;
1298
- const dstAspect = targetWidth / targetHeight;
1299
- let sx = 0;
1300
- let sy = 0;
1301
- let sWidth = srcWidth;
1302
- let sHeight = srcHeight;
1303
- if (Math.abs(srcAspect - dstAspect) > 1e-6) {
1304
- if (dstAspect > srcAspect) {
1305
- sHeight = Math.floor(srcWidth / dstAspect);
1306
- sy = Math.floor((srcHeight - sHeight) / 2);
1307
- } else {
1308
- sWidth = Math.floor(srcHeight * dstAspect);
1309
- sx = Math.floor((srcWidth - sWidth) / 2);
1310
- }
1311
- }
1312
- const temp = new OffscreenCanvas(targetWidth, targetHeight);
1313
- const tctx = temp.getContext("2d");
1314
- if (!tctx) throw new Error("Failed to get 2D context");
1315
- tctx.drawImage(this.canvas, sx, sy, sWidth, sHeight, 0, 0, targetWidth, targetHeight);
1316
- const blob = await temp.convertToBlob({ type: mimeType });
1317
- const arrayBuffer = await blob.arrayBuffer();
1318
- self.postMessage({
1319
- type: "capture-frame-result",
1320
- id: message.id,
1321
- timestamp: Date.now(),
1322
- data: {
1323
- mimeType,
1324
- buffer: arrayBuffer,
1325
- width: targetWidth,
1326
- height: targetHeight
1327
- }
1328
- }, [arrayBuffer]);
1329
- } catch (error) {
1330
- this.postMessage("error", {
1331
- id: message.id,
1332
- message: error.message,
1333
- code: "CAPTURE_FRAME_ERROR"
1334
- });
1335
- }
1336
- }
1337
- handleSetSceneCode(message) {
1338
- if (message.data && message.data.sceneCode) {
1339
- self.setSceneCode(message.data.sceneCode);
1340
- }
1341
- }
1342
- startRenderLoop() {
1343
- this.isRunning = true;
1344
- this.startTime = performance.now();
1345
- this.lastTime = this.startTime;
1346
- this.renderFrame();
1347
- }
1348
- renderFrame() {
1349
- if (!this.isRunning) return;
1350
- const currentTime = performance.now();
1351
- this.interactionSystem.frameStart();
1352
- this.viji.fps = this.frameRateMode === "full" ? this.screenRefreshRate : this.screenRefreshRate / 2;
1353
- let shouldRender = true;
1354
- if (this.frameRateMode === "half") {
1355
- shouldRender = !this.skipNextFrame;
1356
- this.skipNextFrame = !this.skipNextFrame;
1357
- }
1358
- if (shouldRender) {
1359
- this.viji.deltaTime = (currentTime - this.lastTime) / 1e3;
1360
- this.viji.time = (currentTime - this.startTime) / 1e3;
1361
- this.viji.frameCount = ++this.frameCount;
1362
- this.trackEffectiveFrameTime(currentTime);
1363
- this.lastTime = currentTime;
1364
- try {
1365
- const renderFunction2 = self.renderFunction;
1366
- if (renderFunction2 && typeof renderFunction2 === "function") {
1367
- renderFunction2(this.viji);
1368
- }
1369
- } catch (error) {
1370
- console.error("Render error:", error);
1371
- this.postMessage("error", {
1372
- message: error.message,
1373
- code: "RENDER_ERROR",
1374
- stack: error.stack
1375
- });
1376
- }
1377
- }
1378
- this.reportEffectiveRefreshRate(currentTime);
1379
- requestAnimationFrame(() => this.renderFrame());
1380
- }
1381
- postMessage(type, data) {
1382
- self.postMessage({
1383
- type,
1384
- id: data?.id || `${type}_${Date.now()}`,
1385
- timestamp: Date.now(),
1386
- data
1387
- });
1388
- }
1389
- // Phase 7: Interaction Message Handlers (delegated to InteractionSystem)
1390
- handleMouseUpdate(message) {
1391
- this.interactionSystem.handleMouseUpdate(message.data);
1392
- }
1393
- handleKeyboardUpdate(message) {
1394
- this.interactionSystem.handleKeyboardUpdate(message.data);
1395
- }
1396
- handleTouchUpdate(message) {
1397
- this.interactionSystem.handleTouchUpdate(message.data);
1398
- }
1399
- handleInteractionEnabled(message) {
1400
- this.interactionSystem.setInteractionEnabled(message.data.enabled);
1401
- }
1402
- }
1403
- const runtime = new VijiWorkerRuntime();
1404
- let renderFunction = null;
1405
- function setSceneCode(sceneCode) {
1406
- try {
1407
- runtime.resetParameterState();
1408
- const functionBody = sceneCode + '\nif (typeof render === "function") {\n return render;\n}\nthrow new Error("Scene code must define a render function");';
1409
- const sceneFunction = new Function("viji", functionBody);
1410
- renderFunction = sceneFunction(runtime.viji);
1411
- self.renderFunction = renderFunction;
1412
- runtime.sendAllParametersToHost();
1413
- } catch (error) {
1414
- console.error("Failed to load scene code:", error);
1415
- self.postMessage({
1416
- type: "error",
1417
- id: `scene_error_${Date.now()}`,
1418
- timestamp: Date.now(),
1419
- data: {
1420
- message: `Scene code error: ${error.message}`,
1421
- code: "SCENE_CODE_ERROR"
1422
- }
1423
- });
1424
- }
1425
- }
1426
- self.setSceneCode = setSceneCode;
1427
- })();
1428
- //# sourceMappingURL=viji.worker-BKsgIT1d.js.map