Demo.prototype.addEffectMeshExplosion = function (startTime, durationTime, amountOfParticles, object, areaX, areaY, areaZ, xOffset, yOffset, particleSize, enabledParameter, colorR, colorG, colorB, kala, ruin)
{

  let meshParticles = new Array(amountOfParticles);
  const maxDist = Math.max(areaX, areaY, areaZ);
  
  for (let i = 0; i < meshParticles.length; i++) {
    // Starting position (center or small area)
    let xStart = Math.random() * 0.2 - 0.1;
    let yStart = Math.random() * 0.2 - 0.1;
    let zStart = Math.random() * 0.2 - 0.1;
    
    let xStartDir = (xStart != 0) ? xStart/Math.abs(xStart) : 0;
    let yStartDir = (yStart != 0) ? yStart/Math.abs(yStart) : 0;
    let zStartDir = (zStart != 0) ? zStart/Math.abs(zStart) : 0;
    
    // Direction for explosion
    let xDir = Math.random() * areaX - areaX * 0.5;
    let yDir = Math.random() * areaY - areaY * 0.5;
    let zDir = Math.random() * areaZ - areaZ * 0.5;
    
    let vectorLength = Math.sqrt(xDir*xDir + yDir*yDir + zDir*zDir);
    
    // End position
    let xEnd = vectorLength*xStartDir*maxDist + (xDir/vectorLength) * (Math.random() * maxDist * 2 - maxDist) + xOffset + xStart;
    let yEnd = vectorLength*yStartDir*maxDist + (yDir/vectorLength) * (Math.random() * maxDist * 2 - maxDist) + yOffset + yStart;
    let zEnd = vectorLength*zStartDir*maxDist + (zDir/vectorLength) * (Math.random() * maxDist * 2 - maxDist) + zStart;

    let angleY = 0;
    let angleX = 0;
    if(ruin)
    {
      angleY = Utils.random()*360;
      angleX = Utils.random()*90;
    } else if (kala)
    {
      angleY = 4.712;
    } else
    {
      angleY = Utils.random()*360;
    }

    meshParticles[i] = {
      "x1": xStart + xOffset,
      "y1": yStart + yOffset,
      "z1": zStart,
      "x2": xEnd,
      "y2": yEnd,
      "z2": zEnd,
      "scale": Utils.random()*1.5*particleSize + particleSize*1.5,
      "startAngleY": angleY,
      "startAngleX": angleX,
      "speedAngleY": (Math.random()*4-2)*250,
      "speedAngleX": ruin ? (Math.random()*4-2)*250 : 0
    };
  }

  this.loader.addAnimation({
    "start": startTime, 
    "duration": durationTime,
    "object": {
      "name": object
    },
    "perspective": "3d",
    "color": [{
      "r": colorR,
      "g": colorG,
      "b": colorB
    }],
    "scale": [{"uniform3d": 0.1}],
    "instancer": {
      "count": meshParticles.length,
      "runInstanceFunction": (properties) => {

        const i = properties.index;
        let angle = properties.angle;
        let object = properties.object;
        let color = properties.color;
        
        const particle = meshParticles[i];
        const now = getSceneTimeFromStart();
        
        // Calculate progress through the animation
        const percent = (now - startTime) / durationTime;
        
        // Logarithmic interpolation for explosion movement (same as EffectExplosion)
        const mix = Math.log((percent * 7.0 + 1.0)) / 3;
        
        // Interpolate position from start to end
        object.position.x = Utils.mix(particle.x1, particle.x2, mix);
        object.position.y = Utils.mix(particle.y1, particle.y2, mix);
        object.position.z = Utils.mix(particle.z1, particle.z2, mix);
        
        // Add kala bobbing effect if enabled
        if(kala) {
          object.position.y += 5 * Math.sin(i + 3 * now);
        }
        
        // Fade out over time (same as EffectExplosion)
        const alpha = 1.0 - percent * percent;
        
        // Apply scale with fade
        const scale = particle.scale * alpha;
        object.scale.x = scale;
        object.scale.y = scale;
        object.scale.z = scale;
        
        // Apply color alpha
        color.a = alpha;
        
        // Rotation animation
        if(ruin) {
          angle.degreesY = particle.startAngleY + now * particle.speedAngleY;
          angle.degreesX = particle.startAngleX + now * particle.speedAngleX;
        } else {
          angle.degreesY = particle.startAngleY + now * particle.speedAngleY;
        }
      } 
    }
  });
}

Demo.prototype.addEffectMeshStream = function (startTime, durationTime, amountOfParticles, particleSize, object, xPos, yPos, zPos, xSpread = 1.0, ySpread = 1.0, interval = 0.0, angleY = 0, colorR = 1.0, colorG = 1.0, colorB = 1.0, enabledSyncParam = "Sub:shoot", shootSpeed = 10.0)
{
  const deg2rad = 0.01745329251;
  let particles = new Array(amountOfParticles);
  
  for (let i = 0; i < particles.length; i++) {
    particles[i] = {
      "x0": xPos,
      "y0": yPos,
      "z0": zPos,
      "x1": (Utils.random() * xSpread * 2 - xSpread),
      "y1": shootSpeed,
      "z1": (Utils.random() * ySpread * 2 - ySpread),
      "angleX": 0,
      "angleY": 0,
      "angleZ": 0,
      "enabled": false,
      "size": Math.random()*2.0+.5
    };
  }

  let prevIntervalTime = 0;
  let particleIndex = 0;
  
  this.loader.addAnimation({
    "start": startTime, 
    "duration": durationTime,
    "object": {
      "name": object
    },
    "perspective": "3d",
    "color": [{
      "r": colorR,
      "g": colorG,
      "b": colorB
    }],
    "scale": [{"uniform3d": 0.1}],
    "angle": [{"degreesY": angleY}],
    "runPreFunction": (animation) => {
      // Only spawn new particles if enabledSyncParam is not set or equals 1
      if(enabledSyncParam && Sync.get(enabledSyncParam) < 0.5) {
        return; // Don't spawn when disabled
      }
      
      if(getSceneTimeFromStart() > prevIntervalTime + interval) {
        prevIntervalTime = getSceneTimeFromStart();
        particleIndex++;
        if(particleIndex >= particles.length) 
          particleIndex = 0;

        particles[particleIndex].enabled = true;
        // Reset position and randomize velocities
        particles[particleIndex].x0 = xPos;
        particles[particleIndex].y0 = yPos;
        particles[particleIndex].z0 = zPos;
        particles[particleIndex].x1 = (Utils.random() * xSpread * 2 - xSpread);
        particles[particleIndex].z1 = (Utils.random() * ySpread * 2 - ySpread);
        // Randomize angles
        particles[particleIndex].angleX = Utils.random() * 360;
        particles[particleIndex].angleY = Utils.random() * 360;
        particles[particleIndex].angleZ = Utils.random() * 360;
      }
    },
    "instancer": {
      "count": particles.length,
      "runInstanceFunction": (properties) => {

        const i = properties.index;
        let object = properties.object;
        let angle = properties.angle;

        if(particles[i].enabled == false) {
          object.scale.x = 0;
          object.scale.y = 0;
          object.scale.z = 0;   
          return;
        }
        
        const scale = particles[i].size;
        object.scale.x = scale;
        object.scale.y = scale;
        object.scale.z = scale;   

        particles[i].x0 += particles[i].x1 * getDeltaTime();
        particles[i].y0 += particles[i].y1 * getDeltaTime();
        particles[i].z0 += particles[i].z1 * getDeltaTime();

        object.position.x = particles[i].x0;        
        object.position.y = particles[i].y0;
        object.position.z = particles[i].z0;
        
        angle.degreesX = particles[i].angleX;
        angle.degreesY = particles[i].angleY;
        angle.degreesZ = particles[i].angleZ;
      }
    }
  });
}
