update method

void update()

Tick principal de la simulación - OPTIMIZADO

Implementation

void update() {
  if (boids.isEmpty) return;

  // Reconstruir spatial grid
  _buildSpatialGrid();

  // Parámetros dinámicos basados en audio
  final dynamicPerception = perceptionRadius + (audioAmplitude * 15);
  final dynamicMaxSpeed = maxSpeed + (audioAmplitude * 1.2);
  final dynamicCohesion = cohesionWeight + (sin(audioPhase) * 0.15);

  for (var boid in boids) {
    final neighbors = _getNeighbors(boid, dynamicPerception);

    // Acumuladores para las 3 reglas
    double sepX = 0, sepY = 0;
    double aliX = 0, aliY = 0;
    double cohX = 0, cohY = 0;
    int sepCount = 0, aliCount = 0;

    for (var other in neighbors) {
      final dx = boid.x - other.x;
      final dy = boid.y - other.y;
      final dSq = dx * dx + dy * dy;
      final d = sqrt(dSq);

      // Separación (muy cercanos)
      final sepRadius = dynamicPerception * 0.35;
      if (d < sepRadius && d > 0.001) {
        final invD = 1 / (dSq + 1);
        sepX += dx * invD;
        sepY += dy * invD;
        sepCount++;
      }

      // Alineación y cohesión
      aliX += other.vx;
      aliY += other.vy;
      cohX += other.x;
      cohY += other.y;
      aliCount++;
    }

    double ax = 0, ay = 0;

    // Aplicar separación
    if (sepCount > 0) {
      sepX /= sepCount;
      sepY /= sepCount;
      final sepMag = sqrt(sepX * sepX + sepY * sepY);
      if (sepMag > 0.001) {
        sepX = sepX / sepMag * maxSpeed - boid.vx;
        sepY = sepY / sepMag * maxSpeed - boid.vy;
        final steerMag = sqrt(sepX * sepX + sepY * sepY);
        if (steerMag > maxForce) {
          sepX = sepX / steerMag * maxForce;
          sepY = sepY / steerMag * maxForce;
        }
      }
      ax += sepX * separationWeight;
      ay += sepY * separationWeight;
    }

    // Aplicar alineación y cohesión
    if (aliCount > 0) {
      // Alineación
      aliX /= aliCount;
      aliY /= aliCount;
      final aliMag = sqrt(aliX * aliX + aliY * aliY);
      if (aliMag > 0.001) {
        aliX = aliX / aliMag * maxSpeed - boid.vx;
        aliY = aliY / aliMag * maxSpeed - boid.vy;
        final steerMag = sqrt(aliX * aliX + aliY * aliY);
        if (steerMag > maxForce) {
          aliX = aliX / steerMag * maxForce;
          aliY = aliY / steerMag * maxForce;
        }
      }
      ax += aliX * (alignmentWeight + audioFrequency * 0.2);
      ay += aliY * (alignmentWeight + audioFrequency * 0.2);

      // Cohesión
      cohX = cohX / aliCount - boid.x;
      cohY = cohY / aliCount - boid.y;
      final cohMag = sqrt(cohX * cohX + cohY * cohY);
      if (cohMag > 0.001) {
        cohX = cohX / cohMag * maxSpeed - boid.vx;
        cohY = cohY / cohMag * maxSpeed - boid.vy;
        final steerMag = sqrt(cohX * cohX + cohY * cohY);
        if (steerMag > maxForce) {
          cohX = cohX / steerMag * maxForce;
          cohY = cohY / steerMag * maxForce;
        }
      }
      ax += cohX * dynamicCohesion;
      ay += cohY * dynamicCohesion;
    }

    // Attractor
    if (attractor != null) {
      final toAttrX = attractor!.dx - boid.x;
      final toAttrY = attractor!.dy - boid.y;
      ax += toAttrX * attractorStrength * (1 + audioAmplitude);
      ay += toAttrY * attractorStrength * (1 + audioAmplitude);
    }

    // Fuerza de pulso con el beat (simplificado)
    if (audioBeat > 0 && audioAmplitude > 0.15) {
      final beatPulse = sin(audioPhase * audioBeat * 0.5) * audioAmplitude * 0.2;
      final centerX = width / 2;
      final centerY = height / 2;
      final fromCenterX = boid.x - centerX;
      final fromCenterY = boid.y - centerY;
      final dist = sqrt(fromCenterX * fromCenterX + fromCenterY * fromCenterY) + 1;
      ax += (fromCenterX / dist) * beatPulse;
      ay += (fromCenterY / dist) * beatPulse;
    }

    // Actualizar velocidad
    boid.vx += ax;
    boid.vy += ay;

    // Limitar velocidad
    final speed = sqrt(boid.vx * boid.vx + boid.vy * boid.vy);
    if (speed > dynamicMaxSpeed) {
      boid.vx = boid.vx / speed * dynamicMaxSpeed;
      boid.vy = boid.vy / speed * dynamicMaxSpeed;
    }

    // Actualizar posición
    boid.x += boid.vx;
    boid.y += boid.vy;

    // Actualizar profundidad Z (movimiento lento de vaivén)
    boid.vz += (_random.nextDouble() - 0.5) * 0.001;
    boid.vz = boid.vz.clamp(-0.008, 0.008);
    boid.z += boid.vz;
    boid.z = boid.z.clamp(0.0, 1.0);

    // Wrap around edges
    if (boid.x < 0) boid.x += width;
    if (boid.x > width) boid.x -= width;
    if (boid.y < 0) boid.y += height;
    if (boid.y > height) boid.y -= height;

    // Actualizar energía y tamaño
    boid.energy = 0.5 + audioAmplitude * 0.5;
    boid.size = 2.5 + (speed / dynamicMaxSpeed) * 1.2 + audioAmplitude * 1.0;
  }

  // Actualizar conexiones cada 4 frames para ahorrar CPU
  _connectionUpdateCounter++;
  if (_connectionUpdateCounter >= 4) {
    _updateConnections();
    _connectionUpdateCounter = 0;
  }

  notifyListeners();
}