@joshtol/emotive-engine 3.3.4 → 3.3.5

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@joshtol/emotive-engine",
4
- "version": "3.3.4",
4
+ "version": "3.3.5",
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",
@@ -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
- // Normalize emotion color for consistent perceived brightness across all emotions
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
- // IMPORTANT: Use glowColor (RGB), not glowColorHex - glowColor has undertone saturation applied
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 normalizedCore = normalizeRGBLuminance(this.glowColor, 0.30);
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
  }
@@ -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
- // Find soul mesh in scene (needed for screen center calculation)
1498
- let soulMesh = null;
1499
- this.scene.traverse(obj => {
1500
- if (obj.name === 'crystalSoul') soulMesh = obj;
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
- const soulWorldPos = soulMesh.position.clone();
1524
- const soulNDC = soulWorldPos.project(this.camera);
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 = (soulNDC.x + 1.0) * 0.5;
1527
- const soulScreenV = (soulNDC.y + 1.0) * 0.5;
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 ===
@@ -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: 1.5 },
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.setRGB(
426
- glowColor[0], glowColor[1], glowColor[2]
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
- // Fixed intensity matching original implementation
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) {