@joshtol/emotive-engine 3.3.2 → 3.3.4

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.2",
4
+ "version": "3.3.4",
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",
@@ -1878,7 +1878,10 @@ export class Core3DManager {
1878
1878
  calibrationRotation: this.calibrationRotation, // Applied on top of animated rotation
1879
1879
  solarEclipse: this.effectManager.getSolarEclipse(), // Pass eclipse manager for synchronized updates
1880
1880
  deltaTime, // Pass deltaTime for eclipse animation
1881
- morphProgress: morphState.isTransitioning ? morphState.visualProgress : null // For corona fade-in
1881
+ morphProgress: morphState.isTransitioning ? morphState.visualProgress : null, // For corona fade-in
1882
+ // OPTIMIZATION FLAGS: Skip render passes when not needed
1883
+ hasSoul: this.customMaterialType === 'crystal' && this.crystalSoul !== null,
1884
+ hasParticles: this.particleVisibility && this.particleOrchestrator !== null
1882
1885
  });
1883
1886
 
1884
1887
  // Update lunar eclipse animation (Blood Moon)
@@ -1270,6 +1270,8 @@ export class ThreeRenderer {
1270
1270
  * @param {number} [params.cameraRoll=0] - Camera-space roll rotation
1271
1271
  * @param {SolarEclipse} [params.solarEclipse=null] - Solar eclipse manager for synchronized updates
1272
1272
  * @param {number} [params.deltaTime=0] - Delta time for eclipse animation (seconds)
1273
+ * @param {boolean} [params.hasSoul=false] - Whether this geometry has a soul layer (optimization)
1274
+ * @param {boolean} [params.hasParticles=true] - Whether particles are enabled (optimization)
1273
1275
  */
1274
1276
  render(params = {}) {
1275
1277
  // Guard against calls after destroy
@@ -1347,7 +1349,9 @@ export class ThreeRenderer {
1347
1349
  cameraRoll = 0, // Camera-space roll rotation applied after all other rotations
1348
1350
  solarEclipse = null, // Solar eclipse manager for synchronized updates
1349
1351
  deltaTime = 0, // Delta time for eclipse animation
1350
- morphProgress = null // Morph progress for corona fade-in (null = no morph, 0-1 = morphing)
1352
+ morphProgress = null, // Morph progress for corona fade-in (null = no morph, 0-1 = morphing)
1353
+ hasSoul = false, // Whether this geometry has a soul layer (skip soul pass if false)
1354
+ hasParticles = true // Whether particles are enabled (skip particle pass if false)
1351
1355
  } = params;
1352
1356
 
1353
1357
  // Update camera controls FIRST before any rendering
@@ -1488,7 +1492,8 @@ export class ThreeRenderer {
1488
1492
  // Render with post-processing if enabled, otherwise direct render
1489
1493
  if (this.composer) {
1490
1494
  // === STEP 0: Render soul (layer 2) to texture for refraction sampling ===
1491
- if (this.soulRenderTarget) {
1495
+ // OPTIMIZATION: Skip soul pass entirely if geometry doesn't have a soul
1496
+ if (this.soulRenderTarget && hasSoul) {
1492
1497
  // Find soul mesh in scene (needed for screen center calculation)
1493
1498
  let soulMesh = null;
1494
1499
  this.scene.traverse(obj => {
@@ -1533,7 +1538,8 @@ export class ThreeRenderer {
1533
1538
  this.composer.render();
1534
1539
 
1535
1540
  // === STEP 2: Render particles (layer 1) to separate render target ===
1536
- if (this.particleRenderTarget && this.particleBloomPass) {
1541
+ // OPTIMIZATION: Skip particle pass entirely if particles are disabled
1542
+ if (hasParticles && this.particleRenderTarget && this.particleBloomPass) {
1537
1543
  // Clear particle render target with WHITE (non-black) to prevent dark halos
1538
1544
  this.renderer.setRenderTarget(this.particleRenderTarget);
1539
1545
  this.renderer.setClearColor(0xffffff, 0); // White RGB, but 0 alpha
@@ -1565,7 +1571,7 @@ export class ThreeRenderer {
1565
1571
  // Reset clear color
1566
1572
  this.renderer.setClearColor(0x000000, 0);
1567
1573
  this.renderer.setRenderTarget(null);
1568
- } else {
1574
+ } else if (hasParticles) {
1569
1575
  // Fallback: Render particles directly (no bloom)
1570
1576
  this.camera.layers.set(1);
1571
1577
  this.renderer.render(this.scene, this.camera);
@@ -95,18 +95,14 @@ const soulFragmentShader = `
95
95
 
96
96
  if (driftEnabled > 0.5) {
97
97
  float t = time * driftSpeed;
98
- // Primary drift - moving in one direction
98
+ // OPTIMIZED: Reduced from 4 noise calls to 2 for better performance
99
+ // Primary drift - single noise call with combined coordinates
99
100
  float drift1 = noise3D(vPosition * 2.0 + vec3(t, t * 0.7, t * 0.3));
100
- float drift2 = noise3D(vPosition * 3.0 - vec3(t * 0.5, t, t * 0.8));
101
- // Use max instead of multiply to avoid near-zero products
102
- primaryDrift = max(drift1, drift2);
103
- primaryDrift = max(0.0, primaryDrift - 0.4) * 2.0; // Rescale after threshold
104
-
105
- // Secondary drift - offset in opposite direction to fill gaps
106
- float drift3 = noise3D(vPosition * 2.5 - vec3(t * 0.8, t * 0.4, t));
107
- float drift4 = noise3D(vPosition * 1.8 + vec3(t * 0.6, t * 0.9, t * 0.2));
108
- secondaryDrift = max(drift3, drift4);
109
- secondaryDrift = max(0.0, secondaryDrift - 0.4) * 2.0;
101
+ primaryDrift = max(0.0, drift1 - 0.3) * 1.5; // Adjusted threshold for single noise
102
+
103
+ // Secondary drift - offset phase to fill gaps
104
+ float drift2 = noise3D(vPosition * 2.5 - vec3(t * 0.6, t * 0.4, t));
105
+ secondaryDrift = max(0.0, drift2 - 0.3) * 1.5;
110
106
 
111
107
  driftEnergy = primaryDrift + secondaryDrift;
112
108
  }
@@ -39,7 +39,7 @@ vec3 rgbToHsl(vec3 rgb) {
39
39
  float l = (maxC + minC) / 2.0;
40
40
 
41
41
  if (delta > 0.0001) {
42
- s = l < 0.5 ? delta / (maxC + minC) : delta / (2.0 - maxC - minC);
42
+ s = l < 0.5 ? delta / max(maxC + minC, 0.0001) : delta / max(2.0 - maxC - minC, 0.0001);
43
43
 
44
44
  // Use tolerance-based comparison instead of exact equality
45
45
  float eps = 0.0001;
@@ -135,18 +135,20 @@ vec3 applyBlendMode(vec3 base, vec3 blend, int mode) {
135
135
  } else if (mode == 2) {
136
136
  // COLOR BURN: (blend==0.0) ? 0.0 : max((1.0-((1.0-base)/blend)), 0.0)
137
137
  // Darkens base color to reflect blend color by increasing contrast
138
+ // Use safe division with epsilon to avoid division by zero
138
139
  return vec3(
139
- blend.r == 0.0 ? 0.0 : max(1.0 - ((1.0 - base.r) / blend.r), 0.0),
140
- blend.g == 0.0 ? 0.0 : max(1.0 - ((1.0 - base.g) / blend.g), 0.0),
141
- blend.b == 0.0 ? 0.0 : max(1.0 - ((1.0 - base.b) / blend.b), 0.0)
140
+ max(1.0 - ((1.0 - base.r) / max(blend.r, 0.0001)), 0.0),
141
+ max(1.0 - ((1.0 - base.g) / max(blend.g, 0.0001)), 0.0),
142
+ max(1.0 - ((1.0 - base.b) / max(blend.b, 0.0001)), 0.0)
142
143
  );
143
144
  } else if (mode == 3) {
144
145
  // COLOR DODGE: (blend==1.0) ? 1.0 : min(base/(1.0-blend), 1.0)
145
146
  // Brightens base color to reflect blend color by decreasing contrast
147
+ // Use safe division with epsilon to avoid division by zero
146
148
  return vec3(
147
- blend.r == 1.0 ? 1.0 : min(base.r / (1.0 - blend.r), 1.0),
148
- blend.g == 1.0 ? 1.0 : min(base.g / (1.0 - blend.g), 1.0),
149
- blend.b == 1.0 ? 1.0 : min(base.b / (1.0 - blend.b), 1.0)
149
+ min(base.r / max(1.0 - blend.r, 0.0001), 1.0),
150
+ min(base.g / max(1.0 - blend.g, 0.0001), 1.0),
151
+ min(base.b / max(1.0 - blend.b, 0.0001), 1.0)
150
152
  );
151
153
  } else if (mode == 4) {
152
154
  // SCREEN: 1 - (1 - base) * (1 - blend)
@@ -183,10 +185,11 @@ vec3 applyBlendMode(vec3 base, vec3 blend, int mode) {
183
185
  } else if (mode == 9) {
184
186
  // VIVID LIGHT: blend < 0.5 ? ColorBurn(base, 2*blend) : ColorDodge(base, 2*(blend-0.5))
185
187
  // Burns or dodges by increasing/decreasing contrast - extreme saturation boost
188
+ // Use safe division with epsilon to avoid division by zero
186
189
  return vec3(
187
- blend.r < 0.5 ? (blend.r == 0.0 ? 0.0 : max(1.0 - ((1.0 - base.r) / (2.0 * blend.r)), 0.0)) : (blend.r == 1.0 ? 1.0 : min(base.r / (2.0 * (1.0 - blend.r)), 1.0)),
188
- blend.g < 0.5 ? (blend.g == 0.0 ? 0.0 : max(1.0 - ((1.0 - base.g) / (2.0 * blend.g)), 0.0)) : (blend.g == 1.0 ? 1.0 : min(base.g / (2.0 * (1.0 - blend.g)), 1.0)),
189
- blend.b < 0.5 ? (blend.b == 0.0 ? 0.0 : max(1.0 - ((1.0 - base.b) / (2.0 * blend.b)), 0.0)) : (blend.b == 1.0 ? 1.0 : min(base.b / (2.0 * (1.0 - blend.b)), 1.0))
190
+ blend.r < 0.5 ? max(1.0 - ((1.0 - base.r) / max(2.0 * blend.r, 0.0001)), 0.0) : min(base.r / max(2.0 * (1.0 - blend.r), 0.0001), 1.0),
191
+ blend.g < 0.5 ? max(1.0 - ((1.0 - base.g) / max(2.0 * blend.g, 0.0001)), 0.0) : min(base.g / max(2.0 * (1.0 - blend.g), 0.0001), 1.0),
192
+ blend.b < 0.5 ? max(1.0 - ((1.0 - base.b) / max(2.0 * blend.b, 0.0001)), 0.0) : min(base.b / max(2.0 * (1.0 - blend.b), 0.0001), 1.0)
190
193
  );
191
194
  } else if (mode == 10) {
192
195
  // LINEAR LIGHT: blend < 0.5 ? LinearBurn(base, 2*blend) : LinearDodge(base, 2*(blend-0.5))