Molehill Spinning Cube
There was quite a meltdown of posts after the release of Molehill yesterday. Unfortunately, most of the posts were either “our huge 3D framework now supports Molehill” or “hey check out this awesome demo I made and no you can’t see the source.” Michael Baczynski posted a great 2D example, though.
Luckily, I’ve done this 3D thing a few times. In the spirit of getting people started, here’s an example of a spinning 3D cube using the new Molehill APIs:
Okay, maybe I added some tweening. This is Flash, after all.
You can view the source here:
https://gist.github.com/847106
You’ll also need this guy from Adobe (throw it into a com/adobe/utils
folder):
https://gist.github.com/847108
I use the command line Flex SDK on Mac. It took a few steps to get everything up and running so I could build Molehill code. Here’s what you need to do:
- Download and install the incubator release of Flash Player 11
- Download build 19786 from the Flex SDK Hero stable builds table
- Extract the SDK somewhere (e.g.
/Developer/SDKs/flex_sdk_4.5
) - Create a new folder in the SDK:
mkdir /Developer/SDKs/flex_sdk_4.5/frameworks/libs/player/11.0
- Download the SWC for Flash Player 11 and move it into the new
11.0
folder you created. Name itplayerglobal.swc
. - Compile using the
bin/mxmlc
from this new SDK, and add-swf-version
and-target-player
to the command line parameters:mxmlc -swf-version=13 -target-player=11.0.0 -o bin/cube.swf src/Cube.as
I’ll highlight a few parts of the code.
_stage = stage.stage3Ds[0];
_stage.addEventListener(Event.CONTEXT3D_CREATE, onContext);
_stage.requestContext3D(Context3DRenderMode.AUTO);
_stage.viewPort = new Rectangle(0, 0, _width, _height);
The first thing you need to do is request a 3D rendering context. The stage
will fire a CONTEXT3D_CREATE
event once the context is ready for you. You
can also define the viewport size you want to use for that context.
_context = _stage.context3D;
_context.configureBackBuffer(_width, _height, 2, true);
_context.enableErrorChecking = true;
Once the context is ready, you need to initialize it. configureBackBuffer
allows you to set the pixel width and height of the buffer, the amount of
anti-aliasing, and whether or not you want depth and stencil buffers.
enableErrorChecking
will allow the context to throw errors when you make
calls to context.clear()
or context.drawTriangles()
. This will, however,
cause a bit of a performance hit, as it forces these calls to block.
_projection = perspectiveProjection(60, _width / _height, 0.1, 2048);
You’ll need to create a projection matrix to transform your vertexes. I tried
to get this working with the old flash.geom.PerspectiveProjection
class, but
it just would not behave. I ended up rolling my own and life got a lot easier.
This demo uses standard OpenGL axes: * -x / +x = left / right * -y / +y = down / up (opposite what you’re used to in 2D) * -z / +z = far / near
_vertexBuffer = _context.createVertexBuffer(_cubeVertexes.length / 6, 6);
_vertexBuffer.uploadFromVector(_cubeVertexes, 0, _cubeVertexes.length / 6);
_context.setVertexBufferAt(0, _vertexBuffer, 0, Context3DVertexBufferFormat.FLOAT_3);
_context.setVertexBufferAt(1, _vertexBuffer, 3, Context3DVertexBufferFormat.FLOAT_3);
_indexBuffer = _context.createIndexBuffer(_cubeIndexes.length);
_indexBuffer.uploadFromVector(_cubeIndexes, 0, _cubeIndexes.length);
You’ll be using vertex and index buffers for everything in Molehill. The
vertex buffer I use in this example has the format (x, y, z, r, g, b). The
setVertexBufferAt
calls are supplying this data to the vertex program as
two separate tuples: va0
= (x, y, z) and va1
= (r, g, b).
The index buffer is created, but isn’t used until later.
var vsAssembler:AGALMiniAssembler = new AGALMiniAssembler;
vsAssembler.assemble(Context3DProgramType.VERTEX, [
"m44 op, va0, vc0", // multiply vertex by modelViewProjection
"mov v0, va1" // copy the vertex color
].join("\n"));
var fsAssembler:AGALMiniAssembler = new AGALMiniAssembler;
fsAssembler.assemble(Context3DProgramType.FRAGMENT, [
"mov oc, v0" // output the fragment color
].join("\n"));
_program = _context.createProgram();
_program.upload(vsAssembler.agalcode, fsAssembler.agalcode);
_context.setProgram(_program);
This code is just setting up some bare bones vertex and fragment shaders. The vertex shader transforms the vertex, and sends the color data down to the fragment shader. The fragment shader just uses the color directly.
Once all the setup is done, there are just a few things to be done each frame.
_context.setProgramConstantsFromMatrix(
Context3DProgramType.VERTEX, 0, _modelViewProjection, true);
Once I’ve concatenated the modelView and projection matrices, this line makes
the matrix available to the vertex shader as a constant (vc0
).
_context.clear();
_context.drawTriangles(_indexBuffer);
_context.present();
These three lines are pretty straightforward: clear the screen, draw the
triangles specified by the given index buffer, then get it all onto the
screen. Note that clear
defaults to black, but allows you to supply a
color and clear value for the various buffers. drawTriangles
also defaults
to drawing all triangles, but can optionally take an offset and limit.