A coloured region of space that tints whatever is seen through it, and the arithmetic a projection draws one with.
volume = Cauldron2D.Projection.Volume.new(
at: {8.5, 6.5},
shape: {:sphere, 1.5, 0.0},
colour: {120, 232, 96},
strength: 0.6
):at is where the volume stands on the ground and :shape what it fills from there,
as Cauldron2D.Projection.Volume.Shapes lists the shapes the engine knows, or
{YourModule, data} for a shape of your own implementing
Cauldron2D.Projection.Volume.Shape. Distances are in tiles. Nothing below the ground
is drawn, whatever the shape says.
:strength is how much of the colour a viewer sees through the thickest part of the
shape, 0.0 to 1.0. :falloff decides what a thinner crossing looks like:
:depth— the colour builds up along the ray, so the volume reads as something you look through: thin at the edges, full in the middle. The default:flat— anything the ray touches at all is tinted by the full strength, which is how an area marked out in whole squares reads:shell— strongest where the ray only grazes the shape, so the surface glows and the middle stays clear
A volume is never fully opaque.
A volume either stands where it was made or follows something that moves. :follows
names what it keeps up with — a term of the caller's choosing, a character, a thrown
light, itself — and anchored/2 moves every such volume to where its subject is now,
turning a cone or a beam to face the way the subject faces and dropping a volume whose
subject is gone. A volume with no :follows is left where it stands. Which effects
follow what, and what may be followed at all, is the game's business; all this knows
is that a subject has a place and may have a heading.
through/4 measures a ray against a volume and mix/3 blends that much colour into a
pixel; the two together tint one pixel:
Volume.mix(pixel, volume, Volume.through(volume, eye, ray, distance_to_surface))A ray is a point and a direction of three numbers, z running up from the ground, and
how far along it the projection can see, or :infinity where nothing stops it. That
limit counts steps of the direction as it is given, so a projection may measure it
however suits it; the length through/4 answers is always in the units the volume
itself is measured in, whatever size the direction is.
eyeline/3 gives the direction the rays of an axonometric projection all run in,
box/2 the rectangle of a picture a volume can reach and reach/1 how far from at
it spreads across the ground.
paint/4 tints a Cauldron2D.Projection.Canvas over a rectangle. It takes a plan
rather than a ray per pixel, because a projection's rays change by the same step from
one pixel to the next: ground_plan/2 describes a projection whose rays start on the
ground and run parallel, eye_plan/3 one whose rays start at the eye and fan out.
Painting is done by Cauldron2D.Projection.Volume.Native where it built, and in
Elixir where it did not or where the shape is a module of the caller's own. A plan
carrying native: false paints in Elixir wherever it is, which is how the two are
checked against each other; they agree to within one level of one channel, where
:math.exp/1 and C's exp round differently.
Summary
Functions
Every volume moved to where the thing it follows is now.
The whole-pixel rectangle {left, top, right, bottom} of a picture the volume can
reach, or nil when none of it is in view.
The world points of the box the volume fits in, above the ground.
A plan for a projection whose rays all start at the eye and fan out.
The direction, towards the viewer, that every ray of an axonometric projection runs in.
A plan for a projection whose rays start on the ground and all run the same way.
Every volume of a list, as new/1 builds one.
pixel with as much of the volume's colour blended in as length inside it calls for,
by its falloff.
A volume from its fields, a map or a keyword list, with :at required.
Tint canvas with volume over box, clipped to the canvas, looking along plan.
How far from at the volume can spread across the ground.
How far a ray travels inside volume before limit, in the units the volume is
measured in.
Types
Functions
@spec anchored([map()], %{ required(term()) => {number(), number()} | {{number(), number()}, number()} }) :: [map()]
Every volume moved to where the thing it follows is now.
subjects maps what a volume's :follows names to where that subject is: a point, or
{point, heading} where the subject faces somewhere and the volume's shape should turn
with it. A volume with no :follows is returned untouched; one whose subject is absent
from subjects is dropped, so an effect outlives nothing it was tied to.
@spec box(t(), (point() -> {number(), number()} | nil)) :: {integer(), integer(), integer(), integer()} | nil
The whole-pixel rectangle {left, top, right, bottom} of a picture the volume can
reach, or nil when none of it is in view.
project maps a world point to its place in the picture, or to nil for a point the
projection cannot show.
The world points of the box the volume fits in, above the ground.
A plan for a projection whose rays all start at the eye and fan out.
along answers the direction a picture pixel looks in, scaled so that one step along
it is one unit of depth, which is what limits counts. limits is one native float per picture pixel, how far that
pixel sees before something opaque stops it, or nil for nothing in the way.
The direction, towards the viewer, that every ray of an axonometric projection runs in.
across and down are the picture vectors a step of one along the world's x and
y moves by, and lift how many pixels up the picture a step of one in z moves.
The three must not be flat against one another.
A plan for a projection whose rays start on the ground and all run the same way.
at answers the world point on the ground under a picture pixel, as a projection's
own unproject does, and direction is the way the rays run, as eyeline/3 gives
it.
Every volume of a list, as new/1 builds one.
pixel with as much of the volume's colour blended in as length inside it calls for,
by its falloff.
A length of nothing leaves the pixel untouched. The pixel's own opacity byte is kept.
A volume from its fields, a map or a keyword list, with :at required.
:shape defaults to a dome of one tile, :colour to white, :strength to
0.5 and :falloff to :depth. Passing a volume back in leaves it
as it is, so a projection may normalise whatever it is handed.
@spec paint( Cauldron2D.Projection.Canvas.t(), t(), {integer(), integer(), integer(), integer()} | nil, plan() ) :: Cauldron2D.Projection.Canvas.t()
Tint canvas with volume over box, clipped to the canvas, looking along plan.
How far from at the volume can spread across the ground.
How far a ray travels inside volume before limit, in the units the volume is
measured in.
The ray starts at origin and runs along direction, and limit is how many steps
of that direction it may take, so a projection whose step is a unit of depth rather
than a unit of distance passes its own depth. Only what lies above the ground, ahead
of the origin and nearer than limit counts; :infinity lets the ray run until it
leaves the shape.