A lot of my projects involve making 3D models, so I wanted to have an easy way to display them on this blog. After all, it’s 2014, and we shouldn’t be confined to mere static renderings. My solution is not revolutionary by any means, but I want to describe it anyway.
First, here’s the finished result. Click on the banana to spin it:
The octopress markup for that is:
1
|
|
Some of the fancybox integration is done using this plugin. It must be installed in order for my plugin to work. But instead of loading an image, my plugin loads an iframe containing the model. This has the advantage of keeping the page itself small, deferring the loading of the model and of three.js itself until the user clicks on the thumbnail.
Three.js does all the work of interfacing with WebGL and displaying the model. The main question I had to answer was, what format should I use to get the models into three.js in the first place? All of my models are made in mechanical CAD software, either Solidworks or my former employer SpaceClaim. Mechanical CAD differs from most artistic 3D modeling packages in that it represents models using geometric primitives like planes and cylinders, rather than triangles. In the end, even these models are turned into triangles for rendering. However, one of the things that I wanted was to display the boundaries of the original faces, not the resulting triangles. You can see that in the model of the banana, where the black lines follow the contours of my original lofted surfaces.
So at a minimum I needed a format that would allow me to import both triangles and polylines. This already rules out both STL, the most common triangle interchange format, and Three.js’ own JSON format. The other enhancement that I wanted was to be able to preserve the original assembly hierarchy of my model. In mechanical CAD, we often divide a model into subassemblies, which can be repeated in multiple orientations or only used once. A simple example of that is a bolt, or a wheel on a car. For one thing, assembly modeling cuts down on memory usage, which is especially important in a web context. Secondly, changes to an assembly are much easier if you only have to change a subcomponent, and every copy of it updates automatically.
My soultion was to devise my own enhancement to the Three.js JSON format:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
|
The format is an array of subassemblies, each one containing an array of meshes and lines. The meshes and lines themselves use the
Three.js JSON format, so I’m able to reuse the JSON loader to turn those into Three.js geometry objects. Geometries that come from a
meshes
array get wrapped in a THREE.Mesh
object, ones coming from a lines
array get wrapped in a THREE.Line
.
Assemblies can also include other subassemblies, indexed according to their index in the array. The format itself does nothing to prevent cyclic
references, which would result in infinite recursion when constructing the rendering tree. The transformation
property contains the coefficients for the
THREE.Matrix4
that places the component in its parent.
To generate the JSON, I wrote a plugin for SpaceClaim. This allows me to take any SpaceClaim model (or one that I import into SpaceClaim using one of its many importers) and export it into the format needed to embed it in my blog. I also hope to write a Solidworks plugin so I can skip over SpaceClaim entirely.
There are three files involved in the plugin. The javascript loader is in this gist.
The entry point is the init(file)
function which takes a filename and loads it into the canvas element. The HTML file is
here and doesn’t have much besides a canvas element for the javascript to display the
model in. These files go in the octopress\source\js
directory.
Finally, there is the Octopress plugin which handles insertion of the link into the page. The link is inserted with some javascript to handle the loading so that the iframe HTML doesn’t have to change for each model. The code in the final HTML for the banana above is below, formatted for readability. Every instance of “Banana” in the ids and function names comes from the title:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|