fitSunShadow function
bool
fitSunShadow(
- RenderView view,
- Vector3 direction,
- ShadowSettings settings,
- ShadowFit out, {
- double from = 0,
- 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;
}