Some Documentation for glsim.js

glsim.js is an open-source JavaScript library, written by David Eck, that implements a subset of OpenGL 1.1 by translating OpenGL function calls into WebGL 1.0. Note in particular that it requires WebGL support. The subset of OpenGL includes the part of the API that is discussed in Chapters 3 and 4 of the on-line computer graphics textbook at http://math.hws.edu/graphicsbook (except for display lists). The purpose of glsim.js is simply to support that textbook. It is not in any way meant as a serious implementation of OpenGL! It is also by no means fully tested, and it is rather inefficient. However, it does work in the simple applications for which I have used it, and it might be useful for basic experimentation and practice with OpenGL 1.1, with the goal of learning something about 3D graphics concepts, without the hassle of setting up an OpenGL development environment for a language like C or Java.

Sample programs using glsim.js can be found in http://math.hws.edu/graphicsbook/source/glsim/. It is also used for some of the live demos in the book, which can be found in http://math.hws.edu/graphicsbook/demos/c3/ and http://math.hws.edu/graphicsbook/demos/c4/.

Some of the code in glsim.js is taken from the popular JavaScript matrix and vector math library glmatrix, written by Brandon Jones and Colin MacKenzie IV, which can be obtained from http://glmatrix.net.

glsim.js defines many functions and constants in the global namespace, including all OpenGL functions and constants that it implements, as well as several utility functions with names that begin with "glsim." It defines a class named GLSim to represent OpenGL graphics contexts, but that class will not usually be referenced directly in code that uses glsim.js. There is also a class named Camera representing a camera abstraction that can be used as an aid for setting up viewing and projection in OpenGL.

Creating the OpenGL Graphics Context

Since glsim is based on WebGL, you need a <canvas> element to draw on. To set up a canvas for OpenGL drawing, call

glsimUse(canvas);

The parameter can be either a string giving the id of a <canvas> element or the DOM object corresponding to a <canvas> element (generally obtained using document.getElementById).

The function call glsimUse(canvas) sets the canvas to be the current (simulated!) OpenGL context. All OpenGL functions apply to the current context and will throw an exception if no such context has been established. It is possible to have multiple canvasses, each with its own OpenGL context. glsimUse() can be used to switch among contexts. If a context has already been created for a canvas when glsimUse(canvas) is called, then it simply uses the existing context for the canvas. Any state from previous uses of the context is retained. That is, glsimUse() can be used to switch among several exiting OpenGL contexts at any time.

An OpenGL context is represented by an object of type GLSim. You can also create the context directly, by calling the GLSim constructor; glsimUse is simply a convenience for creating and managing the contexts.

The function glsimUse can take a second parameter, to control options for the WebGL drawing context that is actually used on the canvas. If the second parameter is omitted, then default options for glsim will produce a color buffer with no alpha component (RGB colors rather than RGBA). This is generally desirable, since alpha transparency in the color buffer allows the background of the canvas on the web page to show through the canvas (but it is not the same default as in the GLSim constructor). To get an RGBA color buffer, you can pass null as the second parameter to glsimUse when creating a new drawing context. Furthermore the default options allow WebGL to discard the WebGL color buffer between drawing operations. Note that this is not correct behavior for OpenGL, but the correct OpenGL behavior is inefficient in WebGL and is not needed as long as all drawing is done in a function that completely redraws the image (as is the case for all of my examples). To preserve the color buffer between drawing operations, you can pass { preserveDrawingBuffer: true } as the second parameter to glsimUse(), or pass { alpha:false, preserveDrawingBuffer: true } as the second parameter to get an RGB color buffer.

Note that the second parameter to glsimUse() applies only when a a new OpenGL context is created, not when it merely switches to one that already exists. (The second parameter can actually be any object that represents a valid set of WebGL options; it is simply passed to the call to getContext() that creates the WebGL context.)

Other "glsim" Functions

The functions glsimTexImage(image) and glsimCopyTexImage(x,y,width,height) are defined as simple abbreviations for

glTexImage2D(GL_TEXTURE_2D,0,GL_RGBA,0,0,0,GL_RGBA,GL_UNSIGNED_BYTE,image);

and

glCopyTexImage2D(GL_TEXTURE_2D,0,GL_RGBA,x,y,width,height,0);

since the parameters are so hard to get right. (Note that the width and the height in glTexImage2D are ignored.) The function

glsimLoadImage(imageURL,callback);

will load an image from the specified imageURL asynchronously. The imageURL parameter must be a string. The callback parameter, if present, must be a function. When the image has been loaded, the callback function will be called with two parameters: the Image object containing the loaded image and the imageURL that was the first parameter to glLoadImage. A typical use would be to install the image as a texture and enable texturing in the callback. For example,

glsimLoadImage("myTextureImage.jpg", function(image) {
    glsimTexImage(image);
    glEnable(GL_TEXTURE_2D);
    display(); // Redraw the image, with the texture.
});

If an error occurs while loading the function, a message is printed to the console, and the callback function is not called, but otherwise the error is ignored.

The current OpenGL context, which is a GLSim object, is available as GLSim.currentContext. The corresponding WebGL context is available as GLSim.currentContext.gl. When glsim.js detects an error, it calls the function GLSim.error(message), where the parameter is an error message. This function could be changed to customize error handling. The default function throws the error message as an exception and prints the message and a stack trace to the console.

OpenGL Functions

This is a list of OpenGL functions that are supported by glsim.js. They should work in the same way as the real OpenGL functions, except as noted. These functions are defined as global identifiers in glsim.js. Any constants that they use are also defined globally, but do not necessarily have the same value as they do in OpenGL. All of these functions will throw an exception if they are called when there is no current OpenGL context.

glEnable(param)/glDisable(param) — The parameter can be one of the following constants: GL_DEPTH_TEST, GL_COLOR_MATERIAL, GL_BLEND, GL_LIGHT0 through GL_LIGHT7, GL_LIGHTING, GL_NORMALIZE, GL_POINT_SMOOTH, GL_POLYGON_OFFSET_FILL, GL_TEXTURE_2D. Note that only four lights are supported by default; to enable additional lights, set GLSim.lightCount equal to the number of lights before calling glsimUse(). (I found that trying to have eight lights seemed to require more data in the WebGL shader programs than they could handle on some computers -- specifically my old desktop -- leading to hard-to-diagnose errors.)

glEnableClientState(param)/glDisableClientState(param) — The parameter can be one of the following constants: GL_VERTEX_ARRAY, GL_COLOR_ARRAY, GL_NORMAL_ARRAY.

glPushAttrib(mask) and glPopAttrib() — The mask can contain only the following constants, OR'ed together: GL_CURRENT_BIT, GL_ENABLE_BIT, GL_LIGHTING_BIT, GL_VIEWPORT_BIT. And of course, only values that are used by glsim are saved and restored.

glGetIntegerv(item,array) — The supported values of item are GL_VIEWPORT, GL_LIGHT_MODEL_TWO_SIDE, and GL_LIGHT_MODEL_LOCAL_VIEWER. The second parameter must be an array, of sufficient size to hold the value.

glGetFLOATv(item,array) — The supported values of item are GL_POINT_SIZE, GL_LINE_WIDTH, GL_CURRENT_COLOR, GL_CURRENT_NORMAL, GL_CURRENT_TEXTURE_COORDS, GL_LIGHT_MODEL_AMBIENT, GL_MODELVIEW_MATRIX, GL_PROJECTION_MATRIX, and GL_TEXTURE_MATRIX. The second parameter must be an array, of sufficient size to hold the value.

glIsEnabled(item) — The supported values of item the same as the supported parameters for glEnable/glDisable.

glGetMaterialfv(face, property, array) — All parameter are supported. The second parameter must be an array, of sufficient size to hold the value.

glGetLightfv(light, property, array) — The supported values of property are those for which glLightfv are defines: GL_POSITION, GL_AMBIENT, GL_DIFFUSE, and GL_SPECULAR. The second parameter must be an array, of sufficient size to hold the value.

glViewport(x,y,width,height)

glClearColor(r,g,b,a)

glClear(buffers) — The parameter can be GL_COLOR_BUFFER_BIT, or GL_DEPTH_BUFFER_BIT, or those two constants OR'ed together.

glLineWidth(size)

glPointSize(size)

glBegin(primitive)/glEnd() — The primitive can be any of the ten OpenGL 1.1 primitives: GL_POINTS, GL_LINES, GL_LINE_STRIP, GL_LINE_LOOP, GL_TRIANGLES, GL_TRIANGLE_FAN, GL_TRIANGLE_STRIP, GL_QUADS, GL_QUAD_STRIP, GL_POLYGON.

glColor* — The suffixes 3f, 4f, 3d, 4d, 3ub, 4ub, 3fv, 4fv, 3dv, 4dv, 3ubv, and 4ubv are supported (but no distinction is made between the f and the d versions). The v versions can take an ordinary JavaScript array or a typed array as parameter.

glVertex* — The suffixes 2i, 3i, 2f, 3f, 2d, 3d, 2iv, 3iv, 2fv, 3fv, 2dv, and 3dv are supported, but no real distinction is made among the various numeric types. The v versions can take an ordinary JavaScript array or a typed array as parameter. Note that the 4-coordinate versions of glVertex* are not available.

glNormal* — The suffixes 3f, 3d, 3fv, and 3dv are supported, but no distinction is made between the f and d versions. The v versions can take an ordinary JavaScript array or a typed array as parameter.

glTexCoord* — The suffixes 2f, 2d, 2i, 2fv, 2dv, and 2iv are supported, but no distinction is made between the f, d, and i versions. The v versions can take an ordinary JavaScript array or a typed array as parameter. Note that only the 2-coordinate versions of glTexCoord* are supported (and only 2D textures are supported).

glRect* — The suffixes i, f, and d are supported, but they are all really the same function.

glMaterialfv(side,property,value) — The side can be GL_FRONT, GL_BACK, or GL_FRONT_AND_BACK. The property can be GL_AMBIENT_AND_DIFFUSE, GL_AMBIENT, GL_DIFFUSE, GL_SPECULAR, or GL_EMISSION. The value can be an ordinary JavaScript or typed array of 3 or 4 numbers. (In OpenGL, 4 numbers are required.)

glMaterialf(side,property,value) — The side can be GL_FRONT, GL_BACK, or GL_FRONT_AND_BACK. The property must be GL_SHININESS. The value is a number, which should be in the range 0 to 128. (You can use the name glMateriali instead of glMaterialf, but it's the same function.)

glColorMaterial(side,property) — The parameter take the same values as for glMaterialfv.

glLightfv(light,property,value) — The first parameter should be one of the constants GL_LIGHT0, GL_LIGHT1, GL_LIGHT2, or GL_LIGHT3 (unless you have changed the number of lights, as discussed above under glEnable). The second parameter must be one of GL_POSITION, GL_AMBIENT, GL_DIFFUSE, or GL_SPECULAR. (Note that not all OpenGL lighting properties are defined; there are no spotlights or attenuation.) The third parameter must be a JavaScript or typed array of 3 or 4 numbers. (In OpengGL, 4 numbers are required.)

glLightModelfv(property, value) — The property must be GL_LIGHT_MODEL_AMBIENT, and the value must be a JavaScript or typed array of 3 or 4 numbers. (In OpenGL, 4 numbers are required).

glLightModeli(property,value) — The property must be GL_LIGHT_MODEL_TWO_SIDE or GL_LIGHT_MODEL_LOCAL_VIEWER. The value is treated as a boolean. (Can also use glLightModelf.)

glTexImage2D(target,level,internalFormat,width,height,border,format,type,image) — The target must be GL_TEXTURE_2D. The level must be a non-negative integer. The internalFormat and format must be GL_RGBA. The type must be GL_UNSIGNED_INT. The image must be an Image object (or an HTMLImageElement). The width, height, and border are ignored. (Consider using glsimTexImage(image), which is described earlier on this page, instead.)

glCopyTexImage2D(target,level,internalFormat,x,y,width,height,border) — The target must be GL_TEXTURE_2D. The level must be a non-negative integer. The internalFormat must be GL_RGBA. The border parameter must be 0 if present. The values of x, y, width, and height must be integers. (Consider using glsimCopyTexImage(x,y,width,height) instead.)

glTexParameteri(target,pname,value) The target must be GL_TEXTURE_2D. The pname can be GL_TEXTURE_WRAP_S or GL_TEXTURE_WRAP_T (with value GL_CLAMP or GL_REPEAT); GL_TEXTURE_MAG_FILTER (with value GL_NEAREST or GL_LINEAR); GL_TEXTURE_MIN_FILTER (with value GL_NEAREST, GL_LINEAR, GL_LINEAR_MIPMAP_LINEAR, GL_NEAREST_MIPMAP_LINEAR, GL_LINEAR_MIPMAP_NEAREST, or GL_NEAREST_MIPMAP_NEAREST).

glGenTextures(count,array) — The first parameter must be a positive integer, and the second must be an array. The generated texture ID's are stored in the array.

glCreateTexture() — This is not a standard OpenGL function (though it is in WebGL). It generates one textureID, as by a call to glGenTextures, and it returns that texture ID.

glBindTexture(texureID) — The textureID must be an integer ID returned by glGenTextures or glCreateTexture. Initially, a default texture object is bound. If glBindTexture is called with no parameter (or any value that evaluates to false as a boolean), then the default initial texture object is rebound.

glMatrixMode(mode) — The parameter must be GL_PROJECTION, GL_MODELVIEW, or GL_TEXTURE.

glLoadIdentity()

glPushMatrix()

glPopMatrix()

glTranslatef(dx,dy,dz)/glTranslated(dx,dy,dz) — These are actually the same function.

glScalef(sx,sy,sz)/glScaled(sx,sy,sz) — These are actually the same function.

glRotatef(degrees,x,y,z)/glRotated(degrees,x,y,z) — These are actually the same function.

glOrtho(xmin,xmax,ymin,ymax,near,far)

glFrustum(left,right,bottom,top,near,far)

gluPerspective(fovy,aspect,near,far) — Note that this is a function from the GLU library rather than an OpenGL function.

gluLookAt(eyeX,eyeY,eyeZ,refX,refY,refZ,upX,upY,upZ) — Note that this is a function from the GLU library rather than an OpenGL function.

glVertexPointer(coordsPerVertex,dataType,stride,vertexArray) — coordsPerVertex can be 2 or 3 (4 is not supported). The dataType must be GL_INT, GL_FLOAT, or GL_DOUBLE, but the actual value is ignored. The stride must be zero. The vertexArray can be a JavaScript array of numbers or a typed array.

glColorPointer(componentsPerColor,dataType,stride,colorArray) — componentsPerColor can be 3 or 4. The dataType can be GL_FLOAT, GL_DOUBLE, or GL_UNSIGNED_BYTE. For GL_UNSIGNED_BYTE, color values are divided by 255, then treated as floats. The other two parameters are as for glVertexPointer.

glNormalPointer(dataType,stride,normalArray) — The parameters are as for the same parameters in glVertexPointer.

glTexCoordPointer(size,dataType,stride,texCoordArray) — The first parameter, size, must be 2. The remaining parameters are as for the parameters in glVertexPointer.

glDrawArrays(primitive,start,count) — The primitive can be any of the ten primitive types, as for glBegin. Note that GL_QUADS is inefficient, since it has to be converted to GL_TRIANGLES, and that requires rebuilding the vertex arrays.

glDrawElements(primitive,vertexCount,indexType,indexArray) — The primitive can be any of the ten primitive types, with the same comment about GL_QUADS as for glDrawArrays. The indexType can be GL_UNSIGNED_INT, GL_UNSIGNED_BYTE, or GL_UNSIGNED_SHORT, but the values are always converted to 16-bit integers. The indexArray can be a JavaScript array of integers or a typed array of integers.

glPolygonOffset(units,value)

glFlush() — This is defined, but calling it shouldn't be necessary.

glDepthMask(enableWriteToDepthBuffer) — The parameter is treated as a boolean value.

glFrontFace(face) — The parameter must be GL_CW or GL_CCW.

glBlendFunc(a,b) — The parameters are passed to WebGL without checking them for validity, but really only glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA) should be called (and none of the other constants that could be used as parameters in OpenGL are defined in glsim.js).

The Camera

The Camera class that is defined by glsim.js is designed to take over the job of setting up the OpenGL projection and viewing transforms. It can, optionally, be used to rotate the view, like a trackball.

To use a camera, you need a global variable to refer to it. I assume that that variable is named camera. The camera is created by a constructor without parameters:

camera = new Camera();
To use the camera, call the function

camera.apply() — Sets the projection and viewing transform for the current OpenGL context, according to the configuration of the camera. This completely replaces the current projection and modelview transforms. It would ordinarily be called at the start of the display function.

The camera can be configured with the following functions:

camera.lookAt(eyeX,eyeY,eyeZ,refX,refY,refZ,upX,upY,upZ) — sets up the viewing transform. The viewing transform will be set up by calling gluLookAt with the same parameters, when camera.apply() is called. The default configuration has the eye at (0,0,30), the ref point at (0,0,0), and the up vector as (0,1,0). If you call the function with fewer than 9 parameters, the up vector is set to (0,1,0). If you call it with fewer than 6 parameters, the ref point is set to (0,0,0). The first three parameters are mandatory.

camera.setLimits(xmin,xmax,ymin,ymax,zmin,zmax) — Set the limits of the view volume. The limits are set with respect to the viewing coordinates. That is, the view center is assumed to be at the point (0,0) in the plane of the screen. The view up vector (more precisely, its projection onto the screen) points upwards on the screen. The z-axis is perpendicular to the screen, with the positive direction of the z-axis pointing out of the screen. In this coordinate system, xmin and xmax give the horizontal limits on the screen, ymin and ymax give the vertical limits on the screen, and zmin and zmax give the limits of the view volume along the z-axis. (Note that this is NOT exactly the same as the parameters in either glOrtho or glFrustum! Most important to note is that zmin and zmax are given with reference to the view center, not the eye. They are not the same as the near and far parameters in glOrtho or glFrustum.) Note that xmin/xmax or ymin/ymax might be adjusted to match the aspect ratio of the viewport. The default limits are (-5,5,-5,5,-10,10).

camera.setScale(s) — Shorthand for camera.setLimits(-s,s,-s,s,-2*s,2*s);

camera.setPreserveAspect(preserve) — The parameter is a boolean which says whether or not to respect the aspect ratio of the viewport. The default is true. If the value is true, then the requested limits will be adjusted, if necessary, to match the aspect ratio of the viewport.

camera.setOrthographic(ortho) — The parameter is a boolean which says whether the projection should be a perspective or an orthographic projection. The default is perspective.

To use the trackball rotation feature, just call the function

camera.installTrackball(displayFunction) — This installs a mouse listener on the canvas that is used by the current OpenGL context. (So, it must be called after creating the context with glsimUse.) When the user drags the mouse on the canvas, the listener responds by modifying the camera's viewpoint in a way that simulates rotating the scene about the point (0,0,0). Note that only rotation about the origin is supported. The parameter is a function that will be called each time the view is modified. Presumably, it will be a function that redraws the entire scene and that includes a call to camera.apply().