Jonathan Rogness is with the Mathematics Department and the IT Center for Educational Programs at the University of Minnesota, where LiveGraphics3D is used in a variety of courses.
Martin Kraus is with the Institute for Visualization and Interactive Systems at the University of Stuttgart. He is the author of LiveGraphics3D.
In this article we describe how to use a Java applet called LiveGraphics3D to speed up the process of creating interactive graphics by removing the need to create a graphics engine in Java, Flash, or other programming languages. Instead, you can use a well-established language to describe the objects on the screen, and LiveGraphics3D handles all aspects of the display. Furthermore, you can define complex manipulations of these objects. This allows users to interact with your graphics in a structured and meaningful way.
Published May, 2006
Copyright © 2006 by Martin Kraus and Jonathan Rogness
Pages 2-10 in the Table of Contents below are intended for general readers. You may exit the article gracefully after reading these topics. Pages 11-17 are advanced topics and are intended primarily for developers.
Jonathan Rogness is with the Mathematics Department and the IT Center for Educational Programs at the University of Minnesota, where LiveGraphics3D is used in a variety of courses.
Martin Kraus is with the Institute for Visualization and Interactive Systems at the University of Stuttgart. He is the author of LiveGraphics3D.
In this article we describe how to use a Java applet called LiveGraphics3D to speed up the process of creating interactive graphics by removing the need to create a graphics engine in Java, Flash, or other programming languages. Instead, you can use a well-established language to describe the objects on the screen, and LiveGraphics3D handles all aspects of the display. Furthermore, you can define complex manipulations of these objects. This allows users to interact with your graphics in a structured and meaningful way.
Published May, 2006
Copyright © 2006 by Martin Kraus and Jonathan Rogness
Pages 2-10 in the Table of Contents below are intended for general readers. You may exit the article gracefully after reading these topics. Pages 11-17 are advanced topics and are intended primarily for developers.
Suppose you have a great idea for an interactive picture or animation which will help students learn a crucial concept in class. How can you take that vision and turn it into a reality? You no doubt have the analytic geometry skills to describe the objects which you want the computer to show, but writing a computer program to display this graphical output is not feasible without a fair amount of programming experience. This is particularly true in the case of three-dimensional graphics, where the desire to rotate objects can make the program even more complex; for an overview of these issues, see "Writing Mathlets II" and "Writing Mathlets III" (Leathrum, 2002, 2005) in this journal.
In this article we describe how to use a Java applet called LiveGraphics3D to speed up the process by removing the need to create a graphics engine in Java, Flash, or other programming languages. Instead, you can use a well-established language to describe the objects on the screen, and LiveGraphics3D handles all aspects of the display. Furthermore, you can define complex manipulations of these objects. This allows users to interact with your graphics in a structured and meaningful way.
As an example consider the following mathlet, which shows the graph of the function
z = f(x,y) = 2 y e^{−x2−y2}
over the domain [-1, 3] × [-2, 1]. The green and blue curves represent the cross sections x = a and y = b. The red point has coordinates (a, b, f(a, b)), and we have also displayed a piece of the plane tangent to the surface at this point. This is a variant of a mathlet used at the University of Minnesota to show students the geometric interpretation of partial derivatives, but we are not including it here to advocate its use; rather, it is a relatively simple example which allows us to demonstrate most of LiveGraphics3D's features.
Try experimenting with the following controls in the mathlet; here "click" always refers to pressing the left mouse button.
Writing a program from scratch to display this image and implement the user interface would be time consuming, but with a little practice you can routinely construct similar mathlets in a matter of minutes using LiveGraphics3D. The process does not require any Java programming; the different behaviors are controlled by parameters in your web page. In this sense LiveGraphics3D has some of the properties of a "lite applet," as described in (Wattenberg, et al.).
The rest of this article is organized into two sections. The first section starts with an overview of LiveGraphics3D's features and compares it to alternatives such as JavaView. We then give you a basic summary of how to create your own mathlets by constructing the example above, step by step. We also explain how to create animations and two-dimensional mathlets using LiveGraphics3D. Finally, this section includes a few advanced examples to demonstrate the power and versatility of the applet. The second section of the article is essentially an appendix with advanced information for more experienced users; topics range from including text in your mathlet to generating input for LiveGraphics3D using Java or other languages.
Throughout the article we will assume you are familiar with placing Java applets on web pages using the <applet>
and <param/>
HTML tags. If needed, you can find a description of this process in the Java Tutorial's "Using the APPLET tag" page (Sun.com). We will also assume you are comfortable with very basic computer statements which are common to most popular programming languages, such as xmax=2
or dx=(xmax-xmin)/n
, as well as the concept of a for
-loop. The article includes all of the necessary HTML and computer code to create the first few examples; in the later examples we will leave some of the details to you.
The graphical input to LiveGraphics3D is described using Mathematica's syntax for graphics objects. It should be stressed that Mathematica is not needed to either create or view mathlets with LiveGraphics3D. The input can always be written by hand using a standard text editor, although using Mathematica can make the development process somewhat easier for large projects. The upshot of this is that you do not need a copy of Mathematica to read this article or use LiveGraphics3D in your courses, but you do need to know something about Mathematica's syntax. We have prepared a short overview of the necessary commands for people who are unfamiliar with the Mathematica language.
The choice of a computer algebra system (CAS) is a personal one and JOMA does not endorse any one particular system over another. However, the computer code in this article is presented in the Mathematica language. This seems to be the most natural choice because some knowledge of the syntax is already necessary to work with LiveGraphics3D. We will not use anything beyond the basic Mathematica functions and loops described in the overview mentioned above; the code also contains a number of comments enclosed by the symbols (* and *) for those of you who are unfamiliar with the language. Later on we will discuss ways to avoid the use of Mathematica entirely by generating the input for LiveGraphics3D using a different CAS or programming language.
LiveGraphics3D's license permits free use for non-commercial purposes, and it runs on any Java 1.1-enabled browser. You may need to install the Java plug-in for your browser.
Bugs in specific versions of certain browsers can occasionally prevent LiveGraphics3D from initializing correctly. Often LiveGraphics3D can correct this problem on its own after a short delay; if not, reloading the page in your browser should fix everything.
Suppose a group of children want to play with plastic airplanes. The first child pulls out a chemistry set, creates a few lumps of plastic, molds them into carefully shaped pieces, and assembles them into an airplane. She has total control over the size and shape of her resulting toy. The next child doesn't have the necessary chemistry skills, so he decides to build an airplane using a set of plastic construction blocks. While this is much faster, he loses some control over the final appearance; for example, the blocks make it impossible to have any rounded edges. The third child simply takes a set of blocks to her parents, along with a description of what she would like them to build for her. This last child has lost even more control over the final product, and yet she will end up with a perfectly good airplane.
The sudden appearance of children and airplanes in this article serves to illustrate a choice you must make when creating a graphical mathlet: how much of the low level computer code will you write? This is an important decision which can affect everything from your user interface to the types of graphics you can display. You must balance these considerations with other issues, such as your programming skill and the amount of time you can devote to your project. Roughly speaking, you have three options, similar to the children above.
At the bottom of the hierarchy, you might decide to write the entire mathlet from scratch. This gives you total control over all aspects of the mathlet; it also burdens you with total responsibility for implementing the user interface and graphics display engine. Needless to say, this option is not realistic unless you are an experienced computer programmer.
Next, you could decide to build your mathlet using a pre-existing library of components provided by packages like JavaView (Polthier et al.) or the Java 1.1 3D Renderer (Perlin). JavaView, for example, provides a large number of Java classes, ranging from configurable sliders to "displays" which let the user rotate three-dimensional objects with various settings for lighting, shading, and transparency. You are locked in to the stylistic choices the JavaView authors made when designing their components, but you can create much more elaborate user interfaces and pictures using JavaView than you could ever accomplish with LiveGraphics3D. Correspondingly, you will still spend significant time writing and compiling Java code in order to assemble the various components together.
The third option is to use a pre-existing applet such as LiveGraphics3D; you supply certain parameters to describe what you want on the screen, and the applet handles everything else. For example, consider this partial list of features implemented in LiveGraphics3D:
The downside to using LiveGraphics3D is that all aspects of the display and user interface are fixed; you cannot change, say, the method for zooming in and out. In many cases, the ability to create mathlets quickly without worrying about the underlying code far outweights the loss of freedom, but in the end this is a personal choice which depends on your needs and resources.
We begin our tutorial with a basic example in which only one parameter, INPUT
, is passed to the applet. The value of this parameter is a string which describes the graphics to be displayed; in this case we have a line, a large red point and a smaller blue point. (Recall that you can review Mathematica's syntax for describing graphics objects.)
To create this applet on your computer system, download the file live.jar
from the "LiveGraphics3D Homepage" (Kraus) or from this article's list of accompanying files. In the same directory, create an HTML file with the following code. If you open the file in a web browser, you should see the same applet as the one displayed below.
Resulting Applet:
You should be able to rotate, spin, and change the zoom level in the image using the same controls as in the introduction. In this particular applet neither point can be dragged.
Equipped with the basic knowledge of how to embed LiveGraphics3D into a web page, we can start to construct the example from the introduction. First we'll create the wireframe mesh which represents a portion of the graph of
z = f(x, y) = 2 y e^{−x2−y2}
over the rectangle −1 ≤ x ≤ 3, −2 ≤ y ≤ 1
We used a 20 by 20 grid, which can be constructed using the following Mathematica commands. (For those who are unfamiliar with Mathematica and have not yet read the syntax overview, Table[]
is a variant of a for
-loop. Again, this could be mimicked using loops with another computer language.)
f[x_,y_]= 2y*Exp[-x^2-y^2]; xmin = -1; xmax = 3; ymin = -2; ymax = 1; n = 20; dx = (xmax-xmin)/n; dy = (ymax-ymin)/n; mesh = {GrayLevel[0.7], (* Make the line segments gray, not black *) (* This pair of nested Tables creates the cross sections y=j *) Table[ (* Let the y-values vary *) Table[ (* y fixed, now let x-values vary *) Line[{{i, j, f[i, j]}, {i + dx, j, f[i + dx, j]}}], {i, xmin, xmax - dx/2, dx}], {j, ymin, ymax, dy}], (* This pair of nested Tables creates the cross sections x=i *) Table[ (* Let the x-values vary *) Table[ (* x fixed, now let the y-values vary *) Line[{{i, j, f[i, j]}, {i, j + dy, f[i, j + dy]}}], {j, ymin, ymax - dy/2, dy}], {i, xmin, xmax, dx}] };
The variable mesh
now contains a long list of line segments which represent the wireframe mesh. Writing this list out would require about 50 kilobytes of text, which is more than we care to display in this article. Fortunately the input for LiveGraphics3D can be placed in a separate file. Within Mathematica, the easiest way to create the file is using the following function, which is included in the accompanying Mathematica notebook. To save space, it truncates decimal numbers before writing the object to the file mesh.lg3d
in the current working directory.
example = Graphics3D[mesh, Boxed -> False]; WriteLiveForm["mesh.lg3d", example]
To show the mesh in LiveGraphics3D, we use the INPUT_FILE
parameter instead of INPUT
:
The use of INPUT
or INPUT_FILE
is largely a personal preference for small amounts of data. For a large scene, an advantage of the INPUT_FILE
method is that the file can be stored into a ZIP archive; because the input consists of fairly repetitive text, the compression rate is typically very high, which results in much shorter download times for the input files. Another advantage is that certain characters can appear in an input file, but not in the value of the INPUT
parameter. For details, see the documentation at the LiveGraphics3D homepage.
So far all of our graphics primitives have had fixed, precomputed numbers as coordinates. One of LiveGraphics3D's most powerful features is its ability to display primitives whose coordinates are not fixed, but depend on independent variables which can be adjusted by the user. These objects are referred to as parametrized graphics.
As a basic example, consider the primitive Point[{x,0,0}]
and suppose the initial value of x is 0. LiveGraphics3D would display this as a point at the three-dimensional origin, but you could click on the point and drag it to any location on the x-axis. As the point is dragged, the value of x would be adjusted accordingly. Any other primitives -- points, lines, polygons or text -- whose coordinates depend on x would be redrawn.
Now let's use this idea to continue building our example. First we add a point to our input file as follows.
(* "mesh" is the same as in the previous example *)
point = {RGBColor[1, 0, 0], PointSize[0.02], Point[{x, y, z}]};
example = Graphics3D[{mesh, point}, Boxed -> False];
WriteLiveForm["meshPoint.lg3d", example]
Because the coordinates in Point[{x,y,z}]
are not fixed numbers, we need to tell LiveGraphics3D how to compute them at runtime. There is a fundamental difference here between z
and the other two variables. x
and y
are independent variables; after specifying their initial values, we want to be able to change them by moving the point. Conversely, z
is a dependent variable whose value should always be calculated using the formula for f(x, y).
This information is passed to the applet using the INDEPENDENT_VARIABLES and DEPENDENT_VARIABLES parameters. The value for either of these parameters is a list of rules LiveGraphics3D can use to assign values to variables. In the case of independent variables each rule just gives the initial value, which can later be changed by the user. The rules for dependent variables describe how to compute the values using any other previously mentioned variables.
Resulting applet:
Click and drag the red point to see how it moves along the surface. The effect of defining z as a dependent variable is quite noticeable in the following sense: if you view the surface from above, the motion of the point is quite natural; motions of the mouse correspond directly to changes in x and y. If you view the surface from the side, however, the point is very tricky to control.
By now you may have noticed that you can drag the point off of the mesh. This undesirable behavior can be avoided with a feature that is tricky and counter-intuitive, yet highly useful: rules for independent variables can also appear in the value of the DEPENDENT_VARIABLES
parameter. This is demonstrated in the following HTML code. We use the same input file as above, together with a new set of rules for dependent variables which restrict the values of x and y so that the point stays on the mesh.
To understand how this process works, suppose you move the point with the mouse. Internally, LiveGraphics3D will recognize that an independent variable has been changed. Whenever this happens, the rules in the DEPENDENT_VARIABLES
parameter will be evaluated in order. Each of the If
statements below restricts the value of a variable; for example, the statement x -> If[x < -1, -1, x]
sets x=-1
if you have dragged it to a value less than -1, and leaves it unchanged otherwise. The new value of z is only computed after the rules for x and y have been processed, ensuring that the point will be on our mesh.
Resulting applet:
Before continuing, we should mention the following issues related to this example.
Point
can only be dragged if at least one of its coordinates is an independent variable; if we replaced Point[{x,y,z}]
in the input file with Point[{x/2,y/2,z}]
-- or even Point[{1*x,1*y,z}]
-- LiveGraphics3D would not allow us to click on it. (In the latter case, 1*x
and 1*y
are certainly equivalent to x
andy
, but LiveGraphics3D is not equipped to make this simplification.)Point
is the only primitive which can be moved this way. On the next two pages we'll discuss how to make other objects move.Point[{x,y,z}]
with Point[{x,y,2*y*Exp[-x^2-y^2]}]
would result in the same display, and the rule z -> 2y*Exp[-x^2-y^2]
would no longer be needed.If
statements such as x -> If[x < -1, -1, x]
, x -> If[x > 3, 3, x]
into the more compact form x -> If[x < -1, -1, If[x > 3, 3, x]]
. This expression is also equivalent to x -> Max[ -1, Min[x, 3] ].
In this section we'll finish constructing the mathlet from the first page of the article. The only missing pieces are the colored cross sections and the tangent plane which follow the point as it moves around the surface. These are created using Line
and Polygon
primitives whose coordinates contain the variables x and y. Whenever the point is moved, the values of these variables are adjusted and the primitives are redrawn.
In terms of learning how to use LiveGraphics3D, there is very little new material in this section. The only changes in the HTML code are in the INPUT_FILE
parameter, and the rules for independent and dependent variables remain the same. The sole lesson here is how to create a list of graphics primitives whose coordinates depend on certain variables. For example, consider the following Mathematica code to construct the cross sections.
(* "mesh" and "point" are the same as in the previous example, *)
(* as are "f[x_,y_]," "xmin," "xmax," "dx" and so on. *)
xSection = {RGBColor[0, 1, 0], Thickness[0.005],
Table[Line[{{x, j, f[x, j]},
{x, j + dy, f[x, j + dy]}}],
{j, ymin, ymax - dy, dy}]};
ySection = {RGBColor[0, 0, 1], Thickness[0.005],
Table[Line[{{i, y, f[i, y]},
{i + dx, y, f[i + dx, y]}}],
{i, xmin, xmax - dx, dx}]};
example = Graphics3D[{mesh, point, xSection, ySection}, Boxed -> False];
WriteLiveForm["meshSections.lg3d", example]
When reading the commands here, keep in mind that Mathematica will replace variables like i
and dx
with their numeric value; in contrast, x
and y
have no numeric value and are therefore left as symbols. For instance, the expression
Line[{{i, y, f[i, y]}, {i + dx, y, f[i + dx, y]}}]
evaluates to the following primitive if i = 1
and dx = 0.2
:
Line[{{1, y, 2 y Exp[-1-y^2]}, {1.2, y, 2 y Exp[-1.44-y^2]}}]
At runtime, LiveGraphics3D will replace any instances of the independent variables x and y with their current values; when the user moves the primitive Point[{x,y,z}]
, the cross sections will automatically follow.
If you do not use Mathematica to generate the input for LiveGraphics3D, you will have to construct loops with output that mimics this behavior with numbers and symbols. (We'll discuss this later in the section on using LiveGraphics3D without Mathematica.) Whatever your method, once the input file is created, the inclusion of the applet into a web page is straightforward. As mentioned above, we only need to change the INPUT_FILE
parameter.
Resulting applet:
We are nearly finished constructing the mathlet; all that remains is to add the tangent plane. This requires a bit more analytic geometry than the previous steps, but nothing harder than a typical multivariable calculus homework problem. Specifically, we need to find a parametric equation for the plane tangent to the surface at the point (x, y, f(x, y)). As readers are well aware, one possibility is
p(s, t) = (x, y, f(x, y)) + s (1, 0, f_{x}(x, y)) + t (0, 1, f_{y}(x, y)).
Once we have determined the tangent plane, we can use polygons to graph a portion of it. Our commands to accomplish these steps appear below, with plenty of comments for people who are unfamiliar with Mathematica. Of course, there are many ways to find an equation for the tangent plane, and your approach may vary.
(* "mesh," "point," "xSection" and "ySection are *) (* the same as in the previous example, as are *) (* "xmin," "xmax," "dx" and so on. *) (* Create the tangent plane at the point {x, y, f[x, y]} *) (* First use the partial derivatives to find two vectors *) (* a and b for the parametrization *) fx[x_, y_] = D[f[x, y], x]; (* Differentiate with respect to x *) fy[x_, y_] = D[f[x, y], y]; (* Differentiate with respect to y *) (* Here is the parametric equation for the plane tangent *) (* to the surface at the point {x,y,f[x,y]}. Recall that *) (* the value of z=f[x,y] is automatically calculated by *) (* LiveGraphics3D. *) plane[s_, t_] = {x, y, z} + s*{1, 0, fx[x, y]} + t*{0, 1, fy[x, y]}; (* Let s and t range from -1/2 to 1/2 *) smin = -1/2; smax = 1/2; tmin = -1/2; tmax = 1/2; (* We'll use a 10x10 grid for the parametrized plane *) n = 10; ds = (smax - smin)/n; dt = (tmax - tmin)/n; (* Use a Table to generate the list of 100 polygons *) tanplane = {RGBColor[0.8, 0.8, 0.8], (* Make the polygons Gray *) Table[Polygon[{plane[i, j], (* 4 vertices for each *) plane[i + ds, j], plane[i + ds, j + dt], plane[i, j + dt]}], {i, smin, smax - ds/2, ds}, {j, tmin, tmax - dt/2, dt}]}; example = Graphics3D[ {mesh, point, xSection, ySection, tanplane}, Lighting->False, (* Use RGBColors for tanplane *) Boxed -> False]; WriteLiveForm["meshPlane.lg3d", example]
Our mathlet is now complete, and can be included on a web page using the following HTML code.
Resulting applet:
Parametrized graphics can be very useful, but at times you might want certain graphics primitives to move in a prearranged fashion. LiveGraphics3D facilitates this by providing another format for its input. Instead of
Graphics3D[ <em>primitives</em>, <em>options</em> ]
we use the form
Animate[ Graphics3D[ <em>primitives</em>, <em>options</em> ], {t, <em>tmin</em>, <em>tmax</em>, <em>tstep</em>} ]
This is demonstrated in the following applet, which shows a very simple model of a solar system. The three points in the applet are located at {x0,y0,z0}
, {x1,y1,z1}
and {x2,y2,z2}
; these coordinates are automatically adjusted by LiveGraphics3D to create an animation. Look at the parameters for the independent and dependent variables to see if you can predict what will happen as t
increases from 0 to 2π
; move the mouse pointer over the applet to start the animation. In some browsers it may be necessary to click on the applet before the motion begins.
Resulting Applet:
As you can verify, LiveGraphics3D still allows you to rotate or zoom in and out while the animation is running. It is also possible to combine animations and parametrized graphics. Notice that x0
, y0
and z0
are independent variables in this example. If you double-click the applet to stop the animation, you can drag the orange point {x0,y0,z0}
anywhere in the bounding box; double-click the applet again to restart the system in its new location. If you wish to move through an animation frame-by-frame, click the picture with the right mouse button and drag the mouse horizontally. (In MacOS using a mouse with a single button, you can step through the animation frame-by-frame by holding down the Command or Apple key, clicking with the single button, and dragging horizontally.)
Although LiveGraphics3D was designed to display three-dimensional graphics, you can use it to display two-dimensional images as well. In principle, all you have to do is specify your two-dimensional graphics as a special case of three-dimensional graphics by setting all z-coordinates to 0. However, there are some additional issues that require special attention.
As an example, we will create a two-dimensional analog of the example in the introduction. The following code will show the tangent line at a point on the graph of f(x)=x(x<sup>2</sup>-1)
. We'll use an independent variable as one of the point's coordinates so that you can move it along the graph.
(* Function Definition *) f[x_] = x(x^2 - 1); (* A pair of thick black lines for the axes *) axes = {Thickness[0.015], Line[{{-2, 0, 0}, {2, 0, 0}}], Line[{{0, -2, 0}, {0, 2, 0}}]}; (* Graph f(x) with blue line segments. Coordinates *) (* on the graph have the form {x, f[x], 0} *) curve = {RGBColor[0, 0, 1], Thickness[0.01], Line[ Table[{x, f[x], 0}, {x, -1.5, 1.5, 0.1}] ]}; (* Large red point at {a, f[a], 0} *) point = {RGBColor[1, 0, 0], PointSize[0.03], Point[{a, f[a], 0}]}; (* The linearization of f(x) at x=a is given by *) (* La(x) = f(a) + f'(a)(x-a); plot this on the *) (* interval [-2,2] with a thin gray line. *) La[x_]=(a^3-a) + (3a^2-1)(x-a); tanline = {RGBColor[.5, .5, .5], Thickness[0.005], Line[{ {-2, La[-2], 0}, {2, La[2], 0} }]}; example = Graphics3D[{curve, axes, point, tanline}]; WriteLive["tangentLine3D.lg3d", example];
In the HTML code, we restrict the independent variable a to stay on the graph.
Resulting Applet:
Needless to say, this first attempt is not very effective. You can drag the point and watch the tangent line move, but the overall effect is not two-dimensional. We have to address several points.
First and foremost, because our two-dimensional graphics are in the xy-plane, the best point of view is on the z-axis. This is specified with the option ViewPoint -> {0, 0, 1000}
. The length of this vector determines the distance of a virtual camera to the center of the bounding box specified by PlotRange
. Specifying a large vector minimizes the effects of the perspective projection, resulting in a nearly orthogonal projection. (For a typical perspective projection the length should be between 3 and 4.) In order to ensure that the y-axis is pointing upwards we also specify ViewVertical -> {0, 1, 0}
; the length of this vector is unimportant. With these settings, the x-axis has to point to the right since LiveGraphics3D uses a right-handed coordinate system (and we have specified a point of view on the positive z-axis).
Second, in a two-dimensional example we have no need for the three-dimensional bounding box. We can remove it with the option Boxed -> False
.
Finally, the above applet lets you rotate the picture. For two-dimensional graphics, however, general three-dimensional rotations are pointless. We can disable them by setting the applet parameter MOUSE_DRAG_ACTION
to NONE
.
The next version of the mathlet includes all of these enhancements. We've also adjusted the PlotRange
and tweaked the MAGNIFICATION
level so that the image fills all of the available space.
With these enhancements, our example takes this form:
(* Preceding commands are unchanged *)
example = Graphics3D[
{curve, axes, point, tanline},
ViewPoint -> {0, 0, 1000},
ViewVertical -> {0, 1, 0},
PlotRange -> {{-1.5, 1.5}, {-1.5, 1.5}, {-1, 1}},
Boxed -> False];
WriteLive["tangentLine.lg3d", example];
Resulting Applet:
This second version is much better than our initial attempt, but there are still more issues involved in using LiveGraphics3D for two-dimensional mathlets. In this particular case, for example, it doesn't much matter if the red point is drawn above or below the blue curve; in more complicated mathlets with many objects, it can be crucial to make sure one object isn't covered up by another. If you need to worry about issues like these, read the "Advanced Features" section of this article, including the page on Advanced Two-Dimensional Mathlets.
We have mentioned numerous times that Mathematica is not necessary to develop mathlets with LiveGraphics3D, and in the advanced section of this article we'll discuss some of the other approaches. We now wish to demonstrate the benefits of using Mathematica if it is available to you. The analytic geometry in complicated mathlets can quickly lead to very messy calculations with dozens of variables, multiple derivatives, cross products, and more. Human error -- not to mention boredom -- inevitably creeps in when doing these computations by hand, but they are child's play for any computer algebra system (CAS). In our experience the use of a CAS is an invaluable timesaver when creating input files, particularly those with parametrized graphics, allowing us to create quite complex mathlets with relatively few lines of code. Because LiveGraphics3D uses a subset of Mathematica's syntax, it is natural that Mathematica lends itself to this development process more than any other CAS.
In this section we will show some of the mathlets we have created this way. We will not discuss the code to produce these mathlets in detail since it requires more knowledge about Mathematica's programming language than we covered in this article; however, the accompanying notebook to this article contains commented Mathematica code for all the examples. Interested readers are encouraged to have a look at them using either Mathematica or the free MathReader tool (Wolfram.com).
Our first example illustrates the geometric construction of the Simson line. The Simson line is the line containing the feet of the perpendiculars from an arbitrary point on the circumcircle of a triangle to the sides or their extensions of the triangle; see MathWorld's description of "Simson Line" (Weisstein) for more information. Our mathlet lets the user define a triangle by its three vertices and automatically constructs the triangle's circumcircle. This is easily achieved by parametrizing the circumcircle by the coordinates of the triangle's vertices. Additionally, a fourth user-specified point is defined, which the mathlet projects to the nearest point on the circumcircle. Parametrized by the coordinates of these four points, the rest of the graphics specifies the perpendiculars from the point on the circumcircle to the triangle's sides, the corresponding feet, and the line connecting two of them.
As you can verify by dragging any of the four red points, the three feet are always on the constructed line, which is in fact the Simson line.
Our second example employs an animation to illustrate the definition of a spirograph, which is the trace generated by a fixed point on a circle rolling inside a fixed circle; further details can be found in MathWorld's "Spirograph" entry (Weisstein). The animation starts as soon as the mouse pointer is over the mathlet. (Some web browsers require one additional click on the mathlet.) The animation is stopped (and restarted) by double-clicking. Remember that you have to stop the animation to drag any of the three red points specifying the radii of the two circles and the distance of the traced point from the center of the rolling circle.
As you experiment with this mathlet, you might realize that it actually restricts the ratio of the radii of the two circles to certain rational numbers to guarantee a closed trace. You might also realize that the mathlet allows you to choose a greater radius for the rolling circle than for the fixed circle. Strictly speaking, this case is not covered by the definition of a spirograph given above because the rolling circle is no longer inside the fixed circle. The mathematics of the two cases is, however, almost identical apart from an interesting sign change of the time parameter, which is seldom discussed in the literature because it doesn't affect the geometry of the curve; however, for an animation this sign change is crucial.
The next two three-dimensional examples are slight adaptations of two of the over sixty mathlets from an ongoing project (Kraus) to provide interactive versions of figures featured in a book on computer-aided geometric design by Gerald Farin (Farin). These mathlets are employed in a course on geometric modelling at the University of Stuttgart. The first mathlet uses a cubic Bézier curve specified by four user-defined control vertices and one additional user-defined point specifying a curve parameter t. Using these parameters, the applet displays the Frenet frame. It also shows additional vectors corresponding to the derivatives of the axes of the Frenet frame to illustrate the Frenet-Serret formulas. (Compare with Figure 10.3 in (Farin).)
The second example illustrates the osculating circle of a curve on a surface. (Compare with Figure 19.4 in (Farin).) Three user-defined points specify a quadratic Bézier curve in the uv parameter space of a quadratic Bézier patch specified by nine user-defined points. Parametrized by this user input, the mathlet illustrates the osculating circle (corresponding to the actual curvature of the curve in one point) and the osculating circle of maximum radius for all curves with the same tangent direction in the same point (corresponding to the surface's normal curvature in this direction). By manipulating the quadratic Bézier curve in parameter space of the patch, one can show that the normal curvature oscillates between two extremums (the principal curvatures of the surface) as the tangent direction is rotated.
One of the features of LiveGraphics3D that has not yet been mentioned is the ability to "strip away" objects in the scene. This is accomplished by dragging downward while holding down the right mouse button. In this mathlet, you can remove the two osculating circles this way in order to explain the construction first. The osculating circles may be added again by dragging upward with the right mouse button pressed.
At this point we hope you have learned the basics of using LiveGraphics3D to create mathlets without any low-level computer programming. You should be aware that we have only covered the essential concepts and features of LiveGraphics3D. We have made no attempt to discuss every parameter of the applet, nor have we tried to describe the complete syntax of the underlying graphics format. A more complete discussion of these topics may be found in the documentation at the LiveGraphics3D homepage (Kraus) and in the second, more advanced section of this article; see the table of contents below. If you are contemplating using LiveGraphics3D in your courses, you might also be interested in what the future holds for LiveGraphics3D.
LiveGraphics3D allows you to create and display mathlets with nothing more than a text editor and a web browser supporting Java 1.1. While Mathematica is not required to create, display, or interact with a mathlet, the use of a CAS such as Mathematica to generate interactive, three-dimensional illustrations for online mathematics is extremely helpful. Therefore, one of the directions for future work is certainly to extend LiveGraphics3D to support other computer algebra systems such as Maple, Mathcad, or MuPAD.
Further possibilities for future extensions are support for complex numbers as well as vector and matrix variables. Moreover, control structures such as loops and functions could be implemented in the future. Furthermore, an implementation as a J# browser control and an improved implementation of the graphics rendering exploiting the features of Java 1.4 will be considered. Beyond a Java-based implementation, one might also think about alternative implementations based on other web standards, such as the XML-based X3D. However, the wide support for Java applets in today's web browsers proves that the original decision to implement LiveGraphics3D in Java (made about eight years ago) was extremely fortunate.
It is impossible to tell what the Internet and online interactive mathematical visualizations will look like in the future. Nonetheless, the steadily increasing number of applications and enthusiastic reactions of users of LiveGraphics3D certainly let us hope that some of the concepts and ideas discussed in this article will be part of it in one form or another. We wish you luck in constructing mathlets for your own use, and look forward to seeing your creations online.
This page finishes the general portion of our article. From here, you may go to the references or to the advanced topics.
In a complicated mathlet, you might want to include text labels for certain points, curves, or other objects. We will illustrate the process here by adding text to the mathlet from the introduction. You may wish to review the construction of this mathlet in the first few pages of the article. Since we will only add text primitives to the scene, most of the code from those pages can be reused. Only the last two commands need to be replaced.
With LiveGraphics3D you can label the whole plot and individual axes, and you can put text labels at arbitrary three-dimensional points in your scene. For example, in order to label the blue and green sections with labels x = const.
and y = const.
, respectively, we only need to insert two Text
primitives. The arguments of each Text
primitive specify the text to be displayed, a three-dimensional point determining the position of the label, and (optionally) a two-dimensional vector specifying the horizontal and vertical alignment with respect to the projected three-dimensional position. The default value for this 2D vector is {0,0}
, specifying that the text is centered horizontally and vertically. For the first label we use {1,1}
to align the right and top edges of the text with the projected three-dimensional point, while the left and top edges of the second label are aligned at its three-dimensional location by specifying {-1,1}
.
(* all but the last two commands are the same as in the earlier example *)
labels = {Text["x = const.", {x, -2, 0}, {1,1}],
Text["y = const.", {3, y, 0}, {-1,1}]};
example = Graphics3D[
{mesh, point, xSection, ySection, tanplane, labels},
Boxed -> False, Lighting -> False]
WriteLiveForm["meshPlaneText1.lg3d", example]
Resulting applet:
Note that the labels move with the cross sections as you drag the red point because the variables x
and y
appear in the specification of their position. However, this was only the first step. There are actually many ways to format text in LiveGraphics3D. First of all, we can change the font and its size by wrapping the labels in a formatting function named StyleForm
. Here is an alternative assignment for labels
, which modifies the font and its size:
labels = {Text[StyleForm["x = const.", FontFamily -> "Times", FontSize -> 20], {x, -2, 0}, {1,1}], Text[StyleForm["y = const.", FontFamily -> "Times", FontSize -> 20], {3, y, 0}, {-1,1}]};
Most systems support the fonts "Times", "Courier", and "Helvetica"; however, not all special characters are supported in all fonts nor by all systems. In our experience, "Times" usually offers the largest set of special characters. These characters can be entered with their hexadecimal unicode; for example, "\:03b1"
represents a lower-case Greek alpha. Many special characters, in particular Greek characters, may be entered by names such as "\[Alpha]"
for the lower-case alpha or "\[CapitalAlpha]"
for the upper-case alpha. A list of all named characters recognized by LiveGraphics3D is available in the reference guide for Mathematica 3.0 (Wolfram, 1996).
In order to modify the style of parts of a text label, you can use StringForm
. The first argument of StringForm
is a formatting string, which contains placeholders (`1`
, `2`
, etc.) for the following arguments. Here is an example, which also shows how to specify symbols in italics and in bold letters:
labels = {Text[StyleForm[StringForm["`1` = const.", StyleForm["x", FontSlant -> "Italic"]], FontFamily -> "Times", FontSize -> 20], {x, -2, 0}, {1,1}], Text[StyleForm[StringForm["`1` = const.", StyleForm["y", FontWeight -> "Bold"]], FontFamily -> "Times", FontSize -> 20], {3, y, 0}, {-1,1}]};
Resulting applet:
Apart from static labels, we can also include the value of any variable in a text label. For example, we can replace the string "const."
by the actual value of x
and y
. The next example shows two slight variations: The label for x
emphasizes the difference between the text label x
specified with "x"
and the value of x
specified with x
:
labels = {Text[StyleForm[StringForm["`1` = `2`", "x", x], FontFamily -> "Times", FontSize -> 20], {x, -2, 0}, {1,1}], Text[StyleForm[StringForm["y = `1`", y], FontFamily -> "Times", FontSize -> 20], {3, y, 0}, {-1,1}]};
Resulting applet:
As you drag the red point in this example, the new numeric values of x
and y
will be displayed by the two text primitives.
To display a changing numeric value in LiveGraphics3D, the text label must refer to a specific variable. For technical reasons, it is not possible to include arbitrary mathematical expressions within a label; a primitive such as Text[x+y, {0,0,0}]
will result in an error. This is not a serious limitation, however, since we can always define a dependent variable for any mathematical expression and then include the name of this dependent variable in a text label instead of the expression.
For example, let us display the values of x
and y
with only two decimal digits. The most straightforward way to accomplish this is to display Round[100*x]/100
and Round[100*y]/100
instead of x
and y
, respectively. Since we cannot put these expressions into a Text
primitive, we have to define new dependent variables, say xDisplay
and yDisplay
. Thus, the complete <APPLET>
tag becomes:
Note the two new variables xDisplay
and yDisplay
in the list for dependent variables. These variables also have to appear in the definition of labels
. There's one additional change in the following code. You might have noticed above that the labels tend to jump around as the lengths of the displayed numbers change. This is very distracting, and can be avoided by using two text primitives for each label. In the following definition, the string "x ="
is displayed just below and to the left of the point {x, -2.5, 0}
, while the value of xDisplay
is shown just below and to the right of that point. A similar strategy is used for the other label.
labels = { Text[StyleForm["x = ", FontFamily -> "Times", FontSize -> 20], {x, -2.5, 0}, {1, 1}], Text[StyleForm[xDisplay, FontFamily -> "Times", FontSize -> 20], {x, -2.5, 0}, {-1, 1}], Text[StyleForm[["y = ", FontFamily -> "Times", FontSize -> 20], {3.5, y, 0}, {1, 1}], Text[StyleForm[yDisplay, FontFamily -> "Times", FontSize -> 20], {3.5, y, 0}, {-1, 1}] };
Resulting applet:
As mentioned earlier, Text
primitives are not the only way to include labels in LiveGraphics3D. Two other possibilities, namely labels for axes and the whole plot, will be discussed in the next section.
Apart from Text
primitives, which may be positioned arbitrarily, LiveGraphics3D also allows you to place labels at predefined positions for each axis and the whole plot. Apart from the discussion of these labels, this section also describes additional formatting functions for superscripts and subscripts.
Labels for axes are specified by the option AxesLabel -> {
...,
...,
...}
, which is set to a list of three labels, one for each axis. These labels are specified in exactly the same way as the first argument of the Text
primitive, including the possibility of using formatting functions such as StyleForm
or StringForm
. In addition to specifying the labels with AxesLabel
, you also have to specify Axes -> True
to display them. Here is an example:
(* all but the last three commands are the same as in the previous example *)
axeslabel = {StyleForm["x", FontFamily -> "Times", FontSize -> 20],
StyleForm["y", FontFamily -> "Times", FontSize -> 20],
StyleForm["z", FontFamily -> "Times", FontSize -> 20]};
example = Graphics3D[
{mesh, point, xSection, ySection, tanplane},
Boxed -> True, Lighting -> False, Axes -> True, AxesLabel -> axeslabel]
WriteLiveForm["meshPlaneText5.lg3d", example]
Resulting applet:
In this example, we have also set Boxed -> True
to display all edges of the bounding box.
To label the whole plot, you can use the option PlotLabel
. The specified label is centered below the top edge of the mathlet. This is particular useful if you do not want a label to move when you rotate the scene. The required commands to include a plot label displaying the value of the dependent variable z
are:
(* all but the last three commands are the same as in the previous example *)
plotlabel = StyleForm[
StringForm["Plot of 2*y*Exp[-x^2-y^2]; Current z value = `1`", z],
FontFamily -> "Times", FontSize -> 20];
example = Graphics3D[
{mesh, point, xSection, ySection, tanplane},
Boxed -> False, Lighting -> False, PlotLabel -> plotlabel]
WriteLiveForm["meshPlaneText6.lg3d", example]
Resulting applet:
As you rotate the plot, the plot label will keep its position. Note also that the function value included in the label is updated as you drag the red point.
However, there is no need to use this syntax for mathematical expressions in labels. We can produce a more traditional mathematical notation with the help of formatting functions such as Superscript
, Subscript
, Subsuperscript
, Overscript
, Underscript
, Underoverscript
, OverBar
, OverDot
, OverHat
, OverTilde
, OverVector
, and UnderBar
. Here is an example with Superscript
that also uses italics for names of variables:
(* all but the last three commands are the same as in the previous example *)
plotlabel=StyleForm[StringForm["Plot of 2 `1` `2`; Current `3` value = `4`",
StyleForm["y", FontSlant -> "Italic"],
Superscript["e", StringForm["-`1`-`2`",
Superscript[StyleForm["x", FontSlant->"Italic"], "2"],
Superscript[StyleForm["y", FontSlant->"Italic"], "2"]]],
StyleForm["z", FontSlant -> "Italic"], zDisplay],
FontFamily->"Times", FontSize->20]; example = Graphics3D[
{mesh, point, xSection, ySection, tanplane},
Boxed -> False, Lighting -> False, PlotLabel -> plotlabel]
WriteLiveForm["meshPlaneText7.lg3d", example]
Note the reference to zDisplay
; this is defined as a dependent variable within the <applet>
tag by the rule zDisplay -> Round[1000 * z] / 1000
. This trick, which reduces the number of displayed digits, was introduced in the previous section.
Resulting applet:
There is still much more to say about text labels in LiveGraphics3D; however, by now you should have got a fairly good idea of how to do basic text formatting with the help of LiveGraphics3D.
In a three-dimensional image it often happens that one object is in front of another. LiveGraphics3D handles these occlusions by sorting graphics primitives according to their distance from the viewer (i.e. you). Objects which are furthest away are drawn first, and the nearest objects are drawn last; we say they "occlude" the objects behind them. In computer graphics this process is often referred to as the "painter's algorithm." It is simple to implement and works well in most cases, but it can have unintended consequences.
To demonstrate how things can go awry, we'll construct a sphere surrounded by a ring. There is a right way and a wrong way to implement this in LiveGraphics3D.
(* A function to convert from spherical to rectangular coordinates *) f[r_, t_, p_] = {r*Sin[p]Cos[t], r*Sin[p]Sin[t], r*Cos[p]}; (* Define dt and dp for use below *) n = 20; tmin = 0; tmax = 2Pi; dt = (tmax - tmin)/(2n); pmin = 0; pmax = Pi; dp = (pmax - pmin)/n; (* Create a light gray sphere of radius 1 *) sphere = { RGBColor[0.8, 0.8, 0.8], Table[ Polygon[{f[1, i, j], f[1, i + dt, j], f[1, i + dt, j + dp], f[1, i, j + dp]}], {i, tmin, tmax - dt/2, dt}, {j, pmin, pmax - dp/2, dp}]}; (* Create a ring of radius 1.1 around the sphere *) badRing = {Thickness[0.01], RGBColor[0, 0, 1], Line[ Table[f[1.1, i, Pi/2], {i, tmin, tmax, dt}] ]}; (* Another way to create the ring of radius 1.1 *) goodRing = {Thickness[0.01], RGBColor[0, 0, 1], Table[ Line[{f[1.1, i, Pi/2], f[1.1, i + dt, Pi/2]}], {i, tmin, tmax, dt}]};
Before continuing, make sure you understand the difference between the two rings defined above. badRing
consists of a single Line
primitive with a list of points:
Line[{{1.1, 0, 0}, {1.08646, 0.172078, 0}, ..., {1.1,, 0, 0}}]
By contrast, goodRing
uses a separate Line
primitive for each of the line segments:
{Line[{{1.1, 0, 0}, {1.08646, 0.172078, 0}}], Line[{{1.08646, 0.172078, 0},<br /> ..., Line[{{1.08646, -0.172078, 0}, {1.1, 0, 0}}]}}
Within Mathematica, we could use either badRing
or goodRing
and not see a difference in the final picture. If anything, the approach in badRing
is better because it only uses one primitive; in terms of bytes of computer memory, badRing
is less than half the size of goodRing
.
In LiveGraphics3D the two versions are definitely not equivalent, as demonstrated by following applets.
Graphics3D[{sphere, badRing}] |
Graphics3D[{sphere, goodRing}] |
---|---|
The painter's algorithm in LiveGraphics3D is applied primitive-by-primitive. The applet computes a single distance for each primitive, and the depth-sorting is based on these distances. However, most primitives are extended in space and, therefore, are not sufficiently characterized by a single distance. This causes the problem with our ring.
badRing
is just a single primitive characterized by one distance, but the actual ring extends over a large interval of distances. The polygons which make up the sphere should be occluded by one half of the ring, but at the same time they should occlude the other half. Obviously, sorting just two distances (one for the ring and one for a polygon) cannot result in the correct depth ordering.
On the other hand, goodRing
is actually a list of primitives -- more specifically, a list of quite small line segments. Since these line segments are much smaller, the approximation of using only one distance per primitive works considerably better. The polygons of the sphere can now occlude the line segments in the back, but the polygons can in turn be occluded by the segments in front.
This example demonstrates an important rule of thumb: to avoid occlusion errors, it is almost always beneficial to break up large primitives into a collection of smaller objects. In particular, you should always replace Line
primitives that contain a list of points by multiple Line
primitives containing pairs of points. Large polygons should also be split up into a list of smaller ones.
You might ask whether one could solve this sorting problem by taking into account the interval of distances occupied by a primitive in some clever way. Unfortunately, there are cases, so-called "cyclic occlusions," which just cannot be sorted, regardless of the sorting criterion. Here is a simple example with four straight Line
primitives:
(* A Graphics3D object consisting of four Lines
which shows occlusions errors for certain view points *)
Graphics3D[{Thickness[0.05],
RGBColor[1,0,0], Line[{{-2,-1,-1}, {2,-1,1}}],
RGBColor[0,1,0], Line[{{1,-2,-1}, {1,2,1}}],
RGBColor[0,0,1], Line[{{2,1,-1}, {-2,1,1}}],
RGBColor[1,0,1], Line[{{-1,2,-1}, {-1,-2,1}}]},
PlotRange -> {{-2, 2}, {-2, 2}, {-1, 1}},
ViewPoint -> {0,0,4}, ViewVertical -> {0,1,0},
Boxed -> False]
For the default view point, each colored line segment should occlude one other line and be occluded by another. This cyclic occlusion cannot be rendered correctly with four Line
primitives, as you can see in the mathlet on the left-hand side below in comparison with the mathlet on the right-hand side.
occlusion errors with four primitives | correct occlusions after splitting |
---|---|
The correct rendering in the second applet was achieved by splitting each Line
primitive into two; this allowed LiveGraphics3D to "break" the occlusion cycle and render a correct image:
(* A Graphics3D object consisting of eight Lines
which shows cyclic occlusions for certain view points *)
Graphics3D[{Thickness[0.05],
RGBColor[1,0,0], Line[{{-2,-1,-1},{0,-1,0}}],Line[{{0,-1,0},{2,-1,1}}],
RGBColor[0,1,0], Line[{{1,-2,-1},{1,0,0}}],Line[{{1,0,0},{1,2,1}}],
RGBColor[0,0,1], Line[{{2,1,-1},{0,1,0}}],Line[{{0,1,0},{-2,1,1}}],
RGBColor[1,0,1], Line[{{-1,2,-1},{-1,0,0}}],Line[{{-1,0,0},{-1,-2,1}}]
},
PlotRange -> {{-2, 2}, {-2, 2}, {-1, 1}},
ViewPoint -> {0,0,4}, ViewVertical -> {0,1,0},
Boxed -> False]
A popular alternative to the "painter's algorithm" is the use of depth buffering (also called z-buffering), which avoids all of the occlusion problems discussed here. In order to support older web browsers and Java virtual machines, LiveGraphics3D is restricted to Java 1.1, which, unfortunately, does not support depth buffering. Therefore, LiveGraphics3D does not employ depth buffering and suffers from frequent occlusion errors. While these errors are inconvenient, you should remember that this approach enables many users to view mathlets based on LiveGraphics3D without downloading and installing a more recent Java virtual machine.
The decision to restrict LiveGraphics3D to Java 1.1 is based on the popularity of web browsers that are limited to Java 1.1. At the time of writing this article, there is still at least one very popular Java virtual machine that is limited to Java 1.1. However, as soon as such Java virtual machines are no longer installed on a large amount of computers, the restriction of LiveGraphics3D to Java 1.1 certainly has to be reconsidered.
For a further discussion of sorting 3D objects, see "Writing Mathlets III" (Leathrum, 2002).
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, especially 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:
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:
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 worry about it.
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, if an occlusion error does occur, it will be with smaller primitives, and the error will not be as noticeable.
Apart from the intersection of lines with polygons, polygons intersecting other polygons will also cause occlusion errors. For an example, consider the following mathlet:
Resulting Applet:
In order to avoid these unpleasant occlusion errors, you will usually have to split the polygons at the line of intersection:
Resulting Applet:
Unfortunately, the computation of intersections between polygons tends to be rather complicated. Thus, we can only recommend this approach for static objects, and not parametrized graphics. 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).
Earlier in the article we described how to simulate two-dimensional graphics with LiveGraphics3D. Now that we've discussed occlusions and intersections, we can discuss some of the more advanced issues with two-dimensional scenes. To illustrate the problems, we will construct the convex hull of five points on the xy-plane. Our basic approach to this problem will be very simple: we just show all triangles defined by all possible triples of points. Since the user should be able to drag all points, their coordinates have to be defined by independent variables. Here is a first try:
Resulting Applet:
Notice that we've used appropriate settings for ViewPoint
and ViewVertical
to make the picture look two-dimensional, but unfortunately this is still not an attractive mathlet. You can drag the red points in the xy-plane and get a rough idea of what their convex hull is, but the overlapping triangles are ugly and obscure the fifth point. There are a few things to be done.
For two-dimensional graphics, the standard "diffuse" lighting of polygons is usually not required. We can disable diffuse lighting by specifying the option Lighting -> False
, and specify colors using RGBColor
.
In the example, all primitives (polygons and points) are in the same plane; therefore, the depth sorting is numerically unstable and occlusions of points and triangles are unpredictable. We solve this problem with two "tricks": First, the z-coordinate of all points are slightly increased such that they are all in front of the triangles and never occluded by them. With the help of multiple z values, you are free to specify an arbitrary occlusion order for two-dimensional primitives. This method should only be used with a large ViewPoint
vector, in order to avoid perspective effects for z values different from 0.
We'll also disable edges of polygons by specifying an empty directive EdgeForm[]
. This does not avoid the unstable sorting of the overlapping polygons; however, we do not need to care about it any longer since all triangles have exactly the same color.
These changes have been incorporated into the following code.
Resulting Applet:
We can show the actual convex polyline of the convex hull by adding thick lines between all pairs of points in an additional "depth layer" with z coordinates smaller than 0. All lines within the convex hull will be occluded by the triangles, while at least half of the thick lines at the edges of the convex hull are visible. The additional primitives are defined in this list:
(* This list is inserted in the previous code after (or before) the list of polygons *) { Thickness[0.02], (* Thick lines *) RGBColor[0, 0, 0], (* Black lines *) Line[{{x0, y0, -0.1}, {x1, y1, -0.1}}], Line[{{x0, y0, -0.1}, {x2, y2, -0.1}}], Line[{{x0, y0, -0.1}, {x3, y3, -0.1}}], Line[{{x0, y0, -0.1}, {x4, y4, -0.1}}], Line[{{x1, y1, -0.1}, {x2, y2, -0.1}}], Line[{{x1, y1, -0.1}, {x3, y3, -0.1}}], Line[{{x1, y1, -0.1}, {x4, y4, -0.1}}], Line[{{x2, y2, -0.1}, {x3, y3, -0.1}}], Line[{{x2, y2, -0.1}, {x4, y4, -0.1}}], Line[{{x3, y3, -0.1}, {x4, y4, -0.1}}] }
A nice effect can be accomplished by additionally coloring the triangles white with the help of the directive RGBColor[1, 1, 1]
, i.e., making them indistinguishable from the background.
Resulting Applet:
This completes the example. It should be noted that we haven't actually constructed a convex hull but rather used a trick to display it. A more "constructive" way of approaching this problem would be to check for each line between two points whether all other points are on the same side of it. Here is an example for the line between {x0,y0,0}
and {x1,y1,0}
:
{
If[Sign[x1 y0 - x2 y0 - x0 y1 + x2 y1 + x0 y2 - x1 y2] ==
Sign[x1 y0 - x3 y0 - x0 y1 + x3 y1 + x0 y3 - x1 y3] &&
Sign[x1 y0 - x3 y0 - x0 y1 + x3 y1 + x0 y3 - x1 y3] ==
Sign[x1 y0 - x4 y0 - x0 y1 + x4 y1 + x0 y4 - x1 y4],
Line[{{x0, y0, 0}, {x1, y1, 0}}],
{}]
(* Analogously for all other pairs of points. *)
}
In general this approach is preferable, among other reasons because it can be adapted for convex hulls in three dimensions. However, it results in rather large expressions, which we wanted to avoid in this introduction.
Stereo images are often used in other fields to give the viewer a sense of depth in a picture; chemists, for example, use stereo pictures to view a molecule in 3D, while geologists can use stereo versions of a relief map to view the topography of an area. Stereo pictures seem to be less common in mathematics, although web pages such as the KnotPlot Site (Scharein) and the Gallery of Famous Surfaces (3DXM) provide static stereo images of beautiful mathematical curves and surfaces. (As it happens, the latter site also uses LiveGraphics3D to display rotating versions of the surfaces.) If you would like to learn how to view these images, searching for "how to view stereo images" with any Internet search engine will lead you to a number of basic tutorials.
LiveGraphics3D can display any image in stereo mode with no extra development work on your part. As an example, we'll use the MAA's icosahedron logo. (To see the list of polygons used to create this image, you can use your web browser to view the source code of this page.) If you place the mouse in the applet below, you can use the 's' key to cycle through parallel stereo mode, cross-eyed stereo mode, and normal viewing mode. Press the Control key and click and drag horizontally to adjust the strength of the stereo effect.
None of LiveGraphics3D's features are affected when you use the stereo mode. You can still rotate and zoom in or out, and animations and parametrized graphics function normally. If you wish to have LiveGraphics3D start in stereo mode, you can include the following parameter in your HTML code:
<param name="STEREO_DISTANCE" value="0.05"/>
The stereo distance 0.05
is appropriate for parallel stereo images; for cross-eyed viewing you should replace it with -0.05
. If you use the stereo mode, you might also wish to make your applet twice as wide as it is high, lest your stereo images get cramped.
Any discussion of stereo displays would be incomplete without mentioning the GeoWall system (GeoWall.org), which has become very popular in the earth sciences. A GeoWall uses side-by-side stereo images, two computer projectors aimed at the same screen, and polarizing filters and glasses to create the illusion of a true three-dimensional display. Any mathlet created with LiveGraphics3D can be adapted for use with a GeoWall with virtually no additional effort: simply make your applet as wide as the entire GeoWall display (typically 2048 pixels), set the stereo distance to 0.05, and you have instantly created a "true" 3D applet.
As mentioned in the introduction, it is not necessary to use Mathematica to develop mathlets with LiveGraphics3D. Theoretically, as long as you have a knowledge of Mathematica's syntax, you can create LiveGraphics3D input files from scratch using any text editor. However, few of us have the patience to calculate 400 vertices, let alone type out a list of 400 polygons, just to display a surface. Realistically, even if you do not use Mathematica, you will still want to avoid creating your input by hand.
One possibility is to create your graphics using some other application, and then use a special program to convert the results into a LiveGraphics3D input file. Two examples of such tools are:
If neither of these tools is suitable, you can use nearly any computer language to produce the input for LiveGraphics3D. The only key is to mimic Mathematica's output. For example, recall the following piece of Mathematica code which was used to construct a cross section of a surface on this earlier page.
(* "xmin," "xmax," "dx," etc. were defined earlier.*)
f[x_,y_]= 2y*Exp[-x^2-y^2];
ySection = {RGBColor[0, 0, 1], Thickness[0.005],
Table[Line[{{i, y, f[i, y]},
{i + dx, y, f[i + dx, y]}}],
{i, xmin, xmax - dx, dx}]};
To create this output with another language, we can simply use a loop to construct the string
{RGBColor[0., 0., 1.], Thickness[0.005], Line[{{-1., y, 2.*2.718^(-1. - 1.*y^2)*y}, {-0.8, y, 2.*2.718^(-0.64 - 1.*y^2)*y}}], ... }
This can be accomplished by implementing the following pseudo-code; here string means a function which takes a number and turns it into a string:
input = "{RGBColor[0, 0, 1], Thickness[0.005]," let i loop from xmin to xmax with stepsize dx if this is not the first polygon, then input = input + "," segment = "Line[{" +"{"+string(i)+", y, 2*y*Exp[-"+string(i^2)+"-y2]}," +"{"+string(i+dx)+", y, 2*y*Exp[-"+string((i+dx)^2)+"-y2]}" +"}]" input = input + segment end of loop input = input + "}"
While it might seem inelegant to piece together a Mathematica-style list in this manner, the difficulties are easy to overcome. One of us has created a stand-alone application version of the "Interactive Gallery of Quadric Surfaces," which appeared in this journal as a suite of mathlets on web pages (Rogness, 2005). The application version computes all of the inputs for the parametrized graphics in the gallery at runtime using for
loops as described here.
JavaScript is also readily adaptable to this purpose, and provides an opportunity for a truly interactive experience. For example, you can create a web page with a form which allows a student to enter the equations for a parametric surface; the JavaScript code which parses the student's input can also generate the corresponding graph using LiveGraphics3D. Try this basic demonstration.
For another example, see Amar Junankar's web page, mentioned above. It includes a "Parametric Curve Generator" which allows users to enter the parametric equations of a curve into an HTML form. The page then uses JavaScript to produce the input for LiveGraphics3D, and the curve is displayed in a pop-up window.
The following files are available for download as part of this article.
If Mathematica is available on your computer, you may be interested in a package developed at the University of Minnesota which allows LiveGraphics3D to run from within Mathematica. Standard Mathematica commands like Show
and Animate
are supplemented by ShowLive
and AnimateLive
, which automatically display 3D plots, parametrized graphics, and animations using LiveGraphics3D. Contact us for details.