In which I present instances of a family of closed-cell geometric objects which are computationally trivial to render.
The Concept
Imagine moving a plane through a three dimensional mesh of cubes.
You start by rotating the plane to your liking. That is, for each pixel on the plane (since that is our rendering reference point) we rotate and translate it as we like, from (x, y, 0) to (x’, y’, z’). To find out which “cell” we’re in, we just look at the integer parts of the prime-coordinates. In the animation above, the cells have been “checkered” by whether their integer coordinates’ sum is even or odd. (Actually, I just used LightWave’s 3d checker texture function. But you get the idea.)
Drawing the checkerboard is easy. Drawing some depth in the cubes is only a little bit trickier. We need a perpendicular vector from the plane, to cast a ray, so we also rotate the perpendicular vector (0,0,1) by the same amount. Then we project that ray from the fractional position within the cell until it hits one of the 6 walls of the cube. Lastly, we give a non-optical depth cue based on how far the intersected wall is.
The Implementation
Here’s the math part of the rotation. There’s probably an easier way to get the 3-axis rotation matrix… But this works.
// concatenate three simple rotations to spinM float3x3 spinXM = float3x3(1,0,0,0,cos(spin.x),spin.x,0,-sin(spin.x),cos(spin.x)); float3x3 spinYM = float3x3(cos(spin.y),0,sin(spin.y),0,1,0,-sin(spin.y),0,cos(spin.y)); float3x3 spinZM = float3x3(cos(spin.z), sin(spin.z), 0,-sin(spin.z), cos(spin.z), 0, 0, 0, 1 ); float3x3 spinM = spinXM * spinYM * spinZM; float2 center = float2(400,500) / 2.0; // YUCK! hardcoded for my blog entry. float2 oc = (outCoord() - center) / zoom; // adjust "input coordinates" // we rotate the requested coordinate by 3 axes, giving the // R3 point on the plane of the screen (after the mesh is turnt) float3 p = float3(oc.x,oc.y,0) * spinM; //oc.x * axis1 + oc.y * axis2; // perp is our viewing line, straight down from the screen. float3 perp = float3(0,0,1) * spinM;
Here is an interactive demo of the algorithm, in Flash and Pixel Bender. (I added a full screen button, whee!)
And here is the Pixel Bender kernel:
And for reference, here is the template for the Flash displayer, compilable by mxmlc.
Flex trivia: stage.addEventListener(FullScreenEvent.FULL_SCREEN,screenChange);
works from mx:Application applicationComplete because the UI and display list is all instanced, but not from mx:Application creationComplete which happens a little bit earlier.
Next up: Suppose we didn’t intersect with a simple plane… !