Fast on the heels of the 3D Langton’s Ants in Javascript using Three.js, here is a version done in Actionscript 3 using Away3d. This will look better on faster computers. Click the image to launch the experiment.
Other than some additional rotation around the main axis, it is identical to the Javascript version, including a glitch that kicks in somewhere around 1200 cubes. In the Javascript version, Chrome would crash at around 700 cubes. In this version, it starts to get a little glitchy at 1600, then progressively more glitchy until it eventually stops updating the screen completely. Oddly, the script continues to run; you will be able to see the number of cubes increment in the upper left corner. I am not sure if this is a hard limit built into Away3d, or the Flash 3D API, or if there is a memory limit of some kind being reached. I suspect – based on the occasional warnings which popped up during development – that it is a hard-coded polygon limit within Away3d. There is probably some way around it, but I don’t (yet) have the know-how to go in and fix it.
Anyway, here is the code for the experiment. Comment out any lines which use the “org.eccesignum.*” files; they assume you have the code for my custom InfoPanel in your library path.
package { import away3d.cameras.Camera3D; import away3d.containers.ObjectContainer3D; import away3d.containers.Scene3D; import away3d.containers.View3D; import away3d.entities.Mesh; import away3d.lights.DirectionalLight; import away3d.lights.PointLight; import away3d.materials.ColorMaterial; import away3d.materials.lightpickers.*; import away3d.primitives.SphereGeometry; import away3d.primitives.CubeGeometry; import flash.display.Sprite; import flash.display.StageAlign; import flash.display.StageScaleMode; import flash.events.Event; import flash.events.MouseEvent; import flash.events.TimerEvent; import flash.geom.Vector3D; import flash.utils.Timer; import org.eccesignum.utilities.InfoPanel; [SWF(width=640,height=480,frameRate=32,backgroundColor=0x000000)] public class Main extends Sprite { internal var _info:InfoPanel; private var view:View3D; private var cubeContainer:ObjectContainer3D; private var scene:Scene3D; private var camera:Camera3D; private var directionalLight:DirectionalLight; private var lightPicker:StaticLightPicker private var cMaterial:ColorMaterial; private var antX:Number = 32, antY:Number = 32, antZ:Number = 32, nextX:Number, nextY:Number, nextZ:Number, cellsX:int = 64, cellsY:int = 64, cellsZ:int = 64, cellWidth:int = 8, cellHeight:int = 8, cellDepth:int = 8, antSize:int = 7, maxDirections:Number = 8, colorMultiplier:Number = Math.round(256/cellsX), xOff:Number = cellsX/2*cellWidth, yOff:Number = cellsY/2*cellHeight, zOff:Number = cellsZ/2*cellDepth, objects:Array, antDirection:Number = 1, filledCells:int = 0; public function Main():void { addEventListener(Event.ADDED_TO_STAGE,onAddedToStage); } private function onAddedToStage(e:Event):void { removeEventListener(Event.ADDED_TO_STAGE,onAddedToStage); stage.scaleMode = StageScaleMode.NO_SCALE; stage.align = StageAlign.TOP_LEFT; init(); } private function init():void { _info = new InfoPanel(this,100,50); scene = new Scene3D(); camera = new Camera3D(); view = new View3D(null,camera); view.antiAlias = 2; camera.x = 0; camera.z = 150; camera.y = -300; cubeContainer = new ObjectContainer3D(); cubeContainer.rotationY=0; cubeContainer.rotationZ=45; directionalLight = new DirectionalLight(0,150,-300); directionalLight.diffuse = 1; directionalLight.specular = 0.3; directionalLight.color=0xffffff; scene.addChild(directionalLight); lightPicker = new StaticLightPicker([directionalLight]); cMaterial = new ColorMaterial(0x999999); cMaterial.lightPicker = lightPicker; scene.addChild(cubeContainer); camera.lookAt(new Vector3D(0,0,0)); view.scene = scene; addChild(view); objects = new Array(cellsX); for(var i:int=0;i<objects.length;i++) { objects[i] = new Array(cellsY); for(var j:int=0;j<objects[i].length;j++) { objects[i][j] = new Array(cellsZ); } } view.render(); addEventListener(Event.ENTER_FRAME,onEnterFrame); } private function onEnterFrame(e:Event):void { if(!objects[antX][antY][antZ]) { antDirection++; if(antDirection == maxDirections) antDirection = 0; addObject(antX,antY,antZ); } else { removeObject(antX,antY,antZ); antDirection--; if(antDirection == -1) antDirection = maxDirections-1; } switch(antDirection) { case 0: antZ--; break; case 1: antX++; break; case 2: antY++; break; case 3: antX--; break; case 4: antZ++; break; case 5: antX++; break; case 6: antY--; break; case 7: antX--; break; default: break; } if(antY < 0) antY += cellsY; if(antY >= cellsY) antY -= cellsY; if(antX < 0) antX += cellsX; if(antX >= cellsX) antX -= cellsX; if(antZ < 0) antZ += cellsZ; if(antZ >= cellsZ) antZ -= cellsZ; cubeContainer.rotationZ+=.5; cubeContainer.rotationY+=.5; cubeContainer.rotationX+=.5; _info.update(filledCells.toString(),true); view.render(); } private function addObject(x:int,y:int,z:int):void { var cGeometry:CubeGeometry = new CubeGeometry(); cGeometry.width = antSize; cGeometry.height = antSize; cGeometry.depth = antSize; var cMesh:Mesh = new Mesh(cGeometry,cMaterial); cMesh.x = x*cellWidth-xOff; cMesh.y = y*cellHeight-yOff; cMesh.z = z*cellDepth-zOff; cubeContainer.addChild(cMesh); objects[x][y][z] = cMesh; filledCells++; } private function removeObject(x:int,y:int,z:int):void { cubeContainer.removeChild(objects[x][y][z]); objects[x][y][z].material.dispose(); objects[x][y][z].dispose(); objects[x][y][z] = null; filledCells--; } private function getRGB(r:int,g:int,b:int):int { var rgb:int = parseInt((r*colorMultiplier).toString(16) + (g*colorMultiplier).toString(16) + (b*colorMultiplier).toString(16),16); return rgb; } } }
Feel free to use and modify the code to your heart’s content. If you come up with anything nifty, post a link to it in the comments.