Gregory Lee Newsome

Index Array API

Index Array API for Jitter is a JavaScript API for controlling the visibility of each face of a surface, specifically a jit.gl.gridshape. In a sense, it’s a filter for a surface, enabling a variety of partial geometry such as hemisphere or lattice. You can control the API via Max message, or if you know JavaScript, anonymous function.

Before we start, it’s helpful to know what an index array is. It’s an array of vertex numbers whose order specifies which vertices connect to form a face. Constructing this array is error-prone and tedious, so normally Jitter does this for us. However, the rightmost inlet of jit.gl.mesh allows us to specify our own index array, and this API does just that to control face visibility.

Download

Index Array API is available on GitHub.

Setup

Create a new Max patcher file and place it in a folder. Locate the file index_array.js from your download and copy it to the same folder. This is important as your .maxpat file must be able to see the .js file.

Next we need a core Jitter setup – world, camera, and two-point lighting. Since we’re going to see the interior of the object, let’s also place a point light at the origin. I’m also including a background gradient, but this is optional (if you want to include it, copy it from example.maxpat).

Surface

We need a surface to operate on, so let’s create a sphere. Since we can only access the index array via jit.gl.mesh, we must first disassemble the sphere with jit.unpack before reassembling it with jit.gl.mesh. Let’s also send a bang every frame to jit.gl.gridshape for convenience.

You should see something like this:

js Object

Now we’re ready to load the API. Since it’s written in JavaScript, we’ll use Max’s js object for this, passing in the file name as the first argument. To generate a new index array, we send a bang to js. The API has to know the surface dimensions, so let’s synchronize these between jit.gl.gridshape and js. Finally, let’s send a bang to pak on patcher load to ensure we’re in sync whenever we open our patcher.

Now you should see this (you may have to send a dimension value to pak first):

You can optionally specify the draw mode, but must synchronize it between jit.gl.mesh and js. See example.maxpat for how to accomplish this.

Now let’s apply a filter.

Filter via Max Message

Controlling face visibility via Max message is pretty straightforward, just create a list with a length equal to the number of faces comprising your surface, and use 0 to hide or 1 to show. Then, send that list to js. For example, if our surface has four faces, the list 1 0 0 0 will show the first face and hide the remaining three. How do we know the number of faces our surface has? Just send a bang to js and this value will be sent out its right outlet.

Let’s start with a random filter. Since a 20 * 20 sphere has hundreds of faces, it’s more practical to generate our list than type it out. We can use uzi as a bang generator, decide to choose 0 or 1, and zl to aggregate the result. Note how we use the face count from js to coordinate uzi and zl.

Press the button object to generate a filter. The result should resemble this:

Filter via JavaScript

If you’re familiar with JavaScript, or just programming in general, you can pass an anonymous function to the API to specify your filter. If your surface has more than 32,767 faces you must use JavaScript, as this is the maximum length of a Max message.

In your project folder, create the file example.js. In your patcher, replace the file argument of js with this, and set its @autowatch attribute to 1. Close and reopen your patcher, then double-click js to open its text editor.

random()

First we need to import index_array.js for core support:

include('index_array.js');

Now let’s recreate our random example from above in JavaScript. If you’re unfamiliar with JavaScript in Max, every function you define is callable by name from the parent patcher via Max message. We’ll name our function random(). Within this enclosing function, we pass our anonymous function to the API’s global filter object via its generate() method, resulting in a new filter according to what we specify in our anonymous function. Before exiting our enclosing function, we must set the API’s global draw flag to true:

function random() {
  g.filter.generate(function() {
    with (Math) {
      return round(random());
    }
  });
  g.draw = true;
}

Save the file.

Now in the parent patcher, send the Max message random to js and you should see a random filter again.

It’s important to understand that the anonymous function is called for every face of the surface.

fill()

What if we want to return to a complete surface? We can define an anonymous function that returns 1 for every face, enclose it in a function called fill(), and send the Max message fill to js:

function fill() {
  g.filter.generate(function() {
    return 1;
  });
  g.draw = true;
}

reverse()

It’s possible to use the current filter to derive a new filter. Use g.filter.mask() to access the current filter, set your new filter with set_mask(), then call apply(). Here, we reverse the current filter with some help from JavaScript’s map() function:

function reverse() {
  var a = g.filter.mask().map(function(bit, i) {
    return 1 - bit;
  });
  g.filter.set_mask(a);
  g.filter.apply();
  g.draw = true;
}

ring()

In JavaScript, when you pass an anonymous function to a function, the anonymous function has access to that function’s internal state. We can use this to our advantage to access g.filter.generate()’s iterator variables i and j, which represent face sector and stack, respectively. By using the modulo operator (%) on the sector iterator, along with the argument count from the parent patcher, we create a ring effect:

function ring(count) {
  g.filter.generate(function() {
    return i % count > 0;
  }, true);
  g.draw = true;
}

Note that we include true as a second argument to g.filter.generate() in this example. Because the default face drawing order is somewhat counterintuitive, you can tell the API to use a more intuitive order, specifically counterclockwise from near to far. Set this value to false to see the effect it has.

spiral()

Let’s finish with the spiral effect seen in this post’s image. Here we use the surface dimensions, the sector iterator, and a variety of operators to craft our filter:

function spiral() {
  g.filter.generate(function() {
    return i < (g.surface.dim()[0] * 
           (i % g.surface.dim()[1])) ? 0 : 1;
  });
  g.draw = true;
}

Wrapping Up

You may be wondering if this API will work with jit.gl.model, and the answer is yes, if a glitch effect is what you’re after. For precise control, jit.gl.gridshape is necessary.

While we didn’t cover animating a filter, it’s definitely possible.

Hopefully this tutorial has given you a sense of how to use Index Array API to craft partial geometry in Jitter. The API and example code have complete documentation, but if you still have questions, you can reach me at greg.newsome@utoronto.ca.