@viji-dev/core 0.2.7 → 0.2.9
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/README.md +135 -2
- package/dist/artist-dts.js +1 -1
- package/dist/artist-global.d.ts +151 -6
- package/dist/artist-js-ambient.d.ts +66 -0
- package/dist/artist-jsdoc.d.ts +66 -0
- package/dist/assets/P5WorkerAdapter-bO_02bv6.js +345 -0
- package/dist/assets/P5WorkerAdapter-bO_02bv6.js.map +1 -0
- package/dist/assets/ShaderWorkerAdapter-Cn60jh9g.js +939 -0
- package/dist/assets/ShaderWorkerAdapter-Cn60jh9g.js.map +1 -0
- package/dist/assets/cv-tasks.worker.js +623 -0
- package/dist/assets/p5.min-BBA6UiVb.js +16810 -0
- package/dist/assets/p5.min-BBA6UiVb.js.map +1 -0
- package/dist/assets/viji.worker-Cbdf1a6N.js +2235 -0
- package/dist/assets/viji.worker-Cbdf1a6N.js.map +1 -0
- package/dist/assets/vision_bundle.js +2 -0
- package/dist/assets/wasm/vision_wasm_internal.js +20 -0
- package/dist/assets/wasm/vision_wasm_internal.wasm +0 -0
- package/dist/assets/wasm/vision_wasm_nosimd_internal.js +20 -0
- package/dist/assets/wasm/vision_wasm_nosimd_internal.wasm +0 -0
- package/dist/index.d.ts +182 -13
- package/dist/index.js +104 -22
- package/dist/index.js.map +1 -1
- package/package.json +13 -6
- package/dist/assets/viji.worker-BKsgIT1d.js +0 -1428
- package/dist/assets/viji.worker-BKsgIT1d.js.map +0 -1
|
@@ -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
|