Skip to content

Cast shadows at the physical solar angle for non-cubic voxels - #51

Merged
allisonrenie merged 2 commits into
mainfrom
49/voxel-dim-geometry
Aug 21, 2026
Merged

allisonrenie merged 2 commits into
mainfrom
49/voxel-dim-geometry

Conversation

@amarcozzi

Copy link
Copy Markdown
Contributor

Closes #49.

What

Makes attenuate_all cast shadows at the physical solar angle for the voxel
dimensions in env.voxel_dim, instead of assuming cubic voxels.

Why

attenuate_all rotates grid-index coordinates so the sun points straight
down, then floors onto a 1×1 lattice for the shadow sweep. Index space is only
physically proportioned when the voxel is cubic, so voxel_dim never entered the
geometry — it only fed the scalar path length d
(_calculate_d). For any non-cubic voxel
(e.g. a canopy 2 m wide but 1 m tall) the horizontal shadow displacement was
tan(zenith) cells per vertical cell regardless of the true dz/dx ratio, so
shadows landed in the wrong cells.

How

Scale the index coordinates by the voxel aspect ratio, normalized by the
x-resolution (voxel_dim / dx), before rotation. The rotation then acts on
physically-proportioned space, so the shadow angle is correct. Applied to the
leaf, terrain, and sensor rotation inputs; outputs stay in index coordinates.

  • Cubic voxels and voxel_dim=None normalize to (1, 1, 1) → no scaling, no
    change
    .
  • An anisotropic voxel is stretched to physical proportions → correct angle.
  • When dx != dy the shadow angle is still physical; only the sweep's cell
    buckets become non-square (documented in the docstring).

Tests (TDD)

Written before the implementation — test_voxel_dim_sets_shadow_geometry: a
single elevated occluder over two ground sensors, 45° sun in the x–z plane. The
shadowed ground cell must sit at offset z_top · (dz/dx) cells:

  • None, (1,1,1), (2,2,2) → offset z_top (unchanged),
  • (1,1,3), (2,2,6) → offset 3·z_top.

Before the fix the cubic/None cases passed and the anisotropic cases failed
(5 != 9); after, all pass. Full suite: 56 passed — the stored-result
assert_equal tests confirm the cubic/None path is byte-for-byte unchanged.

⚠️ Breaking change

Results change for any non-cubic voxel_dim (they were previously wrong).
Cubic-voxel and voxel_dim=None results are unaffected.

Scope / interaction

Kept to attenuate_all (the path the FastFuels treevox consumer uses);
attenuate_surface is unchanged. Independent of #48/#50 (origin offset) but
they touch the same three rotation lines, so expect a small merge conflict — the
combined form is (coords + origin_offset) * voxel_scale, which is compatible
(the global offset is applied before scaling, preserving tile invariance).

attenuate_all rotates grid-index coordinates so the sun points straight
down, then floors onto a 1x1 lattice for the shadow sweep. Index space is
only physically proportioned when the voxel is cubic, so voxel_dim was
ignored in the geometry (it only fed the path-length scalar d) and
shadows landed at the wrong angle for any non-cubic voxel.

Scale the index coordinates by the voxel aspect ratio, normalized by the
x-resolution, before rotation. Cubic voxels and voxel_dim=None normalize
to (1, 1, 1) and are unchanged; an anisotropic voxel is stretched to
physical proportions so the shadow angle is correct. Applied to the leaf,
terrain, and sensor rotation inputs; outputs stay in index coordinates.

This changes results for any non-cubic voxel_dim (previously wrong).

Closes #49
@amarcozzi

Copy link
Copy Markdown
Contributor Author

Some qualitative evidence of this occuring

image

The test covered aspect 1 (cubic/None) and aspect 3, but not aspect < 1 —
the shipping treevox voxel is 2 m wide and 1 m tall (dz/dx = 1/2), whose
shadow should reach HALF as far as the cubic case. Add a third ground
sensor at that offset and assert 2x2x1 lands there. Verified the new
assertion fails on the pre-fix geometry (it shadowed the cubic cell).
@allisonrenie
allisonrenie merged commit 342a9ae into main Aug 21, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Rotation geometry ignores voxel_dim — shadows are wrong for non-cubic voxels

2 participants