updateSpineSkeleton method

void updateSpineSkeleton(
  1. Fish fish,
  2. double dt
)

Implementation

void updateSpineSkeleton(Fish fish, double dt) {
  // 1. Update Wiggle Phase based on current speed
  double speedRatio = (fish.currentSpeed / fish.config.maxSpeed).clamp(0.1, 2.5);

  // Smooth gentle wiggle speed during loading instead of hyperactive fast wiggling
  double wiggleSpeed = (fish.state == FishState.loading)
      ? 2.4 * fish.config.tailWiggleMultiplier
      : 8.5 * speedRatio * fish.config.tailWiggleMultiplier;
  fish.wigglePhase += dt * wiggleSpeed;

  double totalLength = fish.config.bodyLength * fish.scale;
  double segmentLength = totalLength / (Fish.numJoints - 1);

  // 2. Undulatory Traveling Wave Physics along Spine
  double effectiveSpeedRatio = (fish.state == FishState.loading) ? 0.32 : speedRatio;
  double headSwayAmp = 0.07 * (0.4 + 0.6 * effectiveSpeedRatio);
  fish.jointAngles[0] = fish.angle + sin(fish.wigglePhase) * headSwayAmp;
  fish.spineJoints[0] = fish.position;

  for (int i = 1; i < Fish.numJoints; i++) {
    double t = i / (Fish.numJoints - 1);

    // Amplitude increases towards tail with exponent curve
    double waveAmplitude = (0.03 + 0.36 * pow(t, 1.35)) * (0.5 + 0.5 * effectiveSpeedRatio);
    double phaseShift = t * 3.3; // Phase delay along body

    double waveOffset = sin(fish.wigglePhase - phaseShift) * waveAmplitude;
    double targetJointAngle = fish.angle + waveOffset;

    double prevAngle = fish.jointAngles[i - 1];
    double angleDiff = _normalizeAngle(targetJointAngle - prevAngle);

    fish.jointAngles[i] = prevAngle + angleDiff * 0.72;

    Offset dir = Offset(cos(fish.jointAngles[i]), sin(fish.jointAngles[i]));
    fish.spineJoints[i] = fish.spineJoints[i - 1] - (dir * segmentLength);
  }
}