/* PerlinizedMesh.as Lee Felarca http://www.zeropointnine.com/blog 5-2007 v0.9 Source code licensed under a Creative Commons Attribution 3.0 License. http://creativecommons.org/licenses/by/3.0/ Some Rights Reserved. */ package { import com.robertpenner.easing.*; import flash.display.BitmapData; import flash.geom.Point; import org.papervision3d.core.geom.TriangleMesh3D; import org.papervision3d.core.geom.renderables.Triangle3D; import org.papervision3d.core.geom.renderables.Vertex3D; import org.papervision3d.core.math.NumberUV; import org.papervision3d.core.proto.MaterialObject3D; import org.papervision3d.materials.WireframeMaterial; public class PerlinizedMesh { public static var BASESHAPE_SPHERE:int = 0; public static var BASESHAPE_SEMISPHERE:int = 1; public static var BASESHAPE_CYLINDER:int = 2; public static var BASESHAPE_BOWL:int = 3; private const DEGREE:Number = Math.PI/180; private var _mesh:TriangleMesh3D; private var _numColumns:int; private var _numRows:int; private var _sizeMultiplier:Number; private var _aspectRatio:Number; private var _perlinSeed:Number; private var _material:MaterialObject3D; private var _perlinOctaves:int; private var _morphMultiplier:Number; private var _perlinBmd:BitmapData; private var _perlinOffsets:Array; private var _perlinOffsetMultiplier:Array; private var _perlinOffsetAngleSteps:Array; private var _perlinOffsetAngles:Array; private var _perlinOctaveOffsetRandomnessRange:Number; private var _baseShape:int; private var _radiusMultiplier:Number; /** * @param $numColumns * @param $numRows * @param $properties VO for additional params */ public function PerlinizedMesh($numColumns:int, $numRows:int, $material:MaterialObject3D=null, $properties:Object=null): void { if (!$properties) $properties = new Object(); _sizeMultiplier = ($properties.sizeMultiplier) ? $properties.sizeMultiplier : 150; _baseShape = ($properties.baseShape) ? $properties.baseShape : PerlinizedMesh.BASESHAPE_SPHERE; _aspectRatio = ($properties.aspectRatio) ? $properties.aspectRatio: 2; _perlinSeed = ($properties.perlinSeed) ? $properties.perlinSeed : int(Math.random()*0xFFFFFF); _perlinOctaves = ($properties.perlinOctaves) ? $properties.perlinOctaves : 3; _perlinOctaveOffsetRandomnessRange = ($properties.perlinOctaveOffsetRandomnessRange) ? $properties.perlinOctaveOffsetRandomnessRange : 1.0; _radiusMultiplier = (!isNaN($properties.radiusMultiplier)) ? $properties.radiusMultiplier : 2; _morphMultiplier = (!isNaN($properties.morphMultiplier)) ? $properties.morphMultiplier : 1; _numColumns = $numColumns; _numRows = $numRows; if ($material==null) $material = new WireframeMaterial(0xFF0000); _material = $material; _perlinOffsets = []; _perlinOffsetAngles = []; _perlinOffsetAngleSteps = []; _perlinOffsetMultiplier = []; for (var i:int = 0; i < _perlinOctaves; i++) { _perlinOffsets[i] = new Point(0,0); var mx:Number = Math.random()*_perlinOctaveOffsetRandomnessRange - (_perlinOctaveOffsetRandomnessRange/2); var my:Number = Math.random()*_perlinOctaveOffsetRandomnessRange - (_perlinOctaveOffsetRandomnessRange/2); _perlinOffsetMultiplier[i] = new Point(mx,my); _perlinOffsetAngles[i] = new Point(Math.random()*180*DEGREE, Math.random()*180*DEGREE); _perlinOffsetAngleSteps[i] = new Point(Math.random()*DEGREE,Math.random()*DEGREE); } _perlinBmd = new BitmapData(_numColumns+1, _numRows, false, 0x0); advancePerlinBitmap(); // Create vertices var vertices:Array = []; for (var v:int = 0; v <= (_numRows+2) * (_numColumns+1); v++) { vertices[v] = new Vertex3D(); } updateVerticesFromPerlinBitmap(vertices); _mesh = makeTriangleMesh3DFromVertices(vertices); } public function destroy():void { _mesh.renderLayer = null; _mesh = null; } /** * Expose the mesh, utilized by composition. Breaks encapsulation. */ public function get triangleMesh3D():TriangleMesh3D { return _mesh; } public function set material($m:MaterialObject3D):void { _material = $m; _mesh.material = _material; for (var i:int = 0; i < _mesh.geometry.faces.length; i++) { Triangle3D(_mesh.geometry.faces[i]).material = _material; } } public function get bitmapDataSource():BitmapData { return _perlinBmd; } public function get baseShape():int { return _baseShape; } public function set baseShape($i:int):void { if ($i < 0 || $i > 3) return; _baseShape = $i; } public function get radiusMultiplier():Number { return _radiusMultiplier; } public function set radiusMultiplier($n:Number):void { _radiusMultiplier = $n; } public function get morphMultiplier():Number { return _morphMultiplier; } public function set morphMultiplier($n:Number):void { _morphMultiplier = $n; } public function updateMesh():void { advancePerlinBitmap(); updateVerticesFromPerlinBitmap(_mesh.geometry.vertices); } public function updateVerticesFromPerlinBitmap(verts:Array):void { var index:int = 0; // first cap bottom of the shape for (var i:int = 0; i <= _numColumns; i++) { verts[index].x = 0; verts[index].z = 0; verts[index].y = 0; index++; } var y:Number = 0; var yAdd:Number; for (var r:int = 0; r <= _numRows; r++) // note "<=" ! { var rowRatio:Number = r / _numRows; var baseRadius:Number; yAdd = 1/_numRows * _sizeMultiplier * _aspectRatio; if (_baseShape == PerlinizedMesh.BASESHAPE_SPHERE) { if (rowRatio <=0.5) baseRadius = Circ.easeOut(rowRatio*2, 0,1,1) * _sizeMultiplier ; else baseRadius = Circ.easeOut(rowRatio*2, 0,1,1) * _sizeMultiplier ; } else if (_baseShape == PerlinizedMesh.BASESHAPE_BOWL) { if (rowRatio <=0.5) baseRadius = Circ.easeOut(rowRatio*2, 0,1,1) * _sizeMultiplier ; else baseRadius = Circ.easeOut(rowRatio*2, 0,1,1) * _sizeMultiplier ; if (rowRatio >= 0.6) { yAdd *= -1; baseRadius *= 0.85; } } else if (_baseShape == PerlinizedMesh.BASESHAPE_SEMISPHERE) { baseRadius = Circ.easeOut(1-rowRatio, 0,1,1) * _sizeMultiplier ; } else { // Cylinder baseRadius = _sizeMultiplier; } var rotation:Number = 0; var rotationStep:Number = 1/_numColumns * (Math.PI*2); for (var c:int = 0; c <= _numColumns; c++) { var perlinAdjust:Number = getBrightness( _perlinBmd.getPixel(c, r) ) - 0.5; var radiusOffset:Number = _radiusMultiplier * perlinAdjust * baseRadius; var radius:Number = baseRadius + radiusOffset; var pt:Point = circle(rotation, radius); verts[index].x = pt.x verts[index].z = pt.y; verts[index].y = y; index++; rotation += rotationStep; } y += yAdd; } } private function advancePerlinBitmap():void { _perlinBmd.perlinNoise( _numColumns+1, _numRows, _perlinOctaves, _perlinSeed, true, false, 7, true, _perlinOffsets); for (var i:int = 0; i < _perlinOffsets.length; i++) { _perlinOffsetAngles[i].x += _perlinOffsetAngleSteps[i].x; _perlinOffsetAngles[i].y += _perlinOffsetAngleSteps[i].y; var x:Number = Math.sin(_perlinOffsetAngles[i].x) * _perlinOffsetMultiplier[i].x; var y:Number = Math.sin(_perlinOffsetAngles[i].y) * _perlinOffsetMultiplier[i].y; _perlinOffsets[i].x += x * _morphMultiplier; _perlinOffsets[i].y += y * _morphMultiplier; } } private function makeTriangleMesh3DFromVertices($verts:Array):TriangleMesh3D { var faces:Array = []; var mesh:TriangleMesh3D = new TriangleMesh3D(_material, $verts, faces); var count:int = 0; var t1:Triangle3D, t2:Triangle3D; for (var y:int = 0; y < _numRows + 2; y++) // note "+2" { /* C.--B | / | A.--D */ for (var x:int = 0; x <= _numColumns; x++) { // Upper left triangle - ABC var va:int = (y+0)*_numColumns + (x+0); var vb:int = (y+1)*_numColumns + (x+1); var vc:int = (y+1)*_numColumns + (x+0); var vArr1:Array = [ $verts[va], $verts[vb], $verts[vc] ]; var ua:NumberUV = new NumberUV(0,0); var ub:NumberUV = new NumberUV(1,1); var uc:NumberUV = new NumberUV(0,1); var uvArr1:Array = [ua, ub, uc]; t1 = new Triangle3D(mesh, vArr1, _material, uvArr1); faces[count] = t1; // var index1:int = (y * _numColumns * 2) + x * 2 + 0; count++; // Lower right triangle - ADB var vd:int = (y+0)*_numColumns + (x+1); var ud:NumberUV = new NumberUV(1,0); var vArr2:Array = [ $verts[va], $verts[vd], $verts[vb] ]; var uvArr2:Array = [ua, ud, ub]; t2 = new Triangle3D(mesh, vArr2, _material, uvArr2); faces[count] = t2; // var index2:int = (y * _numColumns * 2) + x * 2 + 1; count++; } } return mesh; } /** * @param $radians where 0 degrees == the "12 o'clock" position * @param $radius */ private function circle($radians:Number, $radius:Number):Point { $radians -= Math.PI/2; var x:Number = $radius * Math.cos($radians); var y:Number = $radius * Math.sin($radians); return new Point(x,y); } /** * Assuming grayscale image. * Returning blueness only! * * @return a value between 0 and 1 */ private function getBrightness($color:uint):Number { return (($color & 0xFF) / 255); /* var r:Number = $color >> 16; var g:Number = $color >> 8 & 0xff; var b:Number = $color & 0xff; return ((r+g+b)/3) / 255; */ } } }