@joshtol/emotive-engine 3.3.4 → 3.3.6
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 +9 -8
- package/src/3d/ThreeRenderer.js +19 -11
- package/src/3d/animation/Rhythm3DAdapter.js +44 -3
- package/src/3d/effects/CrystalSoul.js +7 -9
- package/src/3d/index.js +37 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@joshtol/emotive-engine",
|
|
4
|
-
"version": "3.3.
|
|
4
|
+
"version": "3.3.6",
|
|
5
5
|
"description": "Open-source animation engine for AI-controlled emotional visualizations with musical time synchronization",
|
|
6
6
|
"main": "dist/emotive-mascot.umd.js",
|
|
7
7
|
"module": "dist/mascot.js",
|
package/src/3d/Core3DManager.js
CHANGED
|
@@ -291,6 +291,8 @@ export class Core3DManager {
|
|
|
291
291
|
this.glowColor = [1.0, 1.0, 1.0]; // RGB
|
|
292
292
|
this.glowColorHex = '#FFFFFF'; // Hex color for luminance normalization
|
|
293
293
|
this.glowIntensity = 1.0;
|
|
294
|
+
// OPTIMIZATION: Cache normalized color to avoid recalculating every frame
|
|
295
|
+
this._normalizedGlowColor = null;
|
|
294
296
|
this.coreGlowEnabled = true; // Toggle to enable/disable core glow
|
|
295
297
|
this.glowIntensityOverride = null; // Manual override for testing
|
|
296
298
|
this.intensityCalibrationOffset = 0; // Universal filter calibration offset
|
|
@@ -419,6 +421,9 @@ export class Core3DManager {
|
|
|
419
421
|
this.glowColor = rgb;
|
|
420
422
|
// Store hex color for bloom luminance normalization
|
|
421
423
|
this.glowColorHex = emotionData.visual.glowColor;
|
|
424
|
+
// OPTIMIZATION: Pre-compute normalized color (avoids recalculating every frame)
|
|
425
|
+
const normalized = normalizeRGBLuminance(rgb, 0.30);
|
|
426
|
+
this._normalizedGlowColor = [normalized.r, normalized.g, normalized.b];
|
|
422
427
|
|
|
423
428
|
// Calculate intensity using universal filter based on color luminance
|
|
424
429
|
// This ensures consistent visibility across all emotions regardless of color brightness
|
|
@@ -1832,11 +1837,9 @@ export class Core3DManager {
|
|
|
1832
1837
|
this.customMaterial.uniforms.glowIntensity.value = effectiveGlowIntensity;
|
|
1833
1838
|
}
|
|
1834
1839
|
|
|
1835
|
-
//
|
|
1840
|
+
// OPTIMIZATION: Use pre-computed normalized color (calculated when emotion changes)
|
|
1836
1841
|
// This ensures yellow (joy) doesn't wash out the soul while blue (sadness) stays visible
|
|
1837
|
-
|
|
1838
|
-
const normalized = normalizeRGBLuminance(this.glowColor, 0.30);
|
|
1839
|
-
const normalizedColor = [normalized.r, normalized.g, normalized.b];
|
|
1842
|
+
const normalizedColor = this._normalizedGlowColor || [1, 1, 1];
|
|
1840
1843
|
|
|
1841
1844
|
// Update emotion color on outer shell (luminance-normalized)
|
|
1842
1845
|
this.customMaterial.uniforms.emotionColor.value.setRGB(
|
|
@@ -1848,12 +1851,10 @@ export class Core3DManager {
|
|
|
1848
1851
|
this.customMaterial.uniforms.blinkIntensity.value = blinkPulse;
|
|
1849
1852
|
}
|
|
1850
1853
|
}
|
|
1851
|
-
// Update inner core color and animation (also use normalized color)
|
|
1854
|
+
// Update inner core color and animation (also use cached normalized color)
|
|
1852
1855
|
// Only update if core glow is enabled
|
|
1853
|
-
// IMPORTANT: Use glowColor (RGB), not glowColorHex - glowColor has undertone saturation applied
|
|
1854
1856
|
if (this.coreGlowEnabled) {
|
|
1855
|
-
const
|
|
1856
|
-
const normalizedCoreColor = [normalizedCore.r, normalizedCore.g, normalizedCore.b];
|
|
1857
|
+
const normalizedCoreColor = this._normalizedGlowColor || [1, 1, 1];
|
|
1857
1858
|
this.updateCrystalInnerCore(normalizedCoreColor, deltaTime);
|
|
1858
1859
|
}
|
|
1859
1860
|
}
|
package/src/3d/ThreeRenderer.js
CHANGED
|
@@ -143,6 +143,13 @@ export class ThreeRenderer {
|
|
|
143
143
|
this._zAxis = new THREE.Vector3(0, 0, 1);
|
|
144
144
|
this._cameraToMesh = new THREE.Vector3();
|
|
145
145
|
this._cameraDir = new THREE.Vector3();
|
|
146
|
+
|
|
147
|
+
// OPTIMIZATION: Reusable temp vector for soul position projection (avoids allocation per frame)
|
|
148
|
+
this._soulPosTemp = new THREE.Vector3();
|
|
149
|
+
// OPTIMIZATION: Cached soul mesh reference (avoids scene.traverse every frame)
|
|
150
|
+
this._cachedSoulMesh = null;
|
|
151
|
+
// OPTIMIZATION: Reusable Vector2 for drawing buffer size queries
|
|
152
|
+
this._drawingBufferSize = new THREE.Vector2();
|
|
146
153
|
}
|
|
147
154
|
|
|
148
155
|
/**
|
|
@@ -1494,14 +1501,15 @@ export class ThreeRenderer {
|
|
|
1494
1501
|
// === STEP 0: Render soul (layer 2) to texture for refraction sampling ===
|
|
1495
1502
|
// OPTIMIZATION: Skip soul pass entirely if geometry doesn't have a soul
|
|
1496
1503
|
if (this.soulRenderTarget && hasSoul) {
|
|
1497
|
-
//
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1504
|
+
// OPTIMIZATION: Use cached soul mesh reference instead of traversing every frame
|
|
1505
|
+
if (!this._cachedSoulMesh) {
|
|
1506
|
+
this.scene.traverse(obj => {
|
|
1507
|
+
if (obj.name === 'crystalSoul') this._cachedSoulMesh = obj;
|
|
1508
|
+
});
|
|
1509
|
+
}
|
|
1510
|
+
const soulMesh = this._cachedSoulMesh;
|
|
1502
1511
|
|
|
1503
1512
|
this.renderer.setRenderTarget(this.soulRenderTarget);
|
|
1504
|
-
this.renderer.setClearColor(0x000000, 0);
|
|
1505
1513
|
this.renderer.clear();
|
|
1506
1514
|
|
|
1507
1515
|
// Render only soul layer (layer 2)
|
|
@@ -1520,17 +1528,17 @@ export class ThreeRenderer {
|
|
|
1520
1528
|
}
|
|
1521
1529
|
// Compute soul's screen center position for refraction sampling
|
|
1522
1530
|
if (this.coreMesh.material.uniforms.soulScreenCenter && soulMesh) {
|
|
1523
|
-
|
|
1524
|
-
|
|
1531
|
+
// OPTIMIZATION: Reuse pooled vector instead of cloning every frame
|
|
1532
|
+
this._soulPosTemp.copy(soulMesh.position);
|
|
1533
|
+
this._soulPosTemp.project(this.camera);
|
|
1525
1534
|
// Convert from NDC (-1 to 1) to UV (0 to 1)
|
|
1526
|
-
const soulScreenU = (
|
|
1527
|
-
const soulScreenV = (
|
|
1535
|
+
const soulScreenU = (this._soulPosTemp.x + 1.0) * 0.5;
|
|
1536
|
+
const soulScreenV = (this._soulPosTemp.y + 1.0) * 0.5;
|
|
1528
1537
|
this.coreMesh.material.uniforms.soulScreenCenter.value.set(soulScreenU, soulScreenV);
|
|
1529
1538
|
}
|
|
1530
1539
|
}
|
|
1531
1540
|
|
|
1532
1541
|
this.renderer.setRenderTarget(null);
|
|
1533
|
-
this.renderer.setClearColor(0x000000, 0);
|
|
1534
1542
|
}
|
|
1535
1543
|
|
|
1536
1544
|
// === STEP 1: Render main scene (layer 0) through bloom to screen ===
|
|
@@ -142,6 +142,12 @@ export class Rhythm3DAdapter {
|
|
|
142
142
|
// Ranges from 0.15 (tentative) to 1.0 (fully locked), provided by BPM detector
|
|
143
143
|
this.grooveConfidence = 1.0; // Default to full when not using BPM detection
|
|
144
144
|
|
|
145
|
+
// BPM multiplier: scales the effective BPM used for animations
|
|
146
|
+
// Default 1.0 = use detected BPM as-is
|
|
147
|
+
// Set to 0.5 to halve animation speed (e.g., for high BPM songs)
|
|
148
|
+
// Set to 2.0 to double animation speed (e.g., for slow songs)
|
|
149
|
+
this.bpmMultiplier = 1.0;
|
|
150
|
+
|
|
145
151
|
// Modulation output (computed each frame) - these are the SMOOTHED values
|
|
146
152
|
this.modulation = {
|
|
147
153
|
scaleMultiplier: 1.0, // Applied to gesture scale output
|
|
@@ -532,21 +538,27 @@ export class Rhythm3DAdapter {
|
|
|
532
538
|
// Compute groove motions from ABSOLUTE beat/bar progress
|
|
533
539
|
// This is frame-rate independent because beatProgress/barProgress come from
|
|
534
540
|
// RhythmEngine which uses performance.now(), not accumulated frame deltas
|
|
541
|
+
//
|
|
542
|
+
// Apply BPM multiplier to scale animation speed:
|
|
543
|
+
// - multiplier 0.5 = animations run at half speed (for high BPM songs)
|
|
544
|
+
// - multiplier 2.0 = animations run at double speed (for slow songs)
|
|
545
|
+
const effectiveBeatProgress = (this.beatProgress * this.bpmMultiplier) % 1;
|
|
546
|
+
const effectiveBarProgress = (this.barProgress * this.bpmMultiplier) % 1;
|
|
535
547
|
|
|
536
548
|
// Vertical bounce: synced to beat with configurable frequency
|
|
537
|
-
const bouncePhase = (
|
|
549
|
+
const bouncePhase = (effectiveBeatProgress * bounceFreq * Math.PI * 2) + phaseOffset;
|
|
538
550
|
const rawBounce = Math.sin(bouncePhase);
|
|
539
551
|
const easedBounce = this._applyEasing(rawBounce, easing);
|
|
540
552
|
|
|
541
553
|
// Horizontal sway: synced to bar with configurable frequency
|
|
542
|
-
const swayPhase = (
|
|
554
|
+
const swayPhase = (effectiveBarProgress * swayFreq * Math.PI * 2) + phaseOffset;
|
|
543
555
|
const rawSway = Math.sin(swayPhase);
|
|
544
556
|
const easedSway = this._applyEasing(rawSway, easing);
|
|
545
557
|
|
|
546
558
|
// Accent response: smooth curve that peaks at beat start, scaled by accent level
|
|
547
559
|
// Uses cosine curve centered on beat boundaries (0 and 1) for smooth falloff
|
|
548
560
|
// beatProgress 0.0 → peak, 0.5 → minimum, 1.0 → peak again
|
|
549
|
-
const beatProximity = (Math.cos(
|
|
561
|
+
const beatProximity = (Math.cos(effectiveBeatProgress * Math.PI * 2) + 1) * 0.5; // 0-1, peaks at beat
|
|
550
562
|
const accentStrength = Math.max(0, this.accent - 0.4) / 0.6; // 0-1, normalized above 0.4 threshold
|
|
551
563
|
const accentBoost = beatProximity * accentStrength * 0.25; // Smooth accent curve
|
|
552
564
|
|
|
@@ -774,6 +786,35 @@ export class Rhythm3DAdapter {
|
|
|
774
786
|
this.grooveConfidence = Math.max(0, Math.min(1, confidence));
|
|
775
787
|
}
|
|
776
788
|
|
|
789
|
+
/**
|
|
790
|
+
* Set BPM multiplier for animation speed control
|
|
791
|
+
*
|
|
792
|
+
* This scales the effective BPM used for groove animations without affecting
|
|
793
|
+
* the actual BPM detection or rhythm engine. Useful for:
|
|
794
|
+
* - Halving animation speed for high BPM songs (set to 0.5)
|
|
795
|
+
* - Doubling animation speed for slow songs (set to 2.0)
|
|
796
|
+
*
|
|
797
|
+
* @example
|
|
798
|
+
* // Halve animation speed for songs > 90 BPM
|
|
799
|
+
* const status = mascot.getBPMStatus();
|
|
800
|
+
* if (status.bpm > 90) {
|
|
801
|
+
* mascot.setBPMMultiplier(0.5);
|
|
802
|
+
* }
|
|
803
|
+
*
|
|
804
|
+
* @param {number} multiplier - BPM multiplier (0.25 to 4.0, default 1.0)
|
|
805
|
+
*/
|
|
806
|
+
setBPMMultiplier(multiplier) {
|
|
807
|
+
this.bpmMultiplier = Math.max(0.25, Math.min(4.0, multiplier));
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
/**
|
|
811
|
+
* Get current BPM multiplier
|
|
812
|
+
* @returns {number} Current BPM multiplier (default 1.0)
|
|
813
|
+
*/
|
|
814
|
+
getBPMMultiplier() {
|
|
815
|
+
return this.bpmMultiplier;
|
|
816
|
+
}
|
|
817
|
+
|
|
777
818
|
/**
|
|
778
819
|
* Set groove configuration (for custom tuning)
|
|
779
820
|
* @param {Object} config - Groove settings
|
|
@@ -291,7 +291,7 @@ export class CrystalSoul {
|
|
|
291
291
|
uniforms: {
|
|
292
292
|
time: { value: 0 },
|
|
293
293
|
emotionColor: { value: new THREE.Color(1, 1, 1) },
|
|
294
|
-
energyIntensity: { value:
|
|
294
|
+
energyIntensity: { value: 0.8 }, // Fixed value - no per-frame update needed
|
|
295
295
|
driftEnabled: { value: 1.0 },
|
|
296
296
|
driftSpeed: { value: 0.5 },
|
|
297
297
|
crossWaveEnabled: { value: 1.0 },
|
|
@@ -420,17 +420,15 @@ export class CrystalSoul {
|
|
|
420
420
|
uniforms.time.value += deltaTime / 1000;
|
|
421
421
|
}
|
|
422
422
|
|
|
423
|
-
// Update emotion color
|
|
423
|
+
// Update emotion color only if changed (avoid unnecessary GPU uniform sync)
|
|
424
424
|
if (uniforms.emotionColor && glowColor) {
|
|
425
|
-
uniforms.emotionColor.value
|
|
426
|
-
|
|
427
|
-
|
|
425
|
+
const current = uniforms.emotionColor.value;
|
|
426
|
+
if (current.r !== glowColor[0] || current.g !== glowColor[1] || current.b !== glowColor[2]) {
|
|
427
|
+
current.setRGB(glowColor[0], glowColor[1], glowColor[2]);
|
|
428
|
+
}
|
|
428
429
|
}
|
|
429
430
|
|
|
430
|
-
//
|
|
431
|
-
if (uniforms.energyIntensity) {
|
|
432
|
-
uniforms.energyIntensity.value = 0.8;
|
|
433
|
-
}
|
|
431
|
+
// Note: energyIntensity is fixed at 0.8 (set in constructor, no per-frame update needed)
|
|
434
432
|
|
|
435
433
|
// Apply breathing scale
|
|
436
434
|
if (this.mesh) {
|
package/src/3d/index.js
CHANGED
|
@@ -1245,6 +1245,43 @@ export class EmotiveMascot3D {
|
|
|
1245
1245
|
return this.core3D?.rhythm3DAdapter?.grooveConfidence ?? 1.0;
|
|
1246
1246
|
}
|
|
1247
1247
|
|
|
1248
|
+
/**
|
|
1249
|
+
* Set BPM multiplier for groove animation speed control
|
|
1250
|
+
*
|
|
1251
|
+
* This scales the effective BPM used for groove animations without affecting
|
|
1252
|
+
* the actual BPM detection or rhythm engine. Useful for controlling how fast
|
|
1253
|
+
* the mascot dances regardless of the detected song tempo.
|
|
1254
|
+
*
|
|
1255
|
+
* Common use cases:
|
|
1256
|
+
* - Set to 0.5 to halve animation speed for high BPM songs (>90 BPM)
|
|
1257
|
+
* - Set to 2.0 to double animation speed for very slow songs
|
|
1258
|
+
* - Set to 1.0 (default) to use detected BPM as-is
|
|
1259
|
+
*
|
|
1260
|
+
* @example
|
|
1261
|
+
* // Halve animation speed for fast songs
|
|
1262
|
+
* const status = mascot.getBPMStatus();
|
|
1263
|
+
* if (status.bpm > 90) {
|
|
1264
|
+
* mascot.setBPMMultiplier(0.5);
|
|
1265
|
+
* } else {
|
|
1266
|
+
* mascot.setBPMMultiplier(1.0);
|
|
1267
|
+
* }
|
|
1268
|
+
*
|
|
1269
|
+
* @param {number} multiplier - BPM multiplier (0.25 to 4.0, default 1.0)
|
|
1270
|
+
*/
|
|
1271
|
+
setBPMMultiplier(multiplier) {
|
|
1272
|
+
if (this.core3D?.rhythm3DAdapter) {
|
|
1273
|
+
this.core3D.rhythm3DAdapter.setBPMMultiplier(multiplier);
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
/**
|
|
1278
|
+
* Get current BPM multiplier
|
|
1279
|
+
* @returns {number} Current BPM multiplier (default 1.0)
|
|
1280
|
+
*/
|
|
1281
|
+
getBPMMultiplier() {
|
|
1282
|
+
return this.core3D?.rhythm3DAdapter?.getBPMMultiplier() ?? 1.0;
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1248
1285
|
/**
|
|
1249
1286
|
* Set groove configuration for idle animations (advanced tuning)
|
|
1250
1287
|
* @param {Object} config - Groove settings
|