5 Combining Scenes
In 3D space, the terms horizontal and vertical are ambiguous.
When there’s no unique or preferred way to view an object, even the terms top, bottom,
left, right, front and back are ambiguous.
When an object is squashed or rotated, or is irregularly shaped, these terms are ambiguous even with
a preferred orientation.
For example, if we say that the following ellipsoid is leaning to the right, there are at least two
acceptable ways to interpret the term on the left side:
Now suppose we decide that the green arrow points at the “left side,” and that we want to attach the
face of a cube to it.
There are infinitely many ways to do so: one for each point on the face and each angle of rotation.
In general, combining two 3D scenes relationally requires specifying a lot of data.
Pict3D tries to reduce the burden by
For example, the following code defines a basis
, which is an empty group, on the left side
of the ellipsoid p1
, pointing away from the surface.
Here, the surface/normal
function finds a point on the surface of p1
It returns that point and a surface normal
, which is the direction perpendicular to the
surface at that point.
function returns an affine transformation
that represents pointing in the
from position v
creates a Pict3D
out of the result.
We could display left, but it’s more instructive to combine it with the ellipsoid whose
surface it’s on.
(We’ve moved it upward 4 units only to keep the origin’s axes from obscuring the new basis.)
Notice that the blue arrow points outward.
Now we’ll create a cube to attach onto that basis.
(Again, we’ve moved it upward, away from the origin’s axes.)
This time, we used surface
instead of surface/normal
because we know the surface
But we haven’t applied point-at
, we’ve used -z
instead, causing the
blue arrow to point in the direction opposite the surface normal: downward, into the cube.
All we have left is to pin
the scenes together:
Because (pin pict1 path1 pict2 path2) puts pict2 inside of the
group path1, it naturally creates group hierarchies. When this isn’t what you
want, use join instead.
This has applied the transformation necessary to make the basis named 'top
match the basis named 'left
, and combined the transformed p2/top
In other words, it’s rotated and moved p2/top
so that top
’s red arrow is the same as
’s red arrow, top
’s green arrow is the same as left
’s green arrow, and
’s blue arrow is the same as left
’s blue arrow.
(We refer to the group named 'left using the tag path '(left).)
Suppose we want to rotate the cube 30 degrees.
We have a few options.
Recreate p1/left with a rotated left basis, and then recreate p3.
Recreate p2/top with a rotated top basis, and then recreate p3.
Replace the group named 'left in p3 with the same group rotated 30
The first option only requires us to replace (basis 'left (point-at v dv))
in the definition
with (basis 'left (point-at v dv #:angle 30))
The second option requires similar changes to top
But at this point, with p3
already defined, both are more work than the third option, which
is simply this:
This replaces, in p3
, every group p
(rotate-z p 30)
The cube rotates because the group named 'left
isn’t empty any longer: it’s
been filled with a transformed p2/top
It’s instructive to consider what would have happened if we’d had p2/top
’s basis point in the
direction of the surface normal; i.e. if we had used +z
instead of -z
Generally, when we want to pin two Pict3D
instances together using bases, one’s basis should
point inward, and the other’s should point outward.
5.1 Scene Combiners
The type and predicate for names of groups.
is defined as (U Symbol Integer)
Functions that operate on groups do not accept a Tag
value on its own.
They accept a tag path
, which is a list
This allows them to operate on groups within groups.
For example, to remove the contents of the group named 'cannon
within a group named
, we might write
Then, only the poor player will be left without a printer.
To remove the contents of every cannon group regardless of the group that contains it, we
The tag path '()
refers to an entire Pict3D
(group pict name)
creates a group
: a named collection of shapes.
, it simply returns pict
, whose tag path
Otherwise, the new group can be referred to by its tag path (list name)
If pict is a group, group-tag returns its name and group-contents returns
If pict isn’t a group, group-tag returns #f and group-contents
These seemingly strange rules preserve the following properties.
Replaces every group p in pict with the given path with (f p).
If there is more than one group with the given path, set-origin
raises an error.
Does two things:
Transforms pict2 so that its group with path path2 aligns with the group
in pict1 with path path1.
Combines the result with pict1.
is given as an affine transformation
uses them directly instead of looking up a group transformation.
If there is more than one group with path1 in pict1, join raises an error,
and likewise for path2 in pict2.
Recall that the tag path '() represents the entire Pict3D.
Thus, when path2 is '() or is omitted, the origin and coordinate axes are used for
are tag paths
(join pict1 path1 pict2 path2)
is equivalent to
Like (join pict1 path1 pict2 path2)
, but ungroups all groups in pict1
, and ungroups all groups in pict2
that have path path2
Like join, glue accepts affine transformations as well as tag paths.
Similar to join
, but additionally
In code, (pin pict1 path1 pict2 path2)
is equivalent to
Like join, pin accepts an affine transformation for its second argument.
Like (pin pict1 path1 pict2 path2)
, but additionally ungroups all groups in pict1
that have path path1
Use weld instead of pin when you don’t intend to update the group with path
path1 in the result.
For example, use pin to attach a swinging arm to a robot body, and use weld to
place a roof on top of a house.
(Unless you intend to blow up the house later. Then you’ll need to pin the roof.)
Like glue, weld accepts an affine transformation for its second argument.
, and glue
, but if multiple groups with path
exist in pict1
is pinned, welded, joined, or glued to all of
Applies f to every group with the given path in pict, and returns the results in a
The list is in no particular order.
Like (map-group pict name f)
, but f
is additionally supplied the transformation
from world coordinates to group coordinates.
Finds the group within pict with path, and returns the transformation from world
coordinates to that group’s coordinates.
If multiple groups with path exist, find-group-transform raises an error.
This is used in the implementations of functions like join and set-origin.
, but if multiple groups with path
exist, returns a
transformation for each of them.