@joshtol/emotive-engine 3.2.2 → 3.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/emotive-mascot-3d.js +1 -1
- package/dist/emotive-mascot-3d.js.map +1 -1
- package/dist/emotive-mascot-3d.umd.js +1 -1
- package/dist/emotive-mascot-3d.umd.js.map +1 -1
- package/package.json +1 -1
- package/src/3d/Core3DManager.js +111 -308
- package/src/3d/ThreeRenderer.js +91 -6
- package/src/3d/effects/CrystalSoul.js +10 -16
- package/src/3d/effects/SolarEclipse.js +6 -8
- package/src/3d/geometries/Moon.js +3 -3
- package/src/3d/index.js +24 -8
- package/src/3d/managers/AnimationManager.js +269 -0
- package/src/3d/managers/BehaviorController.js +248 -0
- package/src/3d/managers/BreathingPhaseManager.js +163 -0
- package/src/3d/managers/CameraPresetManager.js +182 -0
- package/src/3d/managers/EffectManager.js +385 -0
- package/src/3d/utils/MaterialFactory.js +6 -5
- package/types/index.d.ts +207 -11
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EffectManager - Manages geometry-specific visual effects
|
|
3
|
+
*
|
|
4
|
+
* Handles lifecycle management for:
|
|
5
|
+
* - SolarEclipse (sun geometry)
|
|
6
|
+
* - LunarEclipse (moon geometry)
|
|
7
|
+
* - CrystalSoul (crystal-type geometries)
|
|
8
|
+
*
|
|
9
|
+
* Extracted from Core3DManager to improve separation of concerns.
|
|
10
|
+
*
|
|
11
|
+
* @module 3d/managers/EffectManager
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { SolarEclipse } from '../effects/SolarEclipse.js';
|
|
15
|
+
import { LunarEclipse } from '../effects/LunarEclipse.js';
|
|
16
|
+
import { CrystalSoul } from '../effects/CrystalSoul.js';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Geometry types that use CrystalSoul effect
|
|
20
|
+
*/
|
|
21
|
+
const CRYSTAL_SOUL_GEOMETRIES = ['crystal', 'rough', 'heart', 'star'];
|
|
22
|
+
|
|
23
|
+
export class EffectManager {
|
|
24
|
+
/**
|
|
25
|
+
* Create effect manager
|
|
26
|
+
* @param {ThreeRenderer} renderer - The Three.js renderer
|
|
27
|
+
* @param {string} assetBasePath - Base path for loading assets
|
|
28
|
+
*/
|
|
29
|
+
constructor(renderer, assetBasePath = '/assets') {
|
|
30
|
+
this.renderer = renderer;
|
|
31
|
+
this.assetBasePath = assetBasePath;
|
|
32
|
+
|
|
33
|
+
// Effect instances
|
|
34
|
+
this.solarEclipse = null;
|
|
35
|
+
this.lunarEclipse = null;
|
|
36
|
+
this.crystalSoul = null;
|
|
37
|
+
|
|
38
|
+
// State tracking
|
|
39
|
+
this.currentGeometryType = null;
|
|
40
|
+
this.coreGlowEnabled = true;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Initialize effects for a specific geometry type
|
|
45
|
+
* @param {string} geometryType - The geometry type (sun, moon, crystal, etc.)
|
|
46
|
+
* @param {Object} options - Options for initialization
|
|
47
|
+
* @param {THREE.Mesh} options.coreMesh - The core mesh to attach effects to
|
|
48
|
+
* @param {Object} options.customMaterial - Custom material (for lunar eclipse)
|
|
49
|
+
* @param {number} options.sunRadius - Sun radius (for solar eclipse)
|
|
50
|
+
*/
|
|
51
|
+
initializeForGeometry(geometryType, options = {}) {
|
|
52
|
+
const { coreMesh, customMaterial, sunRadius = 1.0 } = options;
|
|
53
|
+
this.currentGeometryType = geometryType;
|
|
54
|
+
|
|
55
|
+
// Clean up effects not needed for this geometry
|
|
56
|
+
this._cleanupUnneededEffects(geometryType);
|
|
57
|
+
|
|
58
|
+
// Initialize geometry-specific effects
|
|
59
|
+
if (geometryType === 'sun') {
|
|
60
|
+
this._initSolarEclipse(sunRadius, coreMesh);
|
|
61
|
+
} else if (geometryType === 'moon') {
|
|
62
|
+
this._initLunarEclipse(customMaterial);
|
|
63
|
+
} else if (CRYSTAL_SOUL_GEOMETRIES.includes(geometryType)) {
|
|
64
|
+
// CrystalSoul is initialized separately via createCrystalSoul()
|
|
65
|
+
// because it has complex async loading requirements
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Initialize solar eclipse effect
|
|
71
|
+
* @private
|
|
72
|
+
*/
|
|
73
|
+
_initSolarEclipse(sunRadius, coreMesh) {
|
|
74
|
+
if (!this.solarEclipse && this.renderer?.scene) {
|
|
75
|
+
this.solarEclipse = new SolarEclipse(this.renderer.scene, sunRadius, coreMesh);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Initialize lunar eclipse effect
|
|
81
|
+
* @private
|
|
82
|
+
*/
|
|
83
|
+
_initLunarEclipse(customMaterial) {
|
|
84
|
+
if (!this.lunarEclipse && customMaterial) {
|
|
85
|
+
this.lunarEclipse = new LunarEclipse(customMaterial);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Clean up effects that are not needed for the current geometry
|
|
91
|
+
* @private
|
|
92
|
+
*/
|
|
93
|
+
_cleanupUnneededEffects(geometryType) {
|
|
94
|
+
// Dispose solar eclipse if not sun
|
|
95
|
+
if (geometryType !== 'sun' && this.solarEclipse) {
|
|
96
|
+
this.solarEclipse.dispose();
|
|
97
|
+
this.solarEclipse = null;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Dispose lunar eclipse if not moon
|
|
101
|
+
if (geometryType !== 'moon' && this.lunarEclipse) {
|
|
102
|
+
this.lunarEclipse.dispose();
|
|
103
|
+
this.lunarEclipse = null;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Dispose crystal soul if not a crystal-type geometry
|
|
107
|
+
if (!CRYSTAL_SOUL_GEOMETRIES.includes(geometryType) && this.crystalSoul) {
|
|
108
|
+
this.crystalSoul.dispose();
|
|
109
|
+
this.crystalSoul = null;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Create crystal soul effect (async due to inclusion geometry loading)
|
|
115
|
+
* @param {Object} options - Crystal soul options
|
|
116
|
+
* @param {THREE.Mesh} options.coreMesh - Core mesh to attach to
|
|
117
|
+
* @param {string} options.geometryType - Geometry type for configuration
|
|
118
|
+
* @returns {Object} Soul configuration { mesh, material, baseScale, shellBaseScale }
|
|
119
|
+
*/
|
|
120
|
+
async createCrystalSoul(options = {}) {
|
|
121
|
+
const { coreMesh, geometryType } = options;
|
|
122
|
+
|
|
123
|
+
// Dispose existing soul
|
|
124
|
+
if (this.crystalSoul) {
|
|
125
|
+
this.crystalSoul.dispose();
|
|
126
|
+
this.crystalSoul = null;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (!coreMesh) {
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Preload inclusion geometry
|
|
134
|
+
await CrystalSoul._loadInclusionGeometry(this.assetBasePath);
|
|
135
|
+
|
|
136
|
+
// Create new soul
|
|
137
|
+
this.crystalSoul = new CrystalSoul({
|
|
138
|
+
radius: 0.35,
|
|
139
|
+
detail: 1,
|
|
140
|
+
geometryType,
|
|
141
|
+
renderer: this.renderer,
|
|
142
|
+
assetBasePath: this.assetBasePath
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
this.crystalSoul.attachTo(coreMesh, this.renderer?.scene);
|
|
146
|
+
|
|
147
|
+
// Get geometry-specific scale configuration
|
|
148
|
+
const { shellBaseScale, soulScale } = this._getCrystalScaleConfig(geometryType);
|
|
149
|
+
|
|
150
|
+
this.crystalSoul.baseScale = soulScale;
|
|
151
|
+
this.crystalSoul.mesh.scale.setScalar(soulScale);
|
|
152
|
+
this.crystalSoul.setVisible(this.coreGlowEnabled);
|
|
153
|
+
|
|
154
|
+
return {
|
|
155
|
+
mesh: this.crystalSoul.mesh,
|
|
156
|
+
material: this.crystalSoul.material,
|
|
157
|
+
baseScale: this.crystalSoul.baseScale,
|
|
158
|
+
shellBaseScale
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Create crystal soul synchronously (non-async version)
|
|
164
|
+
* @param {Object} options - Crystal soul options
|
|
165
|
+
* @returns {Object} Soul configuration
|
|
166
|
+
*/
|
|
167
|
+
createCrystalSoulSync(options = {}) {
|
|
168
|
+
const { coreMesh, geometryType } = options;
|
|
169
|
+
|
|
170
|
+
if (this.crystalSoul) {
|
|
171
|
+
this.crystalSoul.dispose();
|
|
172
|
+
this.crystalSoul = null;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (!coreMesh) {
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
this.crystalSoul = new CrystalSoul({
|
|
180
|
+
radius: 0.35,
|
|
181
|
+
detail: 1,
|
|
182
|
+
geometryType,
|
|
183
|
+
renderer: this.renderer,
|
|
184
|
+
assetBasePath: this.assetBasePath
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
this.crystalSoul.attachTo(coreMesh, this.renderer?.scene);
|
|
188
|
+
|
|
189
|
+
const { shellBaseScale, soulScale } = this._getCrystalScaleConfig(geometryType);
|
|
190
|
+
|
|
191
|
+
this.crystalSoul.baseScale = soulScale;
|
|
192
|
+
this.crystalSoul.mesh.scale.setScalar(soulScale);
|
|
193
|
+
this.crystalSoul.setVisible(this.coreGlowEnabled);
|
|
194
|
+
|
|
195
|
+
return {
|
|
196
|
+
mesh: this.crystalSoul.mesh,
|
|
197
|
+
material: this.crystalSoul.material,
|
|
198
|
+
baseScale: this.crystalSoul.baseScale,
|
|
199
|
+
shellBaseScale
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Get scale configuration for crystal-type geometries
|
|
205
|
+
* @private
|
|
206
|
+
*/
|
|
207
|
+
_getCrystalScaleConfig(geometryType) {
|
|
208
|
+
let shellBaseScale = 2.0; // Default crystal shell size
|
|
209
|
+
let soulScale = 1.0; // Default: full size
|
|
210
|
+
|
|
211
|
+
if (geometryType === 'heart') {
|
|
212
|
+
shellBaseScale = 2.4;
|
|
213
|
+
soulScale = 1.0;
|
|
214
|
+
} else if (geometryType === 'rough') {
|
|
215
|
+
shellBaseScale = 1.6;
|
|
216
|
+
soulScale = 1.0;
|
|
217
|
+
} else if (geometryType === 'star') {
|
|
218
|
+
shellBaseScale = 2.0;
|
|
219
|
+
soulScale = 1.4; // Larger soul for star to fill the shape
|
|
220
|
+
} else if (geometryType === 'crystal') {
|
|
221
|
+
shellBaseScale = 2.0;
|
|
222
|
+
soulScale = 1.0;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return { shellBaseScale, soulScale };
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Set solar eclipse type
|
|
230
|
+
* @param {string} eclipseType - 'off', 'annular', or 'total'
|
|
231
|
+
* @returns {boolean} True if eclipse was set
|
|
232
|
+
*/
|
|
233
|
+
setSolarEclipse(eclipseType) {
|
|
234
|
+
if (!this.solarEclipse) {
|
|
235
|
+
return false;
|
|
236
|
+
}
|
|
237
|
+
this.solarEclipse.setEclipseType(eclipseType);
|
|
238
|
+
return true;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Set lunar eclipse type
|
|
243
|
+
* @param {string} eclipseType - 'off', 'penumbral', 'partial', 'total'
|
|
244
|
+
* @returns {boolean} True if eclipse was set
|
|
245
|
+
*/
|
|
246
|
+
setLunarEclipse(eclipseType) {
|
|
247
|
+
if (!this.lunarEclipse) {
|
|
248
|
+
return false;
|
|
249
|
+
}
|
|
250
|
+
this.lunarEclipse.setEclipseType(eclipseType);
|
|
251
|
+
return true;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Stop all eclipse effects
|
|
256
|
+
*/
|
|
257
|
+
stopAllEclipses() {
|
|
258
|
+
if (this.solarEclipse) {
|
|
259
|
+
this.solarEclipse.setEclipseType('off');
|
|
260
|
+
}
|
|
261
|
+
if (this.lunarEclipse) {
|
|
262
|
+
this.lunarEclipse.setEclipseType('off');
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Update crystal soul
|
|
268
|
+
* @param {number} deltaTime - Time since last frame
|
|
269
|
+
* @param {Array} glowColor - RGB color [r, g, b]
|
|
270
|
+
* @param {number} breathScale - Breathing animation scale
|
|
271
|
+
*/
|
|
272
|
+
updateCrystalSoul(deltaTime, glowColor, breathScale = 1.0) {
|
|
273
|
+
if (this.crystalSoul) {
|
|
274
|
+
this.crystalSoul.update(deltaTime, glowColor, breathScale);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Update lunar eclipse
|
|
280
|
+
* @param {number} deltaTime - Time since last frame
|
|
281
|
+
*/
|
|
282
|
+
updateLunarEclipse(deltaTime) {
|
|
283
|
+
if (this.lunarEclipse) {
|
|
284
|
+
this.lunarEclipse.update(deltaTime);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Set crystal soul effect parameters
|
|
290
|
+
* @param {Object} params - Effect parameters
|
|
291
|
+
*/
|
|
292
|
+
setCrystalSoulEffects(params) {
|
|
293
|
+
if (this.crystalSoul) {
|
|
294
|
+
this.crystalSoul.setEffects(params);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Set crystal soul size
|
|
300
|
+
* @param {number} size - Size value
|
|
301
|
+
* @returns {number} The new base scale
|
|
302
|
+
*/
|
|
303
|
+
setCrystalSoulSize(size) {
|
|
304
|
+
if (this.crystalSoul) {
|
|
305
|
+
this.crystalSoul.setSize(size);
|
|
306
|
+
return this.crystalSoul.baseScale;
|
|
307
|
+
}
|
|
308
|
+
return 1.0;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Set crystal soul visibility
|
|
313
|
+
* @param {boolean} visible - Whether soul should be visible
|
|
314
|
+
*/
|
|
315
|
+
setCrystalSoulVisible(visible) {
|
|
316
|
+
this.coreGlowEnabled = visible;
|
|
317
|
+
if (this.crystalSoul) {
|
|
318
|
+
this.crystalSoul.setVisible(visible);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Check if crystal soul exists
|
|
324
|
+
* @returns {boolean}
|
|
325
|
+
*/
|
|
326
|
+
hasCrystalSoul() {
|
|
327
|
+
return !!this.crystalSoul;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Get crystal soul base scale
|
|
332
|
+
* @returns {number}
|
|
333
|
+
*/
|
|
334
|
+
getCrystalSoulBaseScale() {
|
|
335
|
+
return this.crystalSoul?.baseScale ?? 1.0;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Check if solar eclipse is active
|
|
340
|
+
* @returns {boolean}
|
|
341
|
+
*/
|
|
342
|
+
hasSolarEclipse() {
|
|
343
|
+
return !!this.solarEclipse;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Check if lunar eclipse is active
|
|
348
|
+
* @returns {boolean}
|
|
349
|
+
*/
|
|
350
|
+
hasLunarEclipse() {
|
|
351
|
+
return !!this.lunarEclipse;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Get solar eclipse instance (for render pass)
|
|
356
|
+
* @returns {SolarEclipse|null}
|
|
357
|
+
*/
|
|
358
|
+
getSolarEclipse() {
|
|
359
|
+
return this.solarEclipse;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* Dispose all effects
|
|
364
|
+
*/
|
|
365
|
+
dispose() {
|
|
366
|
+
if (this.solarEclipse) {
|
|
367
|
+
this.solarEclipse.dispose();
|
|
368
|
+
this.solarEclipse = null;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
if (this.lunarEclipse) {
|
|
372
|
+
this.lunarEclipse.dispose();
|
|
373
|
+
this.lunarEclipse = null;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
if (this.crystalSoul) {
|
|
377
|
+
this.crystalSoul.dispose();
|
|
378
|
+
this.crystalSoul = null;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
this.renderer = null;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
export default EffectManager;
|
|
@@ -75,10 +75,10 @@ function createCrystalMaterial(glowColor, glowIntensity, textureType = 'crystal'
|
|
|
75
75
|
if (textureType) {
|
|
76
76
|
const textureLoader = new THREE.TextureLoader();
|
|
77
77
|
const texturePaths = {
|
|
78
|
-
crystal: `${assetBasePath}/textures/Crystal/crystal.
|
|
79
|
-
rough: `${assetBasePath}/textures/Crystal/rough.
|
|
80
|
-
heart: `${assetBasePath}/textures/Crystal/heart.
|
|
81
|
-
star: `${assetBasePath}/textures/Crystal/star.
|
|
78
|
+
crystal: `${assetBasePath}/textures/Crystal/crystal.png`,
|
|
79
|
+
rough: `${assetBasePath}/textures/Crystal/rough.png`,
|
|
80
|
+
heart: `${assetBasePath}/textures/Crystal/heart.png`,
|
|
81
|
+
star: `${assetBasePath}/textures/Crystal/star.png`
|
|
82
82
|
};
|
|
83
83
|
const texturePath = texturePaths[textureType] || texturePaths.crystal;
|
|
84
84
|
crystalTexture = textureLoader.load(texturePath,
|
|
@@ -191,7 +191,8 @@ function createCrystalMaterial(glowColor, glowIntensity, textureType = 'crystal'
|
|
|
191
191
|
}
|
|
192
192
|
|
|
193
193
|
function createMoonMaterial(textureLoader, glowColor, glowIntensity, materialVariant = null, assetBasePath = '/assets') {
|
|
194
|
-
const
|
|
194
|
+
const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
|
|
195
|
+
const resolution = isMobile ? '2k' : '4k';
|
|
195
196
|
|
|
196
197
|
if (materialVariant === 'multiplexer') {
|
|
197
198
|
const material = createMoonMultiplexerMaterial(textureLoader, {
|
package/types/index.d.ts
CHANGED
|
@@ -207,22 +207,28 @@ declare namespace EmotiveEngine {
|
|
|
207
207
|
// Main Emotive Mascot
|
|
208
208
|
export interface EmotiveMascotConfig {
|
|
209
209
|
canvas?: HTMLCanvasElement;
|
|
210
|
+
canvasId?: string | HTMLCanvasElement;
|
|
210
211
|
width?: number;
|
|
211
212
|
height?: number;
|
|
212
213
|
autoStart?: boolean;
|
|
213
214
|
fps?: number;
|
|
215
|
+
targetFPS?: number;
|
|
214
216
|
adaptive?: boolean;
|
|
215
217
|
theme?: Theme;
|
|
216
218
|
enableAudio?: boolean;
|
|
217
219
|
enableParticles?: boolean;
|
|
220
|
+
enableGazeTracking?: boolean;
|
|
221
|
+
defaultEmotion?: string;
|
|
218
222
|
}
|
|
219
223
|
|
|
220
224
|
export interface Theme {
|
|
221
|
-
primary
|
|
222
|
-
secondary
|
|
223
|
-
accent
|
|
225
|
+
primary?: string;
|
|
226
|
+
secondary?: string;
|
|
227
|
+
accent?: string;
|
|
224
228
|
background?: string;
|
|
225
229
|
particles?: string[];
|
|
230
|
+
core?: string;
|
|
231
|
+
glow?: string;
|
|
226
232
|
}
|
|
227
233
|
|
|
228
234
|
export interface EmotionalState {
|
|
@@ -231,22 +237,212 @@ declare namespace EmotiveEngine {
|
|
|
231
237
|
dominance?: number;
|
|
232
238
|
}
|
|
233
239
|
|
|
240
|
+
export interface FeelResult {
|
|
241
|
+
success: boolean;
|
|
242
|
+
error: string | null;
|
|
243
|
+
parsed: ParsedIntent | null;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
export interface ParsedIntent {
|
|
247
|
+
emotion: string | null;
|
|
248
|
+
gestures: string[];
|
|
249
|
+
shape: string | null;
|
|
250
|
+
undertone: string | null;
|
|
251
|
+
intensity: number;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
export interface FeelVocabulary {
|
|
255
|
+
emotions: string[];
|
|
256
|
+
undertones: string[];
|
|
257
|
+
gestures: string[];
|
|
258
|
+
shapes: string[];
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
export interface BackdropOptions {
|
|
262
|
+
enabled?: boolean;
|
|
263
|
+
radius?: number;
|
|
264
|
+
shape?: 'circle' | 'ellipse' | 'fullscreen';
|
|
265
|
+
color?: string;
|
|
266
|
+
intensity?: number;
|
|
267
|
+
blendMode?: 'normal' | 'multiply' | 'overlay' | 'screen';
|
|
268
|
+
falloff?: 'linear' | 'smooth' | 'exponential' | 'custom';
|
|
269
|
+
falloffCurve?: Array<{ stop: number; alpha: number }>;
|
|
270
|
+
edgeSoftness?: number;
|
|
271
|
+
coreTransparency?: number;
|
|
272
|
+
blur?: number;
|
|
273
|
+
responsive?: boolean;
|
|
274
|
+
pulse?: boolean;
|
|
275
|
+
offset?: { x: number; y: number };
|
|
276
|
+
type?: 'radial-gradient' | 'vignette' | 'glow';
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
export interface AttachOptions {
|
|
280
|
+
offsetX?: number;
|
|
281
|
+
offsetY?: number;
|
|
282
|
+
animate?: boolean;
|
|
283
|
+
duration?: number;
|
|
284
|
+
scale?: number;
|
|
285
|
+
containParticles?: boolean;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
export interface PerformanceMetrics {
|
|
289
|
+
fps: number;
|
|
290
|
+
frameTime: number;
|
|
291
|
+
particleCount?: number;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
export interface AudioAnalysis {
|
|
295
|
+
beats?: any;
|
|
296
|
+
tempo?: number;
|
|
297
|
+
energy?: number;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
export interface GazeState {
|
|
301
|
+
x: number;
|
|
302
|
+
y: number;
|
|
303
|
+
enabled: boolean;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
export interface Capabilities {
|
|
307
|
+
audio: boolean;
|
|
308
|
+
recording: boolean;
|
|
309
|
+
timeline: boolean;
|
|
310
|
+
export: boolean;
|
|
311
|
+
shapes: boolean;
|
|
312
|
+
gestures: boolean;
|
|
313
|
+
emotions: boolean;
|
|
314
|
+
particles: boolean;
|
|
315
|
+
gazeTracking: boolean;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
export interface TimelineEvent {
|
|
319
|
+
type: 'emotion' | 'gesture' | 'shape';
|
|
320
|
+
name: string;
|
|
321
|
+
time: number;
|
|
322
|
+
undertone?: string;
|
|
323
|
+
config?: any;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
export interface ScaleOptions {
|
|
327
|
+
global?: number;
|
|
328
|
+
core?: number;
|
|
329
|
+
particles?: number;
|
|
330
|
+
}
|
|
331
|
+
|
|
234
332
|
export class EmotiveMascot {
|
|
235
333
|
constructor(config?: EmotiveMascotConfig);
|
|
236
|
-
|
|
334
|
+
|
|
335
|
+
// Lifecycle
|
|
336
|
+
init(canvas: HTMLCanvasElement | string): Promise<void>;
|
|
237
337
|
start(): void;
|
|
238
338
|
stop(): void;
|
|
239
339
|
pause(): void;
|
|
240
340
|
resume(): void;
|
|
241
341
|
destroy(): void;
|
|
242
|
-
|
|
243
|
-
|
|
342
|
+
|
|
343
|
+
// LLM Integration - Natural Language API
|
|
344
|
+
feel(intent: string): FeelResult;
|
|
345
|
+
static getFeelVocabulary(): FeelVocabulary;
|
|
346
|
+
parseIntent(intent: string): ParsedIntent;
|
|
347
|
+
|
|
348
|
+
// Emotion & Expression
|
|
349
|
+
setEmotion(emotion: string, undertoneOrDurationOrOptions?: string | number | { undertone?: string; duration?: number }, timestamp?: number): void;
|
|
350
|
+
express(gestureName: string, timestamp?: number): void;
|
|
351
|
+
triggerGesture(gestureName: string, timestamp?: number): void;
|
|
352
|
+
chain(chainName: string): void;
|
|
353
|
+
updateUndertone(undertone: string | null): void;
|
|
354
|
+
|
|
355
|
+
// Shape Morphing
|
|
356
|
+
morphTo(shape: string, config?: any): void;
|
|
357
|
+
setShape(shape: string, configOrTimestamp?: any | number): void;
|
|
358
|
+
|
|
359
|
+
// Audio
|
|
360
|
+
loadAudio(source: string | Blob): Promise<void>;
|
|
361
|
+
connectAudio(audioElement: HTMLAudioElement): void;
|
|
362
|
+
disconnectAudio(audioElement?: HTMLAudioElement): void;
|
|
363
|
+
getAudioAnalysis(): AudioAnalysis;
|
|
364
|
+
getSpectrumData(): number[];
|
|
365
|
+
startRhythmSync(bpm?: number): void;
|
|
366
|
+
stopRhythmSync(): void;
|
|
367
|
+
setSoundEnabled(enabled: boolean): void;
|
|
244
368
|
setBPM(bpm: number): void;
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
369
|
+
|
|
370
|
+
// Gaze Tracking
|
|
371
|
+
enableGazeTracking(): void;
|
|
372
|
+
disableGazeTracking(): void;
|
|
373
|
+
setGazeTarget(x: number, y: number): void;
|
|
374
|
+
getGazeState(): GazeState | null;
|
|
375
|
+
|
|
376
|
+
// Positioning
|
|
377
|
+
setPosition(x: number, y: number, z?: number): void;
|
|
378
|
+
animateToPosition(x: number, y: number, z?: number, duration?: number, easing?: string): void;
|
|
379
|
+
attachToElement(elementOrSelector: string | HTMLElement, options?: AttachOptions): EmotiveMascot;
|
|
380
|
+
detachFromElement(): void;
|
|
381
|
+
isAttachedToElement(): boolean;
|
|
382
|
+
setContainment(bounds: { width: number; height: number } | null, scale?: number): void;
|
|
383
|
+
|
|
384
|
+
// Visual Customization
|
|
385
|
+
setScale(scaleOrOptions: number | ScaleOptions): EmotiveMascot;
|
|
386
|
+
getScale(): number;
|
|
387
|
+
setOpacity(opacity: number): EmotiveMascot;
|
|
388
|
+
getOpacity(): number;
|
|
389
|
+
fadeIn(duration?: number): EmotiveMascot;
|
|
390
|
+
fadeOut(duration?: number): EmotiveMascot;
|
|
391
|
+
setColor(color: string): EmotiveMascot;
|
|
392
|
+
setGlowColor(color: string): EmotiveMascot;
|
|
393
|
+
setTheme(theme: Theme): EmotiveMascot;
|
|
394
|
+
setBackdrop(options: BackdropOptions): EmotiveMascot;
|
|
395
|
+
getBackdrop(): BackdropOptions | null;
|
|
396
|
+
|
|
397
|
+
// Particles
|
|
398
|
+
clearParticles(): void;
|
|
399
|
+
setMaxParticles(maxParticles: number): EmotiveMascot;
|
|
400
|
+
getParticleCount(): number;
|
|
401
|
+
setParticleSystemCanvasDimensions(width: number, height: number): EmotiveMascot;
|
|
402
|
+
|
|
403
|
+
// Performance
|
|
404
|
+
setQuality(level: 'low' | 'medium' | 'high'): void;
|
|
405
|
+
setSpeed(speed: number): EmotiveMascot;
|
|
406
|
+
getSpeed(): number;
|
|
407
|
+
setFPS(fps: number): EmotiveMascot;
|
|
408
|
+
getFPS(): number;
|
|
409
|
+
isPaused(): boolean;
|
|
410
|
+
getPerformanceMetrics(): PerformanceMetrics;
|
|
411
|
+
batch(callback: (mascot: EmotiveMascot) => void): EmotiveMascot;
|
|
412
|
+
|
|
413
|
+
// Timeline Recording
|
|
414
|
+
startRecording(): void;
|
|
415
|
+
stopRecording(): TimelineEvent[];
|
|
416
|
+
playTimeline(timeline: TimelineEvent[]): void;
|
|
417
|
+
stopPlayback(): void;
|
|
418
|
+
getTimeline(): TimelineEvent[];
|
|
419
|
+
loadTimeline(timeline: TimelineEvent[]): void;
|
|
420
|
+
exportTimeline(): string;
|
|
421
|
+
importTimeline(json: string): void;
|
|
422
|
+
getCurrentTime(): number;
|
|
423
|
+
seek(time: number): void;
|
|
424
|
+
|
|
425
|
+
// Export
|
|
426
|
+
getFrameData(format?: string): string;
|
|
427
|
+
getFrameBlob(format?: string): Promise<Blob>;
|
|
428
|
+
getAnimationData(): any;
|
|
429
|
+
|
|
430
|
+
// Query
|
|
431
|
+
getAvailableGestures(): string[];
|
|
432
|
+
getAvailableEmotions(): string[];
|
|
433
|
+
getAvailableShapes(): string[];
|
|
434
|
+
getVersion(): string;
|
|
435
|
+
getCapabilities(): Capabilities;
|
|
436
|
+
|
|
437
|
+
// Events
|
|
438
|
+
on(event: string, handler: Function): EmotiveMascot;
|
|
439
|
+
off(event: string, handler?: Function): EmotiveMascot;
|
|
440
|
+
|
|
441
|
+
// Component access (safe proxies)
|
|
442
|
+
readonly renderer: any;
|
|
443
|
+
readonly shapeMorpher: any;
|
|
444
|
+
readonly gazeTracker: any;
|
|
445
|
+
readonly canvas: HTMLCanvasElement;
|
|
250
446
|
}
|
|
251
447
|
|
|
252
448
|
// Particle System
|