fitSunShadow function

bool fitSunShadow(
  1. RenderView view,
  2. Vector3 direction,
  3. ShadowSettings settings,
  4. ShadowFit out, {
  5. double from = 0,
  6. double? to,
})

Places a shadow map for a sun shining along direction around what view sees up to settings' distance, into out. The box is the bounding sphere of that slice of the view (so it keeps its size as the camera turns) and moves in whole texels (so shadow edges don't crawl as the camera moves). False when there is nothing to fit.

Implementation

bool fitSunShadow(
  RenderView view,
  Vector3 direction,
  ShadowSettings settings,
  ShadowFit out, {
  double from = 0,
  double? to,
}) {
  if (direction.lengthSquared < 1e-12) return false;
  _viewProjection.setProduct(view.projection, view.view);
  if (_inverse.copyInverse(_viewProjection) == 0) return false;

  // The view frustum's corners: near (z = 0) then far (z = 1).
  var i = 0;
  for (final z in [0.0, 1.0]) {
    for (final y in [-1.0, 1.0]) {
      for (final x in [-1.0, 1.0]) {
        final corner = _corners[i++]..setValues(x, y, z);
        _inverse.transformPointInto(corner, corner);
      }
    }
  }
  // Cut the far corners at the shadow distance along the view direction.
  final v = view.view.storage;
  final fx = v[2], fy = v[6], fz = v[10];
  final eye = view.cameraPosition;
  double depthOf(Vector3 p) =>
      (p.x - eye.x) * fx + (p.y - eye.y) * fy + (p.z - eye.z) * fz;
  // A cascade covers [from]..[to] of that: its near corners move out too.
  final end = to ?? settings.distance;
  for (var c = 0; c < 4; c++) {
    final near = _corners[c], far = _corners[c + 4];
    final nx = near.x, ny = near.y, nz = near.z;
    final ex = far.x, ey = far.y, ez = far.z;
    final dn = depthOf(near), df = depthOf(far);
    final span = df - dn;
    double at(double depth) =>
        span > 1e-9 ? ((depth - dn) / span).clamp(0.0, 1.0) : 1.0;
    final tNear = at(math.max(from, dn)), tFar = at(math.min(end, df));
    near.setValues(
      nx + (ex - nx) * tNear,
      ny + (ey - ny) * tNear,
      nz + (ez - nz) * tNear,
    );
    far.setValues(
      nx + (ex - nx) * tFar,
      ny + (ey - ny) * tFar,
      nz + (ez - nz) * tFar,
    );
  }
  _centre.setZero();
  for (final p in _corners) {
    _centre.add(p);
  }
  _centre.scale(1 / 8);
  var radius = 0.0;
  for (final p in _corners) {
    radius = math.max(radius, p.distanceTo(_centre));
  }
  // A size that only changes in sixteenths of a metre.
  radius = math.max(1 / 16, (radius * 16).ceilToDouble() / 16);

  _lightView(direction, out.view);
  out.view.transformPointInto(_centre, _point);
  final texel = 2 * radius / settings.mapSize;
  final cx = (_point.x / texel).floorToDouble() * texel;
  final cy = (_point.y / texel).floorToDouble() * texel;
  final near = _point.z - radius - settings.casterReach;
  final far = _point.z + radius;
  out.projection.setOrthographicYDown(
    cx - radius,
    cx + radius,
    cy - radius,
    cy + radius,
    near,
    far,
  );
  out.viewProjection.setProduct(out.projection, out.view);
  out.frustum.setFromViewProjection(out.viewProjection);
  out
    ..radius = radius
    ..texelSize = texel
    ..depthRange = far - near;
  return true;
}