3D graphics tutorial

Generating objects
My sister's handmade cards on Etsy.
Drawing of a pile of animals

So we have a cube now, but what if we want to change its position or size? Or what if we want a rectangular cuboid or many cuboids? With our current code we would have to change the nodes one-by-one, which would be nuisance. What we would like is a simple method to create a cuboid with a certain position and dimensions. In other words, we want a function that maps a position and dimensions into an array or nodes and an array of edges.

Defining a cuboid

Your browser doesn't support canvas.

A cuboid with width 100, height 160 and depth, 50.



A cuboid has three dimensions: width, height and depth. It also has a position in 3D space, giving us six parameters. There are a couple of ways we could define the position of the cube: we could define its centre or we could define one corner. The former is probably more common, but I think the latter is easier to use.

Our function needs to return both the nodes and edges array. One way to return two variables is to package the variables into an associative array. An associative array is created using braces '{' and '}'. Inside, a string is associated with a variable, for example, the string 'nodes' is associated with the variable nodes. Note, you can use any string to refer to the variable, I just find it easier to use the same word.

// Create a cuboid with a vertex at (x, y, z)
// with width, w, height, h, and depth, d.             
var createCuboid = function(x, y, z, w, h, d) {
    var nodes = [];
    var edges = [];
    var object = { 'nodes': nodes, 'edges': edges };
    return object;
};

We can now access the variable nodes like this:

var node0 = object.nodes[0];

This will set node0 to equal the first value in the nodes array. If it's still not clear, it hopefully will become obvious once we start to use it. At the moment, however, there are no values in the nodes or edges arrays.

We define the nodes as being every combination of position with or without the corresponding dimension. Edges are define the same was as before (except rather than define each of the edges individually first, I define them all at once). Note that this function allows you to specific negative dimensions for the cuboid.

var createCuboid = function(x, y, z, w, h, d) {
    var nodes = [[x,   y,   z  ],
                 [x,   y,   z+d],
                 [x,   y+h, z  ],
                 [x,   y+h, z+d],
                 [x+w, y,   z  ],
                 [x+w, y,   z+d],
                 [x+w, y+h, z  ],
                 [x+w, y+h, z+d]];
    var edges = [[0, 1], [1, 3], [3, 2], [2, 0],
                 [4, 5], [5, 7], [7, 6], [6, 4],
                 [0, 4], [1, 5], [2, 6], [3, 7]];
    return { 'nodes': nodes, 'edges': edges };
};

We can then create a cuboid with width 100, height 160, depth 50 and one node on the origin like this:

var object = createCuboid(100, 160, 50, 0, 0, 0);

We need to make sure the variable nodes and edges are defined, so add:

var nodes = object.nodes;
var edges = object.edges;

You can see the complete code here.

Working with multiple objects

We can create objects with different dimensions, what if we want more than one? Whenever we want a variable number of things, an array is useful, so lets create an array of objects.

var object1 = createCuboid(-120, -20, -20, 240, 40, 40);
var object2 = createCuboid(-120, -50, -30, -20, 100, 60);
var object3 = createCuboid( 120, -50, -30,  20, 100, 60);
var objects = [object1, object2, object3];

Now we need to change the display and rotate functions to work with an array of objects. Start by wrapping the code to display edges in a for loop that loops through all the object:

// Draw edges
stroke(edgeColour);

for (var obj = 0; obj < objects.length; obj++) {
    var nodes = objects[obj].nodes;
    var edges = objects[obj].edges;

    for (var e = 0; e < edges.length; e++) {
        var n0 = edges[e][0];
        var n1 = edges[e][1];
        var node0 = nodes[n0];
        var node1 = nodes[n1];
        line(node0[0], node0[1], node1[0], node1[1]);
    }   
}

And a similar for loop for displaying nodes:

// Draw nodes
fill(nodeColour);
noStroke();

for (var obj = 0; obj < objects.length; obj++) {
    var nodes = objects[obj].nodes;

    for (var n = 0; n < nodes.length; n++) {
        var node = nodes[n];
        ellipse(node[0], node[1], nodeSize, nodeSize);
    }
}

We could add a similar for loop to each of the rotate functions, but I think it's more flexible to add pass the array of nodes to each function - that way we can rotate objects independently of one another. For example, the rotateZ3D() function would look like this:

var rotateZ3D = function(theta, nodes) {
...
};
Three cuboids
Three cuboids acting as one object.

Now when we use the mouse to rotate, we have to loop through the objects and call the function for each one:

var mouseDragged = function() {
    var dx = mouseX - pmouseX;
    var dy = mouseY - pmouseY;
    
    for (var obj = 0; obj < objects.length; obj++) {
        var nodes = objects[obj].nodes;
        rotateY3D(dx, nodes);
        rotateX3D(dy, nodes);
    }
};

Make sure you remove any other calls to the rotate functions that do not pass in any nodes. You can see the complete code here.