Skip directly to content

cellular automata

3D Langton's Ant, in Actionscript 3 Using Away3d

on Thu, 04/12/2012 - 17:23

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.

Langton's Ant in 3d, Using Javascript and Three.js

on Fri, 03/02/2012 - 22:36

Langton's Ant in 3d, using Javascript and Three.js

Click the screenshot to launch the demo. You will need to use the latest version of Chrome, Firefox or Safari if you want to see it running.

This is a re-creation of a Flash experiment from several years ago. Back before I started playing around with 3d, I created a series of experiments exploring cellular automata, the majority of which were variations on the Langton's Ant algorithm. The most advanced version was one in pseudo-3d, and that was the last for over three years. Now with the advent of easy-to-use 3d libraries (like Three.js, used here), and the option of hardware acceleration to allow for complex animations, I felt it was time to re-visit some of the old experiments.

Here is the source code for the 3d ant:

window.requestAnimFrame = (function(){
	return  window.requestAnimationFrame	|| 
		window.webkitRequestAnimationFrame || 
		window.mozRequestAnimationFrame    || 
		window.oRequestAnimationFrame      || 
		window.msRequestAnimationFrame     || 
		function( callback ){
			window.setTimeout(callback, 1000 / 60);
		};
})();

var canvas,
	stage,
	container,
	scene,
	camera,
	renderer,
	projector,
	light;
	
var WIDTH = 640,
	HEIGHT = 640;
	VIEW_ANGLE = 45,
	ASPECT = WIDTH/HEIGHT,
	NEAR = .1,
	FAR = 10000,
	centerX = 0,
	centerY = 0,
	centerZ = 0,
	cameraX = 0,
	cameraY = 200,
	cameraZ = 400;
	
var antX = 32,
	antY = 32,
	antZ = 32,
	nextX,
	nextY,
	nextZ,
	cellsX = 64,
	cellsY = 64,
	cellsZ = 64,
	cellWidth = 8,
	cellHeight = 8,
	cellDepth = 8,
	antSize = 7,
	maxDirections = 8,
	colorMultiplier = Math.round(256/cellsX),
	xOff = cellsX/2*cellWidth,
	yOff = cellsY/2*cellHeight,
	zOff = cellsZ/2*cellDepth,
	base,
	objects,
	antDirection = 1,
	filledCells=0,
	
	isRotating=true,
	isPaused=false;
	
	function init() {
		canvas = document.getElementById("myCanvas");
		scene = new THREE.Scene();
		camera = new THREE.PerspectiveCamera( VIEW_ANGLE, ASPECT, NEAR, FAR );
		camera.position.x = cameraX;
		camera.position.y = cameraY;
		camera.position.z = cameraZ;
		camera.lookAt(scene.position);
		scene.add( camera );
		projector = new THREE.Projector();
		renderer = new THREE.WebGLRenderer( { antialias: true } );
		renderer.setSize( WIDTH, HEIGHT );
		light = new THREE.SpotLight();
		light.position.set( cameraX, cameraY, cameraZ );
		scene.add(light);
		canvas.appendChild( renderer.domElement );
		renderer.render(scene,camera);
		
		base = new THREE.Object3D();
		base.position.x = centerX;
		base.position.y = centerY;
		base.position.x = centerZ;
		base.rotation.y = 45;
		scene.add(base);
		camera.lookAt(base.position);
		objects = new Array(cellsX);
		for(var i=0;i<objects.length;i++) {
			objects[i] = new Array(cellsY);
			for(var j=0;j<objects[i].length;j++) {
				objects[i][j] = new Array(cellsZ);
			}
		}
		onEnterFrame();
	}
	
	function onEnterFrame() {
		requestAnimFrame(onEnterFrame);
		renderer.render(scene,camera);
		if(isPaused==true) return;
		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;
		if(isRotating==true) base.rotation.y +=.01;
		document.getElementById("count").value = filledCells;
	}
	function addObject(x,y,z) {
		var sphereMaterial = new THREE.MeshPhongMaterial({
			color: getRGB(x,y,z),
			opacity:1
		});	
		var obj = new THREE.Mesh(
		   new THREE.CubeGeometry(antSize,antSize,antSize),
		   sphereMaterial);
		obj.position.x = x*cellWidth-xOff;
		obj.position.y = y*cellHeight-yOff;
		obj.position.z = z*cellDepth-zOff;
		obj.overdraw = true;
		base.add(obj);
		objects[x][y][z] = obj;
		filledCells++;
	}
	function removeObject(x,y,z) {
		base.remove(objects[x][y][z]);
		objects[x][y][z] = null;
		filledCells--;
	}
	function getRGB(r,g,b) {
		var rgb = parseInt((r*colorMultiplier).toString(16) + (g*colorMultiplier).toString(16) + (b*colorMultiplier).toString(16),16);
		return rgb;
	}
	onload=init;

The above code assumes you have downloaded a copy of Three.js and have a HTML page with appropriate elements set up. To see the entirety of the code I used, right-click on the screenshot and open it in a new tab or window, then view source. It's all one file, other than the Three.js library. Feel free to copy it in its entirety and play around.

Langton's Ant 3D, Version 2

on Fri, 10/03/2008 - 06:06

Langton's Ant in 3D

Another Langton’s Ant 3D experiment. Click here to run the experiment.

I finally got the ruleset sorted out, and now I have a simple 3d version of Langton’s Ant up and running. This is how it works:

Remember the behavior of Langton’s Ant:
1. If the ant is on an empty cell, color it in and move according to the next rule in the set.
2. If the ant is on a filled cell, empty the cell and move according to the previous rule in the set.
3. Do not talk about fight club.

The space in which the Ant is moving is a 50×50×50 (125,000) grid of cells.

There are eight rules which the ant follows, which – if none of the cells are colored in yet – the ant will follow in sequence:

1. Z—
2. X++
3. Y++
4. X—
5. Z++
6. X++
7. Y—
8. X—

In non-computerese, when the applet first runs, the ant will travel in, right, down, left, out, right, up, left, and in doing so fill in eight cells which make up a 2×2×2 cube.

2-3
5-4

1-8
6-7

With the next step the ant begins to encounter filled cells, which mean it takes a step back in the ruleset, and perhaps another, and eventually start to travel to new cells.

It is important, in order for the ant to make the desired patterns, that the sequence of movements in the ruleset visit each cell in a cube of cells exactly once, with the next iteration through the ruleset returning the ant to the original cell. Also, no diagonal movement; only cell face to cell face. I am not sure if this can be accomplished with more than a 2×2×2 cube.

In this demo the cells are colored in according to the “depth”, or the Z value of the cell-s position. Cells farther in the “back” are blue, and cells closer to the “front” are red. All layers (Z) are faded back except for the layer in which the Ant is currently moving. This is why there appears to be a “screen” of cells which moves back and forth and changes colors constantly.

Also in this demo, the pattern created appears to trend to the lower right corner of the screen because each of the layers, back to front, has been offset one cell to the right and one cell down, to provide a sort of isometric view of the pattern.

More experiments along these lines to come, after my brain cools down a bit.

Langton's Ant 3D, Version 1

on Thu, 10/02/2008 - 06:04

Langton's Ant in 3D space

Click here to see the ant in new amazing 3D action.

My first attempt at moving Langton’s Ant into a 3D grid. The 3D part was easy – just add some Z movement to the existing X and Y pattern.

This is what you are looking at: there are 100 cells in the X (left and right) and Y (up and down) plane, and 25 in the Z (toward and away from the viewer) plane. There is one bitmap for each Z level, making for 25 stacked bitmaps. The bitmap in which the ant is currently moving is set at 100% opacity, with the surrounding bitmaps becoming increasingly opaque until they are almost transparent. When the experiment seems to flicker, it is because the ant is rapidly switching between Z levels. Those long horizontal “roads” the ant makes are probably more like tubes when viewed in 3D space.

Making the ant move in an interesting pattern is a little more difficult. In 2D (X,Y) space, from each point the ant can either turn left or right, and move forward one space, depending on the state of the cell to which the ant moves. That means, using initial conditions, the ant will move in a perfect square: up 1, left 1, down 1, right 1, and end up back at the original square. In order for me to make that work in 3D space, the ant must visit each of eight cells, forming a cube, and return to the original cell without doubling back on itself. The current experiment does not do that, thus the long horizontal runs.

Right now there are 18 rules for the ant’s movements. Each of these rules is iterated through, based on the color of the cell on which the ant lands. Landing on an “off” cell causes the ant to move according to the next rule. Landing on an “on” cell causes the ant to move according to the previous rule. If I implement multiple “on” colors, then I can cut the movement rules down to 6, but I will need to have each rule account for the color of the cell in which the ant currently resides.

Look for more iterations on this problem in days to come.

Langton's Ant - Hex pattern

on Mon, 09/29/2008 - 06:11

Langton's Ant - 6

Click here to see it running.

Mostly similar to the previous Langton’s Ant experiment, this one uses a hexagon-ish algorithm instead of a quad. Because I am using square pixels to draw the results, it isn’t precisely a hexagon, but it is close enough to get the point across. Actually, the “ant” view is quite a bit more interesting than the heatmap view.

Click the Flash movie to toggle between the views.

Pages