One By One Design

Rockin and Rollin with the JiglibFlash Terrain

Finally got the chance to play around with the new JiglibFlash height map terrain, today, and have to say I am very impressed!

The first thing I noticed missing, though, was the ability to update the terrain in order to use animated height maps. I tried deleting then creating a new terrain inside a rendering event handler. This actually worked better than it sounds like it might. At least for a very short time. Going this route, within less than a minute, the frame rate dropped from around 58 to around 15 and the memory usage was well over 400 and climbing steadily to an inevitable crash. I knew an update function was needed for any animation.

Before I dig into the nerd stuff, have a look at what I’m talking about. Double click the example below to see an animated JTerrain instance in action (double click on it a second time to stop the animation and save a bit of processor power). And this is just using perlin noise. You could also use sound data (mp3 or microphone), video data, etc. etc (giving myself some ideas and goosebumps just thinking about it).

[kml_flashembed publishmethod=”static” fversion=”10.0.0″ movie=”http://blog.onebyonedesign.com/wp-content/uploads/2010/03/terrain.swf” width=”550″ height=”400″ targetclass=”flashmovie”]

Get Adobe Flash player

[/kml_flashembed]

If you’d like to try this out yourself, here’s the changes I made to a few of the jiglib core classes (for Papervision3D only, so far). In ITerrain.as add just a simple function update():void to the interface methods.

In JTerrain, you’ll need to add a quick update method

public function update():void {
	_terrain.update();
}

The tricky part comes in pv3dTerrain.as. First you’ll need to add some private variables to keep a few things persistent:

private var _map:BitmapData;
private var _d:Number;
private var _w:Number;
private var _maxHH:Number;

In the constructor, you’ll actually want to set those vars:

// set for updating
_map = terrainHeightMap;
_d = depth;
_w = width;
_maxHH = maxHeight;

In the build terrain method, you’ll want to change these 2 lines:

var vertices :Array  = this.geometry.vertices;
var faces    :Array  = this.geometry.faces	;

to:

var vertices :Array  = this.geometry.vertices	= [];
var faces    :Array  = this.geometry.faces		= [];

in order to create the vertices and faces arrays from scratch every time this method is called.

Finally, still in the pv3dTerrain.as file, you’ll need to add an update method that calls the buildTerrain method using our stored variables like so:

public function update():void {
	buildTerrain(_w, _d, _maxHH, _map);
}

Now, keep in mind that doing this may add to a bit more memory consumption when not using animated terrains, as a reference to your bitmapdata is now stored within your terrain instance (but you always destroy bitmapdata when through with it, right?), plus, who knows, it may just break something down the road, so I’d recommend keeping a back up of the original files if you give this a try.

In any case, when done, you can create the example above with the script below:

package {

	import flash.display.Bitmap;
	import flash.display.BitmapData;
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.events.MouseEvent;
	import flash.geom.Point;
	import flash.geom.Vector3D;
	import flash.text.TextField;
	import flash.text.TextFieldAutoSize;
	import flash.text.TextFormat;

	import jiglib.geometry.JSphere;
	import jiglib.geometry.JTerrain;
	import jiglib.plugin.papervision3d.Papervision3DPhysics;
	import jiglib.plugin.papervision3d.Pv3dMesh;

	import net.hires.debug.Stats;

	import org.papervision3d.cameras.Camera3D;
	import org.papervision3d.lights.PointLight3D;
	import org.papervision3d.materials.shadematerials.PhongMaterial;
	import org.papervision3d.objects.DisplayObject3D;
	import org.papervision3d.objects.primitives.Sphere;
	import org.papervision3d.render.BasicRenderEngine;
	import org.papervision3d.scenes.Scene3D;
	import org.papervision3d.view.Viewport3D;

	/**
	 * Animated JiglibFlash terrain example
	 * @author Devon O.
	 */

	[SWF(width='550', height='400', backgroundColor='#000000', frameRate='60')]
	public class Main extends Sprite {

		public static const NUM_BALLS:int = 5;

		private var _scene:Scene3D;
		private var _cam:Camera3D;
		private var _light:PointLight3D;
		private var _view:Viewport3D;
		private var _eng:BasicRenderEngine;

		private var _lightAngle:Number = 0;

		private var _terrain:JTerrain;
		private var _terrainMap:BitmapData;
		private var _offsetPoint:Point;
		private var _seed:int;

		private var _physics:Papervision3DPhysics;

		private var _balls:Vector. = new Vector.(NUM_BALLS, true);

		private var _appSeen:Boolean = false;
		private var _noApp:Sprite;

		public function Main():void {
			if (stage) init();
			else addEventListener(Event.ADDED_TO_STAGE, init);
		}

		private function init(e:Event = null):void {
			removeEventListener(Event.ADDED_TO_STAGE, init);

			init3D();
			initPhysics();
			initScene();

			addChild(new Stats());

			_noApp = createNoApp();
			addChild(_noApp);

			stage.doubleClickEnabled = true;
			stage.addEventListener(MouseEvent.DOUBLE_CLICK, startStop);
		}

		private function startStop(event:MouseEvent):void {
			if (_appSeen) {
				stopRendering();
				addChild(_noApp);
			} else {
				startRendering();
				if (contains(_noApp)) removeChild(_noApp);
			}
			_appSeen = !_appSeen;
		}

		private function createNoApp():Sprite {
			var s:Sprite = new Sprite();
			s.mouseEnabled = false;
			s.graphics.beginFill(0x000000);
			s.graphics.drawRect(0, 0, 550, 400);
			s.graphics.endFill();
			var tf:TextField = new TextField();
			tf.defaultTextFormat = new TextFormat("_sans", 12, 0xFFFFFF);
			tf.selectable = false;
			tf.mouseEnabled = false;
			tf.autoSize = TextFieldAutoSize.LEFT;
			tf.text = "Double Click to Start.";
			tf.x = Math.round(550 * .5 - tf.width * .5);
			tf.y = Math.round(400 * .5 - tf.height * .5);
			s.addChild(tf);
			return s;
		}

		private function init3D():void {
			_view = new Viewport3D(stage.stageWidth, stage.stageHeight);
			addChild(_view);

			_cam = new Camera3D();
			_cam.z = -500;
			_cam.y = 400;

			_light = new PointLight3D();
			_light.y = 500;

			_cam.target = new DisplayObject3D();

			_scene = new Scene3D();
			_eng = new BasicRenderEngine();
		}

		private function initPhysics():void {
			_physics = new Papervision3DPhysics(_scene, 8);
		}

		private function initScene():void {
			// terrain stuff
			var tmat:PhongMaterial = new PhongMaterial(_light, 0x000077, 0x000000, 0);
			_terrainMap = new BitmapData(100, 100, false);
			_seed = int(Math.random() * 10000);
			_offsetPoint = new Point();

			_terrain = _physics.createTerrain(_terrainMap, tmat, 600, 600, 200, 9, 9);

			// just so you can see the bitmapdata creating the terrain
			var bmp:Bitmap = new Bitmap(_terrainMap);
			bmp.y = 300;
			addChild(bmp);

			// add the balls to the mix
			var bmat:PhongMaterial = new PhongMaterial(_light, 0xFF00FF, 0x000000, 0);
			for (var i:int = 0; i < NUM_BALLS; i++) {
				var ball:Sphere = new Sphere(bmat, 30);
				_scene.addChild(ball);

				// keep balls in front of terrain at all times with line below
				//_view.getChildLayer(ball).layerIndex = (i+1);

				var jball:JSphere = new JSphere(new Pv3dMesh(ball), 30);
				jball.moveTo(new Vector3D(0, i * 200, 0));
				_balls[i] = jball;
				_physics.addBody(jball);
			}
		}

		private function startRendering():void {
			addEventListener(Event.ENTER_FRAME, render);
		}

		private function stopRendering():void {
			removeEventListener(Event.ENTER_FRAME, render);
		}

		private function render(event:Event):void {
			// wrinkle up then update the terrain
			_offsetPoint.x += 1;
			_offsetPoint.y -= 1;
			_terrainMap.perlinNoise(50, 50, 1, _seed, true, false, 7, true, [_offsetPoint]);

			_terrain.update();

			// orbit the light around
			_light.z = Math.sin(_lightAngle) * 200;
			_light.x = Math.cos(_lightAngle) * 200;
			_lightAngle += 0.05;

			// ease the camera back and forth according to mouse pos
			var ratio:Number = ((stage.mouseX / stage.stageWidth) - .5) * 2;
			_cam.x += ((ratio * 500) - _cam.x) * .1;

			// check on the family jewels
			var i:int = NUM_BALLS;
			while (i--) {
				var jball:JSphere = _balls[i];
				if ((jball.z < -300) || (jball.z > 300) || (jball.x < -300) || (jball.x > 300)) jball.moveTo(new Vector3D(0, 400, 0));
			}

			// render it all
			_physics.engine.integrate(0.2);
			_eng.renderScene(_scene, _cam, _view);
		}
	}
}

Next up – a little ragdoll action… Woot!

Posted by

Post a comment

Your email address will not be published. Required fields are marked *