|
Previous Entry: “Making the AnaglyphWebGLRenderer a bit more protruding”
Next Entry: “Using Adobe Open Type Fonts Ornaments In XeTeX” 2012-05-15Three.js and Stella-like polyhedronsWith a framework like Three.js, it is pretty easy to display the faces of simple polyhedrons using WebGL. But a polyhedron, considered less as a physical and more as a topological object, consists not only of its faces. Instead often its vertices and edges are equaly interesting, and it would be nice to have an easy way to display them, perhaps assigning them different colors, to distinguish different kinds of vertices or different kinds of edges. Within the program Stella, vertices can be displayed as small balls and edges as thin tubes, and I wrote a function to be used in conjunction with Three.js which makes it a bit easier to construct similar graphical objects, following a similar visual style, within WebGL. In the following examples, I’ll assume that we have set up all the usual Three.js stuff with renderers and virtual cameras and mouse controls and lights and that we have defined a Scene-object. Furthermore, I’ll assume that we imported my helper file JAN3D.js which contains the function JAN3D.stella() (among other stuff; I also included JAN3D.TinyTrackballControls). I’ll show, using four examples, how to use this function. 1st Example A Simple Cube Our first example is a simple, modest cube. The first thing we have to do is to define its structure. Vertices are described as a list of coordinates, with coordinates as a list of three values. Edges and Faces are lists of indices referencing vertices: an edge is a list of two indices, referencing two vertices, and a face is a list of arbitrary length, also referencing vertices. In the case of a cube, such lists may look like this:
// define the structure of a cube
var vertices = [ [100,100,100],[100,100,-100],[100,-100,100],[100,-100,-100], [-100,100,100],[-100,100,-100],[-100,-100,100],[-100,-100,-100] ]; var edges = [ [0,1], [0,2], [1,3], [2,3], [0,4], [1,5], [2,6], [3,7], [4,5], [4,6], [5,7], [6,7] ]; var faces = [ [0,2,3,1], [0,4,6,2], [0,1,5,4], [7,6,4,5], [7,5,1,3], [7,3,2,6] ]; The next step is to define how the vertices, edges and faces of our cube should look like. This information is stored in Look-objects, and we provide these objects with the necessary parameters: what a radius the balls and tubes representing vertices and edges should have (using the parameter radius), how much subdivisions we should use to display them (using the parameter quality), whether our faces should be one-sided or double-sided (using the boolean parameter doubleSided), and which material should be used (using the parameter material). If we omit one of these parameters, a sensible default will be used. We could use something like this:
// define some colors
var vlook = new JAN3D.Look({ material: new THREE.MeshPhongMaterial({ ambient: 0x660000, color: 0xff0000, specular: 0xff8080, shininess: 50 }) }); var elook = new JAN3D.Look({ material: new THREE.MeshPhongMaterial({ ambient: 0x666600, color: 0xdddd00, specular: 0xffff80, shininess: 50 }) }); var flook = new JAN3D.Look({ material: new THREE.MeshPhongMaterial({ ambient: 0x006666, color: 0x00ffff, specular: 0x80ffff, shininess: 50, opacity: 0.9, transparent: true }), doubleSided: false }); Now we have everything in place to call our function and to add the result to our scene:
// add the object to the scene
scene.add(JAN3D.stella({ vertices: vertices, edges: edges, faces: faces, verticesLook: vlook, edgesLook: elook, facesLook: flook })); Provided your browser has WebGL-capabilities, the (for didactical reasons rather horribly colored) result should look like this: You can rotate the cube using your mouse. If no WebGL capabilities are present, Three.js has a fallback mechanism where it can use an ordinary canvas element. My function can work with the CanvasRenderer (if you replace the MeshPhongMaterials above with something the CanvasRenderer can understand), but since the balls and tubes require a lot of internal faces, this fallback solution can be really slow and unresponsive, so in most cases it is more advisable to use a different fallback solution. The parameters used in the code fragment above should be pretty self-explinatory: we supply the geometry data and the desired looks for the different kinds of sub-polytopes of our polyhedron (“sub-polytopes of a polyhedron” is just a fancy way of saying “vertices, edges and faces”). We could have omitted the parameter edgesLook, in this case, the function would have used the look of the vertices as the default for the edges, and the edges would have been colored red like the vertices, instead of yellow. If we had also omitted verticesLook, a light gray look would have been used as the default look for the vertices and edges. In a certain sense, providing the data for the edges ourselves is kind of redundant, since this data is contained in the data for the faces. We can tell the function to compute the edges for itself, so we could also have used this code instead with the same result:
// add the object to the scene
scene.add(JAN3D.stella({ vertices: vertices, //edges: edges, computeEdges: true, faces: faces, verticesLook: vlook, edgesLook: elook, facesLook: flook })); As a default, the function displays those subpolytopes for which it has data. If we had omitted the parameter faces, it would have displayed the vertices and edges, but no faces. Edges are displayed per default if they are either provided, or computing them from the data of the faces gets requested. Sometimes, it may not be desired to display certain subpolytopes (for example, you always have to provide data for the vertices, since they are the base for everything else, but you may wish not to display the vertices as tiny balls). In this case, the display can be switched on and off with the boolean parameters showVertices, showEdges and showFaces. 2nd Example A Visit From An Octahedron I’ll show why it may make sense to define edges as their own data structure. Our next example contains both a cube and its dual, an octahedron. I want to display them as a compound, and since it can be a bit confusing in a compound which vertices, edges and faces belong to which part, they should be distinctly colored: the cube in blue and the octahedron in orange. A straightforward way to do this is to define them as two separate objects, like this:
// Cube
var vertices = [ [100,100,100],[100,100,-100],[100,-100,100],[100,-100,-100], [-100,100,100],[-100,100,-100],[-100,-100,100],[-100,-100,-100] ]; var edges = [ [0,1], [0,2], [1,3], [2,3], [0,4], [1,5], [2,6], [3,7], [4,5], [4,6], [5,7], [6,7] ]; var faces = [ [0,2,3,1], [0,4,6,2], [0,1,5,4], [7,6,4,5], [7,5,1,3], [7,3,2,6] ]; scene.add(JAN3D.stella({ vertices: vertices, edges: edges, faces: faces, verticesLook: new JAN3D.Look({ material: new THREE.MeshPhongMaterial({ ambient: 0x003366, color: 0x0080ff, specular: 0x80c0ff, shininess: 50 }), }), facesLook: new JAN3D.Look({ material: new THREE.MeshPhongMaterial({ ambient: 0x003366, color: 0x0080ff, specular: 0x80c0ff, shininess: 50, opacity: 0.5, transparent: true }), doubleSided: false }) })); // Octahedron vertices = [ [0,0,200],[0,0,-200],[0,200,0],[0,-200,0],[200,0,0],[-200,0,0] ]; edges = [ [0,2], [0,3], [0,4], [0,5], [1,2], [1,3], [1,4], [1,5], [2,4], [2,5], [3,4], [3,5] ]; faces = [ [0,4,2], [0,2,5], [0,3,4], [0,5,3], [1,2,4], [1,5,2], [1,4,3], [1,3,5] ]; scene.add(JAN3D.stella({ vertices: vertices, edges: edges, faces: faces, verticesLook: new JAN3D.Look({ material: new THREE.MeshPhongMaterial({ ambient: 0x663300, color: 0xff8000, specular: 0xffc080, shininess: 50 }), }), facesLook: new JAN3D.Look({ material: new THREE.MeshPhongMaterial({ ambient: 0x663300, color: 0xff8000, specular: 0xffc080, shininess: 50, opacity: 0.5, transparent: true }), doubleSided: false }) })); Once again, I could have spared me the definition of the edges. Anyway, the result looks like this: But now I want to define the compound as a single object. I’ll define the vertices, edges and faces of both parts of the compound share as single data structures:
// Cube-Octahedron-Compound
var vertices = [ [100,100,100],[100,100,-100],[100,-100,100],[100,-100,-100], [-100,100,100],[-100,100,-100],[-100,-100,100],[-100,-100,-100], [0,0,200],[0,0,-200],[0,200,0],[0,-200,0],[200,0,0],[-200,0,0] ]; var edges = [ [0,1], [0,2], [1,3], [2,3], [0,4], [1,5], [2,6], [3,7], [4,5], [4,6], [5,7], [6,7], [8,10], [8,11], [8,12], [8,13], [9,10], [9,11], [9,12], [9,13], [10,12], [10,13], [11,12], [11,13] ]; var faces = [ [0,2,3,1], [0,4,6,2], [0,1,5,4], [7,6,4,5], [7,5,1,3], [7,3,2,6], [8,12,10], [8,10,13], [8,11,12], [8,13,11], [9,10,12], [9,13,10], [9,12,11], [9,11,13] ]; But I still want the individual parts of the compound to have different colors, so I define the corresponding looks:
// some colors
// solid blue blook = new JAN3D.Look({ material: new THREE.MeshPhongMaterial({ ambient: 0x003366, color: 0x0080ff, specular: 0x80c0ff, shininess: 50 }) }); // transparent blue for the faces bflook = new JAN3D.Look({ material: new THREE.MeshPhongMaterial({ ambient: 0x003366, color: 0x0080ff, specular: 0x80c0ff, shininess: 50, opacity: 0.5, transparent: true }), doubleSided: false }); // solid orange ylook = new JAN3D.Look({ material: new THREE.MeshPhongMaterial({ ambient: 0x663300, color: 0xff8000, specular: 0xffc080, shininess: 50 }), }); // transparent orange for the faces yflook = new JAN3D.Look({ material: new THREE.MeshPhongMaterial({ ambient: 0x663300, color: 0xff8000, specular: 0xffc080, shininess: 50, opacity: 0.5, transparent: true }), doubleSided: false }); Now instead of defining a single look, I define arrays of possible looks via the parameters verticesLooks, edgesLooks or facesLooks. And with the parameters verticesLookMap, edgesLookMap and facesLookMap, I can define associative arrays which map individual polytopes to individual looks, like this:
scene.add(JAN3D.stella({
vertices: vertices, edges: edges, faces: faces, verticesLooks: [blook, ylook], edgesLooks: [blook, ylook], facesLooks: [bflook, yflook], verticesLookMap: {0:0, 1:0, 2:0, 3:0, 4:0, 5:0, 6:0, 7:0, 8:1, 9:1, 10:1, 11:1, 12:1, 13: 1}, edgesLookMap: {0:0, 1:0, 2:0, 3:0, 4:0, 5:0, 6:0, 7:0, 8:0, 9:0, 10:0, 11:0, 12:1, 13:1, 14:1, 15:1, 16:1, 17:1, 18:1, 19:1, 20:1, 21:1, 22:1, 23:1}, facesLookMap: {0:0, 1:0, 2:0, 3:0, 4:0, 5:0, 6:1, 7:1, 8:1, 9:1, 10:1, 11:1, 12:1, 13:1} })); This results in the same visual appearance of a blue cube and an orange octahedron, since it maps individual parts to their according looks. Once again, we could omit the definition of the looks for the edges, and the function would take the looks array of the vertices instead:
scene.add(JAN3D.stella({
vertices: vertices, edges: edges, faces: faces, verticesLooks: [blook, ylook], //edgesLooks: [blook, ylook], facesLooks: [bflook, Defined tags for this entry: Javascript, WebGL
Add Comment
| QuicksearchRecent Entries
ArchivesLatest Skizzenblog Entry |