package { /** * * Petri Leskinen, Finland, Espoo, 7th June 2009 * leskinen[dot]petri[at]luukku[dot]com * pixelero.wordpress.com * * * Some test results * 2000 times * for-loops reading vector : 9340ms * linked : 5722ms * * * * looping by public function * Grid.forEach(doSomething) * : 5810ms * * public static function * Grid.forEach(GridItem.doSomething) * : 5705ms (for being 'static' the results were varying a lot, at worse ~7500) * * inline function * Grid.forEach(function (item: ){... * : 4807ms * * do-while loops with inline code, as I've written the most crucial parts of code * : 3306ms * */ public class LinkedGrid { import flash.display.BitmapData; import flash.display.Graphics; import flash.display.TriangleCulling; import flash.events.Event; import flash.events.MouseEvent; import flash.geom.Matrix3D; import flash.geom.Utils3D; import GridItem; public var sizeX:int, sizeY:int, firstItem:GridItem; // most of these parameters work with flash.utils.Utils3D.projectVectors( and graphics.drawTriangles( public var // items:Vector.>, // direct access by items[i][j] wasn't needed here indices:Vector., vertices2D:Vector., vertices3D:Vector., uvtData:Vector., projectedVerts:Vector., triangles:Array, // for sorting it's still an Array bitmapData:BitmapData; // for function getItemAt(row,column) protected var currentRow:int = 0, currentColumn:int = 0, lastAccessed:GridItem; private var i:int, tmp:Number; public function LinkedGrid(_sizeX:int = 10, _sizeY:int = 10, startX:Number = 0.0, startY:Number = 0.0, stepX:Number = 20.0, stepY:Number = 20.0) { sizeX = _sizeX; sizeY = _sizeY; /*items=*/ initGrid(sizeX, sizeY, startX, startY, stepX, stepY); vertices2D = getVertices2D(); vertices3D = getVertices3D(); initIndices(); uvtData = new Vector.(3 * gridSize, true); } protected function initGrid(sizeX:int = 10, sizeY:int = 10, startX:Number = 20.0, startY:Number = 20.0, stepX:Number = 20.0, stepY:Number = 20.0):Vector.> { var items:Vector.> = new Vector.>(sizeX,true), i:int = 0, j:int = 0, id:int = 0; // Version 0 ! the initializing of the grid is written with loops for (i = 0; i != sizeX; i++) { items[i] = new Vector.(sizeY,true); for (j = 0; j != sizeY; j++) { items[i][j] = new GridItem(startX + i * stepX, startY + j * stepY, 0.0, id++) ; // default uv-coordinates in range (0...1) items[i][j].u = i / (sizeX - 1); items[i][j].v = j / (sizeY - 1); } } // linking the items together for (i = 0; i != sizeX; i++) { for (j = 0; j != sizeY; j++) { if (i!=sizeX-1) items[i][j].itemRight = items[i+ 1][j]; if (j!=sizeY-1) items[i][j].itemBelow = items[i][j+ 1]; } } firstItem = items[0][0]; lastAccessed = items[0][0]; return items; } // extract the xy-coordinates to a new vector public function getVertices2D():Vector. { var itemX:GridItem = firstItem, itemY:GridItem = itemX, v:Vector. = new Vector.(); do { do v.push(itemY.x,itemY.y) while (itemY = itemY.itemBelow) } while (itemY = itemX = itemX.itemRight) return v; } // extract the xyz-coordinates to a new vector public function getVertices3D():Vector. { var itemX:GridItem = firstItem, itemY:GridItem = itemX, v:Vector. = new Vector.(); do { do v.push(itemY.x,itemY.y,itemY.z) while (itemY = itemY.itemBelow) } while (itemY = itemX = itemX.itemRight) return v; } // extract the uvt-coordinates (here t=1) to a new vector public function getUVT():Vector. { var itemX:GridItem = firstItem, itemY:GridItem = itemX, v:Vector. = new Vector.(); do { do v.push(itemY.u,itemY.v,1.0) while (itemY = itemY.itemBelow ) } while (itemY = itemX = itemX.itemRight) return v; } public function updateNormals():void { // first reset normals to (0,0,1) forEach(function (item:GridItem):void { item.normalx = item.normaly = 0.0; item.normalz = 1.0; }); // calculate the slopes for each point // by neighbors' z's forEach(function (item:GridItem):void { if (item.itemRight) { item.itemRight.normalx += item.z; item.normalx -= item.itemRight.z; } if (item.itemBelow) { item.itemBelow.normaly += item.z; item.normaly -= item.itemBelow.z; } }); // vertices on the edges need some special handling // because of having less neighbors var item:GridItem = firstItem; do { item.normaly += item.z; } while ((item = item.itemRight).itemRight) item.normaly += item.z; do { item.normalx-= item.z; } while (item = item.itemBelow) item = firstItem; do { item.normalx+= item.z; } while ((item = item.itemBelow).itemBelow) item.normalx += item.z; do { item.normaly -= item.z; } while (item = item.itemRight) // ((item = item.itemRight).itemRight) forEach(GridItem.setNormal); } // updates vertices3D:Vector public function updateVertices3D():void { var itemX:GridItem = firstItem, itemY:GridItem = itemX, i:int=-1; do { do { vertices3D[++i] = itemY.x; vertices3D[++i] = itemY.y; vertices3D[++i] = itemY.z; } while (itemY = itemY.itemBelow ) } while (itemY = itemX = itemX.itemRight) } // for counting grid normals, // in this case checks only the difference in z-direction public function normalDifferences(item:GridItem):void { if (item.itemRight) { item.itemRight.normalx += item.z; item.normalx -= item.itemRight.z; } if (item.itemBelow) { item.itemBelow.normaly += item.z; item.normaly -= item.itemBelow.z; } } // forms indices:Vector and triangles:Array // usually done only once at init public function initIndices():void { triangles = new Array(); indices = new Vector.(); var itemX:GridItem = firstItem, itemY:GridItem = itemX, tr1:GridTriangle, tr2:GridTriangle; do { do { // two new triangles are added each loop // between four neighbor points // // item ------ right // | / | // | / | // | / | // below.----- right.below tr1 = new GridTriangle(itemY, itemY.itemBelow, itemY.itemRight); tr1.pushIndices(indices); tr2 = new GridTriangle(itemY.itemRight, itemY.itemBelow, itemY.itemRight.itemBelow); tr2.pushIndices(indices); triangles.push(tr1, tr2); } while ((itemY = itemY.itemBelow).itemBelow) } while ((itemY = itemX = itemX.itemRight).itemRight) } // sorts triangles and updates indices public function sortTriangles():void { var tmp2:Number; for each (var tr:GridTriangle in triangles) { tmp = uvtData[tr.id0 * 3 + 2]; if (uvtData[i = tr.id1 * 3 + 2] > tmp) tmp=uvtData[i]; if (uvtData[i = tr.id2 * 3 + 2] > tmp) tmp = uvtData[i]; tr.z = tmp; } triangles.sortOn("z", Array.NUMERIC | Array.DESCENDING ); indices = new Vector.(); for each (tr in triangles) tr.pushIndices(indices); } public function render(g:Graphics, light:Object, mtrx:Matrix3D) :void { updateNormals(); forEach(GridItem.updateUVT, light); // update vectors uvtData and vertices3D var itemX:GridItem = firstItem, itemY:GridItem = itemX, i:int=-1; do { do { uvtData[++i] = itemY.u; vertices3D[i] = itemY.x; uvtData[++i] = itemY.v; vertices3D[i] = itemY.y; uvtData[++i] = 1.0; vertices3D[i] = itemY.z; } while (itemY = itemY.itemBelow ) } while (itemY = itemX = itemX.itemRight) Utils3D.projectVectors(mtrx, vertices3D, vertices2D, uvtData); sortTriangles(); with (g) { clear(); //lineStyle(1.0, 0x00, 1.0); beginBitmapFill(bitmapData , null, // no matrix true, // = repeat true); // = smooth drawTriangles(vertices2D, indices, uvtData, TriangleCulling.NONE ); endFill(); } } /* loop though the grid */ public function forEach(f:Function, ... rest):void { var itemX:GridItem = firstItem, itemY:GridItem = itemX; // for performance, cases of extra parameters separated switch ( rest.length) { case 0: do { do f(itemY) while (itemY = itemY.itemBelow) } while (itemY = itemX = itemX.itemRight) break; case 1: do { do f(itemY, rest[0]) while (itemY = itemY.itemBelow) } while (itemY = itemX = itemX.itemRight) break; case 2: do { do f(itemY, rest[0], rest[1]) while (itemY = itemY.itemBelow) } while (itemY = itemX = itemX.itemRight) // easy to add more options if needed } } /* I often need to loop through the grid * except for the points on the rightmost and bottom rows * for exsample for handling the mesh of triangles */ public function forArea(f:Function):void { var itemX:GridItem = firstItem, itemY:GridItem = itemX; do { do f(itemY) while ((itemY = itemY.itemBelow).itemBelow) } while ((itemY = itemX = itemX.itemRight).itemRight) } /* loops through vertices on edges */ public function forEdges(f:Function):void { // first item and top row var item:GridItem = firstItem; do { f(item); } while ((item = item.itemRight).itemRight) // right edge do { f(item); } while (item = item.itemBelow) // return to first, and down the left edge item = firstItem.itemBelow; do { f(item); } while ((item = item.itemBelow).itemBelow) // bottom row, the last item already handled with right edge do { f(item); } while ((item = item.itemRight).itemRight) } public function get gridSize():uint { return sizeX * sizeY; } /* accessing items in this way is much slower (18 vs 2) than reading from a vector[][], * but is in fact quite rarely needed, * If it was needed more, * I would add variables like .itemLeft and .itemAbove, * and check for time * * lastAccessed, currentColumn and currentRow keep track of position in the grid * the principle is actual quite 'stupid' * if the searched item is right or below current, * just go to it * if left or above, * return first to .firstItem, then loop to correct item. */ public function getItemAt(n:uint, m:uint):GridItem { if (n < currentColumn || m