OB3D Details

The purpose of this chapter is to fill in additional details of Jitter OpenGL, which we refer to as OB3Ds.

We will show how to disable and/or override default OB3D attributes and methods, how to support matrix input and output, and manage resources such as textures, display lists, and shaders. This chapter assumes familiarity with the OpenGL API and the OB3D Quick Start chapter. It is out of the scope of our documentation to cover the OpenGL API, so for information on the OpenGL API we recommend consulting the OpenGL Red Book and the many online tutorials.

Defining the OB3D Jitter Class

As covered in the OB3D Quick Start, Jitter OB3Ds have a large number of default attributes and methods, and require some specific methods to be defined. This section seeks to clarify these common attributes and methods and how to achieve custom behavior where necessary.

Declaring a Draw Method

All Jitter OB3Ds must define a method bound to the symbol ob3d_draw. This method takes no arguments in addition to the object struct, and should be defined with the private A_CANT type signature. The private ob3d_draw method will be called by the standard draw, and drawraw methods that are added to every OB3D. The draw method will set up OpenGL state associated with the default OB3D attributes before calling ob3d_draw, while the drawraw method will not.

Declaring Destination and Geometry Related Methods

It is possible for attributes of a Jitter OB3D or your render destination to change, requiring resources to be freed or rebuilt. There are three methods used to communicate to an OB3D which such events happen so that the OB3D can manage resources accordingly. They are: dest_closing, which informs an OB3D that the destination is being freed, and any context dependent resources such as textures, display lists, and shaders should be freed; dest_changed, which informs an OB3D that the destination has been rebuilt, and new resources can be allocated; and rebuild_geometry, which informs an OB3D of a change in texture units or some other attribute which affects jit_gl_drawinfo_setup() and other t_jit_gl_drawinfo related functions, such as jit_gl_texcoord, requiring geometry that uses such functions to be rebuilt. These methods take no arguments in addition to the object struct. The dest_closing and dest_changed methods should be defined with the private A_CANT type signature, and the rebuild_geometry method is typically defined as typed, but without arguments, so that users have the ability to explicitly call, if deemed necessary. The jit.gl.gridshape SDK project is a good example of these methods as it needs to free and allocate a display list as the render destination changes, and also makes use of jit_gl_texcoord to support multi-texturing, requiring geometry to be rebuilt as the number of texture units or other attributes change.

Declaring a Register Method

Since all Jitter OB3D objects are named to support reference by name in jit.gl.sketch, and other objects, it is necessary to add the default registration method, jit_object_register(). Object registration and notification are covered in detail in a future chapter.

Overriding Rotation and Scale Related Attributes

By default, each Jitter OB3D has rotate, rotatexyz, scale, and viewalign attributes added to the class by jit_ob3d_setup(), and these attributes are used in the ob3d_draw_preamble() function to set up OpenGL state prior to calling your object's draw method. These attributes can be disabled by using the JIT_OB3D_NO_ROTATION_SCALE flag. You can override these attributes by defining your own attributes of the same name, however, you will need to manage any necessary OpenGL state inside of your own draw method with the appropriate calls to glMatrixMode, glTranslate, glRotate, and glScale.

Overriding Color Related Attributes

By default, each Jitter OB3D has color, aux_color, and smooth_shading attributes added to the class by jit_ob3d_setup(), and these attributes are used in the ob3d_draw_preamble function prior to calling your object's draw method. These attributes can be disabled by using the JIT_OB3D_NO_COLOR flag. You can override these attributes by defining your own attributes of the same name, however, you will need to manage any necessary OpenGL state inside of your own draw method with the appropriate calls to glColor and glShadeModel.

Overriding Texture Related Attributes

By default, each Jitter OB3D has texture, capture, tex_map, tex_plane_s, and tex_plane_t attributes added to the class by jit_ob3d_setup(), and these attributes are used in the ob3d_draw_preamble() function prior to calling your object's draw method. These attributes can be disabled by using the JIT_OB3D_NO_TEXTURE flag. You can override these attributes by defining your own attributes of the same name, however, you will need to manage any necessary OpenGL state inside of your own draw method with the appropriate calls to glEnable, glTexGen, jit_gl_bindtexture, jit_gl_unbindtexture, jit_gl_begincapture, and jit_gl_endcapture.

Overriding Lighting and Material Related Attributes

By default, each Jitter OB3D has lighting_enable, auto_material, shininess, mat_ambient, mat_diffuse, mat_specular, and mat_emission attributes added to the class by jit_ob3d_setup(), and these attributes are used in the ob3d_draw_preamble function prior to calling your object's draw method. These attributes can be disabled by using the JIT_OB3D_NO_LIGHTING_MATERIAL flag. You can override these attributes by defining your own attributes of the same name, however, you will need to manage any necessary OpenGL state inside of your own draw method with the appropriate calls to glEnable, glLight, glLightModel, and glMaterial.

Overriding Fog Related Attributes

By default, each Jitter OB3D has fog and fog_params attributes added to the class by jit_ob3d_setup(), and these attributes are used in the ob3d_draw_preamble function prior to calling your object's draw method. These attributes can be disabled by using the JIT_OB3D_NO_FOG flag. You can override these attributes by defining your own attributes of the same name, however, you will need to manage any necessary OpenGL state inside of your own draw method with the appropriate calls to glEnable, glHint, and glFog.

Overriding Polygon Variable Related Attributes

By default, each Jitter OB3D has poly_mode, cull_face, point_size, and line_width attributes added to the class by jit_ob3d_setup(), and these attributes are used in the ob3d_draw_preamble function prior to calling your object's draw method. These attributes can be disabled by using the JIT_OB3D_NO_POLY_VARS flag. You can override these attributes by defining your own attributes of the same name, however, you will need to manage any necessary OpenGL state inside of your own draw method with the appropriate calls to glPolygonMode, glEnable, glCullFace, glPointSize, and glLineWidth.

Overriding Blending Related Attributes

By default, each Jitter OB3D has blend_mode and blend_enable attributes added to the class by jit_ob3d_setup(), and these attributes are used in the ob3d_draw_preamble function prior to calling your object's draw method. These attributes can be disabled by using the JIT_OB3D_NO_BLEND flag. You can override these attributes by defining your own attributes of the same name, however, you will need to manage any necessary OpenGL state inside of your own draw method with the appropriate calls to glEnable and glBlendFunc.

Overriding Depth Buffer and Antialiasing Related Attributes

By default, each Jitter OB3D has depth_enable and antialias attributes added to the class by jit_ob3d_setup(), and these attributes are used in your ob3d_draw_preamble function prior to calling your object's draw method. These attributes can be disabled by using the JIT_OB3D_NO_DEPTH and JIT_OB3D_NO_ANTIALIAS flags, respectively. You can override these attributes by defining your own attributes of the same name, however, you will need to manage any necessary OpenGL state inside of your own draw method with the appropriate calls to glEnable and glHint.

Overriding Matrixoutput and Automatic Attributes

By default, each Jitter OB3D has matrixoutput and automatic attributes added to the class by jit_ob3d_setup(), and these attributes are used in the ob3d_draw_preamble function prior to calling your object's draw method. These attributes can be disabled by using the JIT_OB3D_NO_MATRIXOUTPUT and JIT_OB3D_AUTO_ONLY flags, respectively. You can override these attributes by defining your own attributes of the same name.

Declaring a User Interface Object

It is possible to declare a user interface OB3D, such as jit.gl.handle. To do so, you must use the JIT_OB3D_DOES_UI flag to jit_ob3d_setup(), and define a method bound to the symbol ob3d_ui, with the private A_CANT type signature and prototype similar to the following example from jit.gl.handle:

t_jit_err jit_gl_handle_ui(t_jit_gl_handle *x,
t_line_3d *p_line, t_wind_mouse_info *p_mouse);

The Jitter Class Constructor and Destructor

Inside your Jitter class constructor, you must call jit_ob3d_new() with a pointer to your newly allocated object, and your render destination name. The jit_ob3d_new() function allocates an opaque structure that stores the standard OB3D attributes and some additional OB3D state, initializing them to default values, and then setting the pointer at the byte offset specified when calling the jit_ob3d_setup() function in your class definition. If your object supports matrix output or simply uses the t_jit_glchunk structure when drawing, you should typically allocate your initial t_jit_glchunk in your constructor using the jit_glchunk_new() or jit_glchunk_grid_new() functions. Use of the t_jit_glchunk structure and matrix output is described later in this chapter. Similarly, your OB3D Jitter class destructor must call jit_ob3d_free() to free the opaque structure used for common OB3D state, free any allocated instances of t_jit_glchunk with jit_glchunk_free(), and free any other resources allocated such as display lists or textures.

The OB3D Draw Method

The ob3d_draw method is where all the drawing in your object should take place. It is also where you should typically allocate context dependent resources or query the context state, since you know that your context is valid and has been set. For the most part, the drawing you will perform in your ob3d_draw method will be pure and simple OpenGL, though there are a few caveats which we will cover.

The t_jit_glchunk Structure and Matrix Output

Since Jitter is a general purpose matrix processing framework, it makes sense that you would have the ability to pass geometry information through a Jitter network as matrices if your geometry is well suited to a matrix representation. The cells of your matrix can hold vertex information such as position, texture coordinates, normal vectors, color, and edge flags, and are documented in the "Geometry Under The Hood" Jitter Tutorial. You also have the option of specifying a connections matrix to reference the connectivity of the vertices if it is not implicit in the matrix representation, and a drawing primitive to use when drawing the vertices.

All this information, and whether or not the geometry matrix should be rendered immediately or sent through the Jitter network is managed with the t_jit_glchunk. An SDK example which demonstrates the use of t_jit_glchunk is jit.gl.gridshape. The t_jit_glchunk structure along with the vertex matrix it contains is allocated by the jit_glchunk_new() or jit_glchunk_grid_new() functions, freed with the jit_glchunk_delete() function, and drawn with the jit_ob3d_draw_chunk() function. For reference, the t_jit_glchunk structure and relevant chunk flags are provided below:

// jit_glchunk is a public structure to store one
// gl-command's-worth of data, in a format which
// can be passed easily to glDrawRangeElements.
typedef struct _jit_glchunk
{
t_symbol *prim; // GL_TRI_STRIP, GL_TRIANGLES, etc.
t_jit_object *m_vertex; // jit_matrix of xyzst... data.
t_symbol *m_vertex_name; // vertex matrix name
t_jit_object *m_index; // optional 1d connection matrix
t_symbol *m_index_name; // connection matrix name
unsigned long m_flags; // special flags
void *next_chunk; // singly linked list, typically NULL
// flags for chunk creation
#define JIT_GL_CHUNK_IGNORE_TEXTURES 1 << 0
#define JIT_GL_CHUNK_IGNORE_NORMALS 1 << 1
#define JIT_GL_CHUNK_IGNORE_COLORS 1 << 2
#define JIT_GL_CHUNK_IGNORE_EDGES 1 << 3

OB3D OpenGL Caveats

While you can use any standard Open GL calls inside of your ob3d_draw method. There are a few things worth noting to follow Jitter conventions. The first of which is the binding of texture coordinates. Since Jitter OB3Ds support multi-texturing by default, it is not necessarily satisfactory to submit only one texture coordinate with glTexCoord. Jitter provides some utility routines to set the texture coordinates for as many texture units which are bound, jit_gl_texcoord(1/2/3)(f/fv). Determining how many texture units have been bound by the default OB3D attributes requires some overhead, so rather than perform this overhead with every jit_gl_texcoord call, the jit_gl_texcoord functions take a t_jit_gl_drawinfo struct as an argument. This struct can be setup once before rendering many vertices with the jit_gl_drawinfo_setup function. Example use of jit_gl_texcoord and jit_gl_drawinfo_setup is in the jit.gl.videoplane SDK project. Another Jitter specific mechanism is the means to bind textures using named instances of jit.gl.texture. It is possible to create and bind your own textures in an OB3D, but you must then perform all maintenance instead of relying on jit.gl.texture to handle this work for you. To bind and unbind an instance of jit.gl.texture, you should call the jit_gl_bindtexture and jit_gl_unbindtexture functions, which take a t_jit_gl_drawinfo argument, a symbol with the name of the jit.gl.texture instance, and an integer for which texture unit to bind. Unlike binding ordinary textures in OpenGL, it is important to unbind instances of jit.gl.texture, or else problems may arise.

Getting Information About the OB3D Attributes

Though the default OB3D attributes are typically relevant to the code which is automatically handled for your object prior to calling the ob3d_draw method, it is sometimes necessary to access these values. Since the default OB3D attributes are stored in an opaque ob3d struct member, they are not accessible by your object with a simple struct pointer dereference. Instead, you need to use the jit_attr_get* functions to access these attributes. You should pass in your object struct as the first argument to these functions rather than your ob3d struct member. For example:

float pos[3];
jit_attr_getfloat_array(x,gensym("position"),3,pos);

Note that if you are acquiring this value often, it is preferable to generate the symbol in advance rather than generate the symbol for every call.

Getting Information About the Context

From within the ob3d_draw, dest_closing, and dest_changed methods, the rendering context has always been set, and you can get a handle to the native context using either the aglGetCurrentContext or wglGetCurrentContext functions. One can also in these methods use standard OpenGL glGet* functions to determine the context's OpenGL state, such as the viewport, transformation matrix. It is not recommended to try and acquire the native context from other methods, or query the OpenGL state as it may not be valid.

Playing Well with Others

It is important to recognize that OpenGL state is persistent, and that there may be objects which rely on OpenGL state that are drawn after your object draws itself. If your object makes any changes to OpenGL state that might affect objects that follow, you should restore the OpenGL state to whatever it was before your routine was called. For example, if your object changes the texture transformation matrix, you should push and pop the texture transformation matrix with glMatrixMode, glPushMatrix, and glPopMatrix, to prevent any problems with other objects.

Defining the OB3D Max Wrapper Class

As mentioned in the OB3D Quick Start, in your Max wrapper class definition, you need only add a call to the max_ob3d_setup() function to add your standard drawing methods, and the max_jit_ob3d_assist() function as your assist method, unless you wish to define your own custom assist method. Everything else is similar to the standard technique of wrapping a Jitter Class demonstrated in the Max Wrapper Class chapter. Please consult the OB3D Quick Start chapter and the jit.gl.simple SDK project for all necessary information related to the OB3D Max wrapper class.

Matrix Input

Sometimes it is desirable for an OB3D also support incoming matrices as is the case with jit.gl.videoplane or jit.gl.mesh. It is not recommended to mix and match OB3Ds with MOPs. Conflicts arise with respect to arguments, standard inlets and outlets. Instead, if you wish to support matrix input in your OB3D, you should simply add to your Jitter class a method bound to the symbol jit_matrix, and handle the incoming matrix data according to your needs - for example as texture data in the case of jit.gl.videoplane, or geometry data in the case of jit.gl.mesh. The jit.gl.videoplane SDK project provides an example of an OB3D which also supports matrix input. When it is necessary to have multiple input matrices, this is typically managed by either declaring alternately named methods for each input, or exposing an attribute that specifies which input the jit_matrix method assumes it is being called with. Note that this requires additional logic within the Max wrapper class to map to inlets, as it is not handled automatically.

  Copyright © 2015, Cycling '74