The main problem that programmers new to object graphics have is getting anything to appear on the screen (at least anything they recognize). Once you have your graphics appearing in a window, it’s pretty simple to change their properties, rotate them, make them dance the rhumba, etc.

Simple object graphics example

There are various ways to scale your data into the view volume. This article will show one scaling technique I like for displaying 3D objects: using the [XYZ]COORD_CONV properties of a graphics atom object.

The code for the example is MG_CC_DEMO (doc) and a helper routine MG_LINEAR_FUNCION (doc).

The simplest possible object graphics hierarchy is an IDLgrView containing an IDLgrModel which contains a graphics atom object.

oview = obj_new('IDLgrView')
<omodel = obj_new('IDLgrModel')
oview->add, omodel

The surface object has many properties effecting its display. Here, style=2 is a shaded surface, while COLOR and BOTTOM set the color of the top and bottom of the surface.

osurface = obj_new('IDLgrSurface', hanning(20, 20), style=2, $
                   color=[255, 0, 0], bottom=[100, 0, 0])
omodel->add, osurface

The default lighting model is an ambient light that will cause 3D objects to look flat like a silhouette. I put the light in its own model so that when I rotate the model containing the surface the light will stay fixed. Here, type=2 specifies a directional light coming from [1, 1, 1] and heading towards the origin.

olightmodel = obj_new('IDLgrModel')
oview->add, olightmodel
olight = obj_new('IDLgrLight', type=2, location=[1, 1, 1])
olightmodel->add, olight

The hierarchy is complete, but we need to change some properties to get a more reasonable display. The most important task is to make sure the data space is scaled into the coordinates of the view. The default coordinates of the view vary from -1 to 1 in every dimension. First, we need to know the extent of our data in each dimension.

osurface->getProperty, xrange=xr, yrange=yr, zrange=zr

The two-element arrays xr, yr, and zr contain the minimum and maximum value of their respective variables. A handy routine MG_LINEAR_FUNCTION produces a linear function which scales its first argument into its second argument. (A lot of people use the IDL library routine NORM_COORD to do this. NORM_COORD is like MG_LINEAR_FUNCTION with a second argument that is always [0, 1], so it must be shifted to use the range [-0.5, 0.5] and other ranges require some algebra.)

xc = mg_linear_function(xr, [-0.6, 0.6])
yc = mg_linear_function(yr, [-0.6, 0.6])
zc = mg_linear_function(zr, [-0.6, 0.6])

The two-element arrays xc, yc, and zc are the coefficients of linear functions. These are the appropriate values for the `[XYZ]COORD_CONV properties of the surface.

osurface->setProperty, xcoord_conv=xc, ycoord_conv=yc, zcoord_conv=zc

The default orientation will be looking straight down on the surface. A better orientation will make the shape of the surface more apparent. Remember: the model is responsible for holding the transformation matrix, so use methods on the model to rotate, scale, or translate.

omodel->rotate, [1, 0, 0], -90
omodel->rotate, [0, 1, 0], 30
omodel->rotate, [1, 0, 0], 30

Finally, create a destination, here an IDLgrWindow, and use its draw method.

owindow = obj_new('IDLgrWindow', dimensions=[400, 400])
owindow->draw, oview

Don’t forget to destroy the view object when you’re done with the object graphics hierarchy. It will in turn destroy the objects that are contained in it. The owindow object will be destroyed when the user closes the window.

obj_destroy, oview