renderShadows method

void renderShadows(
  1. Canvas canvas,
  2. Matrix4 viewProjection,
  3. Light light,
  4. List<Node> nodes,
  5. double groundY,
)

Renders ground-plane shadows for all nodes illuminated by a directional light.

Implementation

void renderShadows(
  Canvas canvas,
  Matrix4 viewProjection,
  Light light,
  List<Node> nodes,
  double groundY,
) {
  if (!config.enabled || light.type != LightType.directional) return;

  final shadowPaint = Paint()
    ..color = Color.fromARGB((config.darkness * 100).toInt(), 0, 0, 0);

  for (final node in nodes) {
    if (!node.visible) return;
    final pos = node.worldPosition;

    // Project position onto ground plane along light direction
    if (light.direction.y == 0) continue;
    final t = (groundY - pos.y) / light.direction.y;
    if (t < 0) continue; // Light coming from below

    final shadowPos = Vector3(
      pos.x + light.direction.x * t,
      groundY + 0.01, // Slight offset to prevent z-fighting
      pos.z + light.direction.z * t,
    );

    // Project shadow position to screen
    final clip = viewProjection.transformed(
      Vector4(shadowPos.x, shadowPos.y, shadowPos.z, 1),
    );
    if (clip.w <= 0.001) continue;

    final screenX = clip.x / clip.w;
    final screenY = clip.y / clip.w;
    final dist = (pos - shadowPos).length;

    // Shadow size based on distance (penumbra)
    final size = (0.3 + dist * 0.1).clamp(0.2, 2.0);
    final scale = 100 / clip.w;

    // Draw elliptical shadow
    canvas.save();
    canvas.translate(screenX, screenY);
    canvas.scale(scale * size, scale * size * 0.5); // Flatten on ground
    canvas.drawOval(
      Rect.fromCenter(center: Offset.zero, width: 1, height: 1),
      shadowPaint..maskFilter = const MaskFilter.blur(BlurStyle.normal, 0.3),
    );
    canvas.restore();
  }
}