MG Siegler has an excellent post about why he hates Android. My feelings are much the same, but that’s not the point of this post. At one point, he mentions Google’s original hopes for the Nexus One:
Google intended to blow up the carrier market (in the U.S. first) by moving phone distribution online, flattening it in the process. The idea was that you’d go to a website and pick the phone you wanted, then pick the carrier you wanted, pay, and you’d be done. […] Or, you could buy whatever phone you wanted unlocked. Eventually, pay-as-you-go SIM cards would pop up in the U.S. as a result.
I upgraded to an iPhone 4S over the holidays, and switched from AT&T to Verizon. I did the whole thing from within the Apple Store iOS app. It took about 15 minutes. I bought the phone, signed up for Verizon, and even transferred my existing number from AT&T, without ever needing to talk to a carrier. The first time I heard from Verizon was when I got a pro-rated receipt in the mail after activating my new phone.
It was the best phone purchasing experience I have ever had, by far. It was a prime example of why I like Apple products. Google has been farting around and creating generally inconsistent and mediocre experiences. Apple has been pushing forward and quietly building this wonderful customer experience – one that Google wanted, but failed to create. For all the arguments over open and closed, Apple is the company that consistently gives me the impression that they care about what the customer feels.
You can buy it unlocked and use it with a pay-as-you go plan from T-Mobile, but they still don’t have T-Mobile sign up integrated into the checkout process.
I’ve been wanting to follow up on my last Molehill post with some more advanced examples, and I’ve finally gotten around to it! This is the first of several posts about loading and rendering various model formats in Molehill.
Model formats can be frustrating. Most are ancient or written without hardware rendering in mind. You often have to massage the data a bit to make it usable. It’s interesting to see how formats changed over time as hardware accelerated rendering standardize things. I’m going to start with some older formats, and work up to newer ones.
The Wavefront OBJ format was created for Wavefront’s Advanced Visualizer back in the 80s. It hasn’t seen an update since the 90s, as far I can tell. (This is how things usually go with model formats.) It’s often still used today for static objects, because it’s a relatively simple format, and supported by nearly all modeling programs.
It’s a text-based model format. It supports many esoteric things, but this loader only supports the most common ones: vertex position, normal, and UV coordinates; faces with three or more vertices; and material groups. The OBJ format doesn’t support animations.
Here’s a Gist with an example of a cube in OBJ format. It’s a line based format. The first word of each line specifies the command, and parameters to the command follow. Lines starting with a # are comments.
o cube: Sets the object name. There is only one object per OBJ file.
mtllib cube.mtl: Material file to use for this model file. This example loader doesn’t support MTL files, so you will have to set the material textures manually by name.
v -0.500000 -0.500000 0.500000: Vertex position (X, Y and Z coordinates).
g cube: Starts a new face group. Each face group has its own set of indexes and a material. The OBJ file may have more than one of these.
usemtl cube: Specifies the material from the mtllib to use for the face group.
s 1: Sets the smoothing group for a set of faces. Smoothing groups control how vertex normals are generated for a model. I’m ignoring this statement in my loader, since most game models have normals exported for them.
f 1/1/1 2/2/1 3/3/1: Indexes for a face. Each vertex has a tuple of indexes: positionIndex/uvIndex/normalIndex. OBJ faces are polygons, not triangles, so they may have more than three vertices.
// Load the model, and set the material textures_obj=newOBJ();_obj.readBytes(newBUNKER_OBJ(),_context);_obj.setMaterial('h_head',_headTexture);_obj.setMaterial('u_torso',_bodyTexture);_obj.setMaterial('l_legs',_bodyTexture);
You’ll note that I set the material textures manually here, since the loader doesn’t handle MTL files.
The OBJ file itself is parsed in OBJ.as, in the readBytes() method. The byte array for the OBJ is passed in, and it has to be converted into text, then read in line by line. Any empty lines or lines starting with a # should be skipped, like so:
vartext:String=bytes.readUTFBytes(bytes.bytesAvailable);varlines:Array=text.split(/[\r\n]+/);foreach(varline:Stringinlines){// Trim whitespace from the lineline=line.replace(/^\s*|\s*$/g,'');if(line===''||line.charAt(0)==='#'){// Blank line or comment, ignore itcontinue;}// TODO: parse the line}
For that todo, you need to split the line up by spaces, and then check what kind of command it is. That can be done like so:
For the group (g command), a new OBJGroup object is created and added to the list of groups. Groups have several properties (name, material, and faces), so the object is useful to keep track of all that.
The material name (usemtl command) just gets saved and assigned to the current group (if there is one). Any future groups get assigned the current material by default, unless they have their own usemtl command.
The group face (f command) is a list of index tuples, as described earlier. A new vector is created to store the index tuples for the face, and the face is added to the current group. It will be processed later.
That’s all of the commands we need to handle. This loop will repeat for all of the lines in the file. Once it’s done, we’ll have several separate streams of vertex data (positions, normals, and uvs). We’ll also have a list of groups, each with its own list of faces, which have indices into these separate streams.
This is a problem. OBJ specifies a separate index for position, normal, and UV, but modern hardware rendering doesn’t support that. We can only have one index stream. To fix this, we need to merge all three vertex streams into a single stream. The face indices also need to be updated to point to the right offsets within this new stream.
To do this, each group gets a new index stream. Then, for each unique index tuple in the faces, we write a new vertex into the merged stream. If we’ve already encountered that index tuple in another face, we use the existing merged index.
The other problem we have is that OBJ allows polygonal faces: that is, faces don’t have to be triangles. This is a problem: Context3D only supports drawing triangles. To fix this, we’ll turn any non-triangles into a triangle fan.
This loop calls mergeTuple for each index tuple in the face. That function looks like this:
protectedfunction mergeTuple(tuple:String,positions:Vector.<Number>,normals:Vector.<Number>,uvs:Vector.<Number>):uint{if(_tupleIndices[tuple]!==undefined){// Already merged, return the merged indexreturn_tupleIndices[tuple];}else{varfaceIndices:Array=tuple.split('/');// Position indexvarindex:uint=parseInt(faceIndices[0],10)-1;_vertices.push(positions[index*3+0],positions[index*3+1],positions[index*3+2]);// Normal indexif(faceIndices.length>2&&faceIndices[2].length>0){index=parseInt(faceIndices[2],10)-1;_vertices.push(normals[index*3+0],normals[index*3+1],normals[index*3+2]);}else{// Face doesn't have a normal_vertices.push(0,0,0);}// UV indexif(faceIndices.length>1&&faceIndices[1].length>0){index=parseInt(faceIndices[1],10)-1;_vertices.push(uvs[index*2+0],uvs[index*2+1]);}else{// Face doesn't have a UV_vertices.push(0,0);}// Cache the merged tuple index in case it's used againreturn_tupleIndices[tuple]=_tupleIndex++;}}
This function is the bulk of the work for the OBJ loader. If the tuple already exists in our tuple indices cache, we return the existing merged index. Otherwise, we copy the vertex data that the face points to into the merged array, and then return the new index.
OBJ doesn’t require that normal and UV are specified, so we just shove some zeroes in there to keep things consistent when that happens. Modern vertex buffers are zero-indexed, but OBJ’s are one-indexed, so we also need to subtract one from all of the indices.
Last but not least, we need to create a vertex buffer for the new stream:
That handles loading the model. Rendering it is pretty straight-forward. The update() function in DemoOBJ.as does some setup, then renders the OBJ like so:
// Draw the model_context.setVertexBufferAt(0,_obj.vertexBuffer,0,Context3DVertexBufferFormat.FLOAT_3);_context.setVertexBufferAt(1,_obj.vertexBuffer,3,Context3DVertexBufferFormat.FLOAT_3);_context.setVertexBufferAt(2,_obj.vertexBuffer,6,Context3DVertexBufferFormat.FLOAT_2);foreach(vargroup:OBJGroupin_obj.groups){_context.setTextureAt(0,_obj.getMaterial(group.materialName));_context.drawTriangles(group.indexBuffer);}
This sets up the vertex buffer streams for the position, normal, and UVs in the OBJ buffer. Then it loops over each group, setting the texture for the group’s material, and drawing the triangles associated with that group.
I hope that helps show how to load and render models in Molehill. I can’t cover all the code in this post, so feel free to comment or email me if you have any questions. If you want to find more OBJ files to mess around with, I recommend looking at the SDK master thread on Polycount.
In my next post, I’ll look at Quake MDL files. This is another arcane format, but it’s binary and has animation, so there is more to learn!
Texty is a JavaScript library from LearnBoost that provides slim, textbox-like editing for the canvas. It provides text input, selection, and caret positioning. It seems like it would be a great starting point if you’re thinking about building a canvas text editor or IDE.
Reds is a light-weight Redis backed search for node.js. It allows you to associate strings with arbitrary IDs, and then search against the strings. It does some stemming and metaphone processing on the words for you, as well.
PhantomJS is a command line tool that gives you a headless browser stack. It uses WebKit under the hood, and allows you to do everything from JavaScript testing to page rendering, all driven via JavaScript, like so:
NodePHP is an experimental project from David Coallier that turns NodeJS into a FastCGI server for PHP. This allows you to use NodeJS as your HTTP server, proxying requests to PHP-FPM in the same way that nginx does.
Between this and Photon, it’s nice to see people trying to innovate in the PHP stack. We were stuck with a legacy PHP stack at InstantAction, and I was considering writing something like this to give us more flexibility. node-fastcgi-parser has appeared since then, and I imagine that makes the job a bit more feasible.
NowJS is a cool abstraction layer that tries to emulate a shared stack between the server (NodeJS) and clients (browsers). You end up with something like this on the server:
package{importflash.display.Bitmap;importflash.display.Sprite;importflash.media.Sound;importflash.utils.ByteArray;publicclassTestextendsSprite{[Embed(source='assets/test.png')]staticpublicvarTEST_BITMAP:Class;[Embed(source='assets/test.mp3')]staticpublicvarTEST_SOUND:Class;[Embed(source='assets/test.xml',mimeType='application/octet-stream')]staticpublicvarTEST_XML:Class;function Test(){// Types Flash knows how to encode becomes normal Flash objects.varbitmap:Bitmap=newTEST_BITMAP;addChild(bitmap);varsound:Sound=newTEST_SOUND;sound.play();// Other types get the octet-stream mimeType, and end up as a// raw ByteArray, which you can read yourself.varbytes:ByteArray=newTEST_XML;vartext:String=bytes.readUTFBytes(bytes.length);varxml:XML=newXML(text);trace(xml);}}}
All of the content in our current game is embedded this way. But, as the game gets closer to release, this setup has been getting frustrating. We need to recompile every time we want to test a change to a level, or image, or sound.
To fix this, I made the debug SWF load things on the fly, and use the embedded assets in release mode. We can save a level file, and restart the level to see the changes immediately – without recompiling or reloading the page.
This required doing several things:
Using reflection to find the embedded file’s path
Downloading the files instead of instantiating the classes
Stripping the debug download code in release mode
Finding the embedded file’s path
Given a Class variable, you can use Flash’s describeType() to get XML information about the variable, including attached tags. For example, running trace(describeType(TEST_BITMAP)) on the above variable would return:
I’ve snipped most of it (the full output is huge), but you can see the relevant metadata element at the bottom. We can use this to figure out the path to load. You can use Flash’s E4X parsing to extract the attribute:
Once you have the paths, you’ll need to use classes like Loader to download the files on the fly. This means you’ll need to serve the assets over HTTP, unless you’re using AIR. I used a Ruby Sinatra server for this:
If you don’t have Ruby, you can download it here. If you don’t have Sinatra, you can install it by running gem install sinatra.
Once it is running, you can test it in a browser. In my example, the URL for test.png would be http://localhost:4567/assets/test.png. Now the ActionScript code needs to load these URLs. I added some helper methods:
If this were real production code, you would want to catch error events. However, since this is just debug test code, I’m only listening for the COMPLETE event.
Now, instead of using new TEST_BITMAP, you call these methods:
I quickly ran into a problem: Flash was only downloading the file once. This was because it was caching the assets. To stop Flash from caching, the HTTP server needs to send Cache-Control and Pragma headers. For a Sinatra server, you can use a Rack middleware:
However, in my case, I didn’t even want the loader code in my SWF in release mode. I did this with the -definecommand line option for mxmlc. This option allows you to do conditional compilation with constants. When I compile in debug mode, my command line looks like this:
The syntax is a bit strange for these conditionals. It’s basically the AS3 version of a C preprocessor #ifdef. If the constant is false, the attached block is completely stripped from the compiled SWF.
When all is said and done, you have a SWF that uses embedded assets with only the overhead of a function call, and assets that are loaded on the fly for debug mode. You could take this even further by doing things like caching, or monitoring files and automatically refreshing them while the SWF is running… but I will leave that for a future post.
Formalize is a CSS framework (with some JS help) that gives you a consistent baseline for form styles, across browsers and operating systems. Think of it as a CSS reset for styled forms.
I’m not sure how I feel about form input styling. There’s a reason things like the HIG exist. That said, if you have a need to style some inputs, this should make your life much easier.
Tempo is a lightweight, JavaScript template renderer. You write your templates inline in your normal HTML, and just tag them with data attributes. For example:
Tempo will hide the fallback, and show the template once rendered. If JavaScript is disabled, the template will remain hidden, and the user will just see the fallback.