Intersecting Objects
As discussed in the previous section, LiveGraphics3D employs the very simple "painter's algorithm" to render primitives. Unfortunately, this algorithm will often fail to produce correct occlusions, in particular for intersecting objects. In this section we discuss some typical problems and common solutions.
In order to illustrate the problem for a line intersecting
a polygon, consider a line segment between two user-specified points
{x0,y0,z0}
and {x1,y1,z1}
and a polygon in the plane z = 0
defined by the points
{0,0,0}
, {1,0,0}
, {1,1,0}
,
and {0,1,0}
.
Here is a mathlet that visualizes the scene:
<html><body> <applet archive="live.jar" code="Live.class" width="500" height="300"> <param name="INDEPENDENT_VARIABLES" value="{x0 -> 0.5, y0 -> 0.5, z0 -> -1, x1 -> 0.5, y1 -> 0.5, z0 -> 1}"> <param name="INPUT" value="Graphics3D[{PointSize[0.04], RGBColor[1, 0, 0], Point[{x0, y0, z0}], Point[{x1, y1, z1}], Thickness[0.01], RGBColor[0, 0, 0], Line[{{x0, y0, z0}, {x1, y1, z1}}], RGBColor[0.7, 0.7, 0.7], Polygon[{{0, 0, 0}, {1, 0, 0}, {1, 1, 0}, {0, 1, 0}}] }, (* Done with primitives; now include options *) PlotRange -> {{0,1}, {0,1}, {-1,1}}, Boxed -> False]" > </applet> </body></html>Resulting Applet:
By rotating the scene and/or dragging the points defining the line, you can convince yourself that either the polygon will occlude the line or the line will occlude the polygon. If the line intersects the polygon, this will result in an incorrect image.
One very simple solution to this kind of problem is to only render
the outline of the polygon.
By using four Line
primitives
(instead of one) and also coloring them in the same way as the
user-specified line, almost all incorrect occlusions are avoided:
<html><body> <applet archive="live.jar" code="Live.class" width="500" height="300"> <param name="INDEPENDENT_VARIABLES" value="{x0 -> 0.5, y0 -> 0.5, z0 -> -1, x1 -> 0.5, y1 -> 0.5, z0 -> 1}"> <param name="INPUT" value="Graphics3D[{PointSize[0.04], RGBColor[1, 0, 0], Point[{x0, y0, z0}], Point[{x1, y1, z1}], Thickness[0.01], RGBColor[0, 0, 0], Line[{{x0, y0, z0}, {x1, y1, z1}}], Thickness[0.005], RGBColor[0, 0, 0], Line[{{0, 0, 0}, {1, 0, 0}}], Line[{{1, 0, 0}, {1, 1, 0}}], Line[{{1, 1, 0}, {0, 1, 0}}], Line[{{0, 1, 0}, {0, 0, 0}}] }, (* Done with primitives; now include options *) PlotRange -> {{0,1}, {0,1}, {-1,1}}, Boxed -> False]" > </applet> </body></html>Resulting Applet:
While this approach is often suitable,
a better solution is required in many cases.
In general, most other solutions are based on avoiding
intersections by splitting primitives at intersection points.
In our example, the line segment from
{x0,y0,z0}
to {x1,y1,z1}
should be split into two parts, say from
{x0,y0,z0}
to {x2,y2,z2}
and from
{x2,y2,z2}
to {x1,y1,z1}
.
All we need to do is to compute the intersection point
{x2,y2,z2}
of the line with the plane z=0.
This is rather straightforward and implemented with the
help of dependent variables in the example below.
But wait a moment! What if the line segment does not
intersect the plane z=0 at all, i.e., if z0
and z1
are both either smaller than 0 or greater than 0?
In this case we should actually not split the original
line. There are multiple ways to handle this case.
In the example below, we simply set the coordinates of the intersection point
to {x0,y0,z0}
in case the line does not
intersect the plane at z=0. Thus, the line segment
between
{x0,y0,z0}
to {x2,y2,z2}
will collapse to the single point {x0,y0,z0}
and we don't need to bother about it.
<html><body> <applet archive="live.jar" code="Live.class" width="500" height="300"> <param name="INDEPENDENT_VARIABLES" value="{x0 -> 0.5, y0 -> 0.5, z0 -> -1, x1 -> 0.5, y1 -> 0.5, z0 -> 1}"> <param name="DEPENDENT_VARIABLES" value="{x2 -> If[(z0 < 0 && z1 < 0) || (z0 > 0 && z1 > 0), x0, x0 + (x1 - x0) * (0 - z0) / (z1 - z0)], y2 -> If[(z0 < 0 && z1 < 0) || (z0 > 0 && z1 > 0), y0, y0 + (y1 - y0) * (0 - z0) / (z1 - z0)], z2 -> If[(z0 < 0 && z1 < 0) || (z0 > 0 && z1 > 0), z0, 0]}"> <param name="INPUT" value="Graphics3D[{PointSize[0.04], RGBColor[1, 0, 0], Point[{x0, y0, z0}], Point[{x1, y1, z1}], Thickness[0.01], RGBColor[0, 0, 0], Line[{{x0, y0, z0}, {x2, y2, z2}}], Line[{{x2, y2, z2}, {x1, y1, z1}}], RGBColor[0.7, 0.7, 0.7], Polygon[{{0, 0, 0}, {1, 0, 0}, {1, 1, 0}, {0, 1, 0}}] }, (* Done with primitives; now include options *) PlotRange -> {{0,1}, {0,1}, {-1,1}}, Boxed -> False]" > </applet> </body></html>Resulting Applet:
This approach effectively avoids intersecting objects; however, it does not guarantee correct occlusions: By placing one of the points close to the polygon, you can easily generate an incorrect rendering. However, for many applications this way of splitting lines generates acceptable results.
If you have a closer look at the code above,
you might realize that the computation of x2
and y2
includes a division by the difference
between two user-specified coordinates, which might result
in a division by zero. Do we have to take care of this problem?
And what about square roots of potentially negative numbers?
In general, you don't need to care about these cases: whenever a division by zero occurs or the result of any function is not defined in the real domain, LiveGraphics3D will reject the last user input and revert to the last valid configuration. The only exception is the configuration defined by the initial values of the independent variables: if any expression cannot be evaluated for these values, LiveGraphics3D will reject the input and abort the execution. Note that no user action is involved in this case; thus, it is usually easily checked by starting a mathlet.
If it is not possible (or not worth the effort) to compute the intersection between a line and a surface, you can still try to split the line primitive into a set of smaller line primitives. These are more likely to be sorted correctly. Moreover, the visual error is usually reduced this way.
Apart from the intersection of lines with polygons, polygons intersecting other polygons will also raise occlusion errors. For an example, consider the following mathlet:
<html><body> <applet archive="live.jar" code="Live.class" width="300" height="300"> <param name="INPUT" value="Graphics3D[{ Polygon[{{-1, 0, 1}, {1, 0, 1}, {1, 0, -1}, {-1, 0, -1}}], Polygon[{{0, -1, 1}, {0, 1, 1}, {0, 1, -1}, {0, -1, -1}}] }, Boxed -> False]"> </applet> </body></html>Resulting Applet:
In order to avoid these unpleasant occlusion errors, you will usually have to split the polygons at the line of intersection:
<html><body> <applet archive="live.jar" code="Live.class" width="300" height="300"> <param name="INPUT" value="Graphics3D[{ Polygon[{{-1, 0, 1}, {0, 0, 1}, {0, 0, -1}, {-1, 0, -1}}], Polygon[{{0, 0, 1}, {1, 0, 1}, {1, 0, -1}, {0, 0, -1}}], Polygon[{{0, -1, 1}, {0, 0, 1}, {0, 0, -1}, {0, -1, -1}}], Polygon[{{0, 0, 1}, {0, 1, 1}, {0, 1, -1}, {0, 0, -1}}] }, Boxed -> False]"> </applet> </body></html>Resulting Applet:
Unfortunately, the computation of intersections between polygons tends
to be rather complicated. Thus, we can only recommend this approach
for static objects. Moreover, you should consider computing these
intersections with the help of other programming tools.
If you have access to Mathematica, you could use the free package
LiveGraphics3D.m
which is linked from the documentation
of LiveGraphics3D (Kraus).
This package was employed to split intersecting polygons
for many of the mathlets that are part of the online encyclopedia MathWorld
(Weisstein).
Next Page: Two-Dimensional Applets
Table of Contents
- Introduction
- LiveGraphics3D Overview
- LiveGraphics3D Input
- Parametrized Graphics
- Moving Lines and Polygons
- Including Text
- Labeling Axes and Plots
- Animations
- Occlusions of Objects
- Intersecting Objects
- Two-Dimensional Mathlets
- Stereo Images
- Generating Graphical Input
- Advanced Examples
- Future Directions
- References