Sage offers a 3D Graphics API which works in a way very similar to it's 2D API. The API is made up of primitives that can be combined together and then displayed using show. 3D Graphics are shown using a Java applet called Jmol which is designed as viewer for chemical structures.
3D Graphics Primitives
Sage provides a variety of primitives for 3D Graphics from common shapes to platonic solids. Here is a list of the main primitives provided by Sage:
Like many 3D APIs Sage supports affine transformations for 3D graphics including rotate, scale, and translate.
The following methods are provided:
- scale: takes a list of the values [x,y,z] specifying the amount to scale by.
- translate: takes a list of values [x,y,z] specifying the amount to translate by.
- rotate: takes a rotation vector v, and float specifying the rotation around v in radians.
- rotateX: takes a float value specifying the rotation around X in radians
- rotateY: takes a float value specifying the rotation around Y in radians
- rotateZ: takes a float value specifying the rotation around Z in radians
Creating 3D Graphics
If you've read the section on 2D Graphics you might remember that 2D graphics in Sage are created by adding primitives to a Graphics object. However, due to a bug, or strange implementation decision which ever you prefer, this isn't possible with 3D graphics (they did promise that this will change in future versions). We have a few possible options we can use instead.
Let's start with a simple example of creating a sphere with some text.
g = sphere( (0,0,0), 1 ) g += text3d( "This is a sphere", (1,1,1) ) g.show()
or we can also do it this way
g = 0 g += sphere( (0,0,0), 1 ) g += text3d( "This is a sphere", (1,1,1) ) g.show()
As you may have guessed by looking at the code sphere( (0,0,0), 1 ) creates a sphere at position (0,0,0) with a radius of 1, and text3d( "This is a sphere", (1,1,1) ) creates the text This is a sphere at position (1,1,1).
Colors can be set in several different ways for 3D objects in Sage. First colors can be specified as strings such as 'blue' or as rgb values using a 3-Tuple such as (0.5,0.2,0.35). Secondly you can specify one color for the entire object or specify a color for each face of the object. How to specify colors on a per face basis is dependent on the type of object you have.
Example: Create a cube with all sides blue:
cube( (0,0,0), 2, color='blue' )
Example Create a cube with different colors for each face:
cube( (0,0,0), 2, color=['white', 'blue', 'green', 'orange', 'red', 'black'] )
The show Function
The show function for 3D Graphics allows for several useful parameters:
- frame: boolean specifies whether or not there should be a frame drawn around the objects. Default: True
- figsize: integer, specifies the size of the Java applet.
- aspect_ratio: integer, default 1
- zoom: integer, default 1
Tachyon Ray Tracer
In addition to the standard 3D Graphics API Sage also provides the ability to create 3D images using the Tachyon Ray Tracer.
Creating Images with Standard Graphics3D Objects
The simplest way to create 3D images using Tachyon is with standard 3D Graphics object in Sage. This is done by passing the keyword parameter viewer to the show function and giving it the value tachyon. This will output a png image instead of starting up the jmol applet.
s = sphere( (0,0,0), 1 ) s.show( viewer='tachyon' )
Interacting Directly with Tachyon
Sage also provides a interface to interact directly with Tachyon. This process is a bit more complex and involves setting up a camera position as well as adding lights, textures, and objects.
Let's start by walking through a simple example:
We start by constructing a Tachyon object. We must pass in several parameters to the Tachyon constructor:
- xres: integer, image x resolution
- yres: integer, image y resolution
- camera_center: 3-Tuple, specifies the location of the camera
- look_at: 3-Tuple, specifies the location the camera is looking at
- updir: 3-Tuple, specifies the camera's up vector
- raydepth: integer
For anyone familiar with openGL the camera_center, look_at, and updir, correspond to directly to the parameters of gluLookAt.
Other parameters available include:
- antialiasing: boolean
- projection: string, default 'PERSPECTIVE'
- aspectratio: float, default 1.0
For our example we'll start with the camera at (0,5,-10) looking at the origin, with the up vector (0,1,0)
t = Tachyon( xres=800, yres=600, camera_center=(0,5,-10), look_at=(0,0,0), updir=(0,1,0), raydepth=24 )
Now we will add a light to our 'scene' using the light method.
The light method takes three parameters:
- center: 3-Tuple, specifies the location of the light
- radius: double, specifies the radius of the light
- color: 3-Tuple, specifies the rgb values of the light color
For our example we put our light at (-3,10,-2) with a radius of 1, and set the light color to white.
t.light( (-3,10,-2), 1, (1,1,1) )
Next we will create several textures which we can apply to our objects. The texture method takes quite a few parameters:
- name: string, unique identifier for the texture
- ambient: double, level of ambient light between 0 and 1
- diffuse: double, level of diffuse light between 0 and 1
- specular: double, level of specular light between 0 and 1
- opacity: double, opacity level 1.0 for solid, 0.0 for fully transparent
- color: 3-Tuple, rgb values of the texture color
- texfunc: a number between 0 and 9 representing one of the following options
- 0 : No special texture, plain shading
- 1 : 3D checkerboard function
- 2 : Grit Texture, randomized surface color
- 3 : 3D marble texture, uses object's base color
- 4 : 3D wood texture, light and dark brown
- 5 : 3D gradient noise function
- 6 : ??
- 7 : Cylindrical Image Map
- 8 : Spherical Image Map
- 9 : Planar Image Map
Note: texfunc options are taken via Sage's bug tracker, ticket #799
For our example we will create 5 different textures, t1-t5 which we'll use later:
t.texture( 't1', 0.2, 0.8, 0.8, 1.0, (1.0,0.0,0.5) ) t.texture( 't2', 0.2, 0.8, 0.8, 1.0, (1.0,0.0,1.0) ) t.texture( 't3', 0.2, 0.8, 0.8, 1.0, (1.0,0.0,0.0) ) t.texture( 't4', 0.2, 0.8, 0.8, 1.0, (0.0,1.0,0.0) ) t.texture( 't5', 0.2, 0.8, 0.8, 1.0, (0.0,0.0,1.0) )
Next for reasons we will see in a moment we will create a list of our texture names:
textures = ['t1','t2','t3','t4','t5']
Now we will add spheres in a circle, choosing a random texture for each circle. The sphere method takes 3 parameters:
- center: 3-Tuple, location of the center of the sphere
- radius: float, radius of the sphere
- texture: string, string identifier of the texture to be applied to the sphere
[ t.sphere( (5*sin(x),0,5*cos(x)), 1.0, textures[randint(0,len(textures)-1)] ) for x in [0..2*pi-.5,step=1] ]
Now we can use the show method to display our picture:
Here's the resulting image:
A more complex example
The first example produced a very simple image. This example from sagemath.org creates a more complex image drawing points on an elliptic curve.
We start off similar to the previous example by constructing our Tachyon object and adding lights and textures. You may have notice there isn't an up vector specified. That's because it defaults to (0,1,0), I just put it in the previous example to explain it's purpose.
t = Tachyon(xres=1000, yres=800, camera_center=(2,7,4), look_at=(2,0,0), raydepth=4) t.light((10,3,2), 1, (1,1,1)) t.light((10,-3,2), 1, (1,1,1)) t.texture('black', color=(0,0,0)) t.texture('red', color=(1,0,0)) t.texture('grey', color=(.9,.9,.9))
Next we add a plane and two cylinders to the scene:
t.plane((0,0,0),(0,0,1),'grey') t.cylinder((0,0,0),(1,0,0),.01,'black') t.cylinder((0,0,0),(0,1,0),.01,'black')
Next we create an EllipticCurve object, then we add a 100 new textures and spheres to the scene:
E = EllipticCurve('37a') P = E([0,0]) Q = P n = 100 for i in range(n): Q = Q + P c = i/n + .1 t.texture('r%s'%i,color=(float(i/n),0,0)) t.sphere((Q, -Q, .01), .04, 'r%s'%i)
Finally we display the image using the show method:
Here is the resulting image: