Many of the basic primitives we utilize in 3D can be constructed procedurally. All the way from simple planes to complex shapes.
Today I thought I would really quickly share something I use a lot – a revolution mesh. The idea has been around for ages and can be found most 3D packages i.e. the lathe modifier in 3DSMax and its in as3 3D libs like Away3D I think. The good thing about this technique is that it can be used to produce a number of different shapes all with one function (which makes for a smaller code base as well as flexibility).
The idea is simple, pass in a list of 2D points and rotate them about an axis for any given number of times and then construct a solid mesh out of the result.
First up, a demo:
link to standalone version: here
Okay so not very impressive but all of those objects were created in the same way, just revolving a series of points about and axis (in this instance the y/up axis).
So the code to generate them looks like this:
numRevolutions = 25; revolutions = new Vector.<Object3D>(); revolutions.push(generateRevolutionObject(SplineBuilder.generateTorus(5,2,10))); revolutions.push(generateRevolutionObject(SplineBuilder.generateCircle(5,15))); revolutions.push(generateRevolutionObject(SplineBuilder.generateCone())); revolutions.push(generateRevolutionObject(SplineBuilder.generateTube())); revolutions.push(generateRevolutionObject(SplineBuilder.generateDisc())); revolutions.push(generateRevolutionObject(SplineBuilder.generateCylinder())); revolutions.push(generateRevolutionObject(SplineBuilder.generateArc(5, 5, 0, 0.25))); revolutions.push(generateRevolutionObject(SplineBuilder.generateWave(10,20,5,0.5))); revolutions.push(generateRevolutionObject(SplineBuilder.generateWaveAbs(10,20,5,0.5))); revolutions.push(generateRevolutionObject(SplineBuilder.generateSquareWave(10,20,5,0.5))); revolutions.push(generateRevolutionObject(SplineBuilder.generateLine(5,5,5,-5))); scene.addObjects(revolutions); private function generateRevolutionObject(spline:Vector.<Number>):Object3D { //builds the mesh based on the number of revolutions and the points var mesh:RevolutionMesh = new RevolutionMesh(numRevolutions, spline); var object:Object3D = new Object3D().build(mesh, material); //gives the object a parent transform so can be rotated about the scene origin object.transform.parent = transformer; return object; } |
The magic happens in the SplineBuilder and the RevolutionMesh. The SplineBuilder is just a really simple class with static methods to generate lists of points:
//couple of example functions from SplineBuilder public static function generateCone(height:Number = 10, radius:Number = 5):Vector.<Number> { var spline:Vector.<Number> = new Vector.<Number>(); spline.push(0, height, 0); spline.push(radius, 0, 0); spline.push(0, 0, 0); return spline; } public static function generateCylinder(height:Number = 10, radius:Number = 5):Vector.<Number> { var spline:Vector.<Number> = new Vector.<Number>(); spline.push(0, height/2, 0); spline.push(radius, height/2, 0); spline.push(radius, -height/2, 0); spline.push(0, -height/2, 0); return spline; } public static function generateTube(height:Number = 10, radius:Number = 5):Vector.<Number> { var spline:Vector.<Number> = new Vector.<Number>(); spline.push(radius/2, height/2, 0); spline.push(radius, height/2, 0); spline.push(radius, -height/2, 0); spline.push(radius/2, -height/2, 0); spline.push(radius/2, height/2, 0); return spline; } |
See, super simple! You can of course come up with these however you like such as drawn user input or curve interpolations.
So the final piece is the RevolutionMesh (or lathe or whatever you want to call it). Its job is to take the input, transform it around an axis and storing the vertices. Then build the uv’s and indices. (You can then go on to generate normals and tangents etc…)
private function build():void { if(vertices) vertices.length = 0; else vertices = new Vector.<Number>(); if(uvs) uvs.length = 0; else uvs = new Vector.<Number>(); if(ids) ids.length = 0; else ids = new Vector.<uint>(); if(normals) normals.length = 0; else normals = new Vector.<Number>(); //doesn't have to revolve the whole way round, _start and _end are values from 0-1 so a start of 0 and end of 0.5 would mean a 0 - 180 degrees of revolution var totalAngle:Number = (Math.PI*2)*(_end-_start); var angle:Number = totalAngle/_divisions; var startAngle:Number = (Math.PI*2)*_start; for (var i : int = 0; i < _divisions+1; i++) { var vout:Vector.<Number> = new Vector.<Number>(); transformer.identity(); transformer.appendRotation((startAngle*Math3D.RADIANS_TO_DEGREES) + (angle*i*Math3D.RADIANS_TO_DEGREES), _axix); transformer.transformVectors(_spline, vout); vertices = vertices.concat(vout); //could do the same for normals if supplied ;) } var splineLength:int = _spline.length/3; for (i = 0; i < _divisions+1; i++) { for (var j:int = 0; j < splineLength; j++) { uvs.push(i/(_divisions), j/(splineLength-1)); } } for (i = 0; i < _divisions; i++) { for (j = 0; j < splineLength-1; j++) { var id0:uint = ((i+1)*splineLength)+j; var id1:uint = (i*splineLength)+j; var id2:uint = ((i+1)*splineLength)+j+1; var id3:uint = (i*splineLength)+j+1; ids.push(id0, id2, id1); ids.push(id3, id1, id2); } } //go forth and generate normals etc... } |
It works by firstly using a matrix3D to rotate the points and copy them into a vertex list. Next it calculates the uvs based upon vertices’ index in the original point list and the revolution index (u based on the rev index and v based on the position in the point list). Once that is done its a simple case of assigning the indices!
There you have it, some fairly simple code which can be reused to create a crap load of geometry.
Hope that helps someone out, shout if you have and questions or spot any problems etc…
b
2 replies on “3D Procedural Geometry using revolutions”
Amazing! I’m saving it in bookmarks. Thanks.
Very nice! Good work Ben! #