Alternativa 3D Series – Tutorial 1 – Getting Started

by Matthew Casperson 27 views11

Alternativa is a 3D Flash engine that allows you to embed 3D environments directly into your web pages. Given that the Flash player is installed on something like 90% of all internet connected devices, Alternativa gives you a 3D platform that requires no effort or installation from your end users. In this tutorial I will show you how to create a simple 3D application using Alternativa and Flex.

While the end result of this tutorial is very simple, this is not a “hello world” application. From the outset the focus of this tutorial will be to create a solid framework that can be easily extended in future articles. With that goal in mind one of the principals that I’ll be applying here is the separation of the logic relating to the management of the underlying 3D engine (i.e. pretty much any code to do with Alternativa), and the logic that defines the desired result of application itself.

Requirments

EngineManager.as

package
{
	import flash.display.StageAlign;
	import flash.display.StageScaleMode;
	import flash.events.Event;
	import mx.collections.ArrayCollection;
	import mx.core.Application;
	import mx.core.UIComponent;
	import alternativa.engine3d.controllers.CameraController;
	import alternativa.engine3d.core.Camera3D;
	import alternativa.engine3d.core.Object3D;
	import alternativa.engine3d.core.Scene3D;
	import alternativa.engine3d.display.View;
	import alternativa.utils.FPS;

	/**
	 * 	The EngineManager holds all of the code related to maintaining the Alternativa 3D engine.
	 */
	public class EngineManager extends UIComponent
	{
		public var scene:Scene3D;
		public var view:View;
		public var camera:Camera3D;
		public var cameraController:CameraController;

		// a collection of the BaseObjects
		protected var baseObjects:ArrayCollection = new ArrayCollection();
		// a collection where new BaseObjects are placed, to avoid adding items
		// to baseObjects while in the baseObjects collection while it is in a loop
		protected var newBaseObjects:ArrayCollection = new ArrayCollection();
		// a collection where removed BaseObjects are placed, to avoid removing items
		// to baseObjects while in the baseObjects collection while it is in a loop
		protected var removedBaseObjects:ArrayCollection = new ArrayCollection();
		// the last frame time
		protected var lastFrame:Date;

		public function EngineManager()
		{
			super();
			addEventListener(Event.ADDED_TO_STAGE, init);
		}

		public function init(e:Event): void
		{
			stage.scaleMode = StageScaleMode.NO_SCALE;
			stage.align = StageAlign.TOP_LEFT;

			// Creating scene
			scene = new Scene3D();
			scene.root = new Object3D();

			// Adding camera and view
			camera = new Camera3D();
			camera.x = 100;
			camera.y = -150;
			camera.z = 100;
			scene.root.addChild(camera);

			view = new View();
			addChild(view);
			view.camera = camera;

			// Connecting camera controller
			cameraController = new CameraController(stage);
			cameraController.camera = camera;
			cameraController.setDefaultBindings();
			cameraController.checkCollisions = true;
			cameraController.collisionRadius = 20;
			cameraController.controlsEnabled = true;

			// FPS display launch
			FPS.init(stage);

			stage.addEventListener(Event.RESIZE, onResize);
			stage.addEventListener(Event.ENTER_FRAME, onEnterFrame);
			onResize(null);

			// set the initial frame time
			lastFrame = new Date();

			// start the application
			ApplicationManager.Instance.startupApplicationManager();
		}

		private function onResize(e:Event):void
		{
			view.width = stage.stageWidth;
			view.height = stage.stageHeight;
			Application.application.width = stage.stageWidth;
			Application.application.height = stage.stageHeight;
		}

		protected function onEnterFrame(event:Event):void
		{
			// Calculate the time since the last frame
			var thisFrame:Date = new Date();
			var seconds:Number = (thisFrame.getTime() - lastFrame.getTime())/1000.0;
	    	lastFrame = thisFrame;

	    	// sync the baseObjects collection with any BaseObjects created or removed during the
	    	// render loop
	    	removeDeletedBaseObjects();
	    	insertNewBaseObjects();

	    	// allow each BaseObject to update itself
	    	for each (var baseObject:BaseObject in baseObjects)
	    		baseObject.enterFrame(seconds);

	    	// User input processing
			cameraController.processInput();
			// Scene calculating
			scene.calculate();
		}

		public function addBaseObject(baseObject:BaseObject):void
		{
			newBaseObjects.addItem(baseObject);
		}

		public function removeBaseObject(baseObject:BaseObject):void
		{
			removedBaseObjects.addItem(baseObject);
		}

		protected function shutdownAll():void
		{
			// don't dispose objects twice
			for each (var baseObject:BaseObject in baseObjects)
			{
				var found:Boolean = false;
				for each (var removedObject:BaseObject in removedBaseObjects)
				{
					if (removedObject == baseObject)
					{
						found = true;
						break;
					}
				}

				if (!found)
					baseObject.shutdown();
			}
		}

		protected function insertNewBaseObjects():void
		{
			for each (var baseObject:BaseObject in newBaseObjects)
				baseObjects.addItem(baseObject);

			newBaseObjects.removeAll();
		}

		protected function removeDeletedBaseObjects():void
		{
			for each (var removedObject:BaseObject in removedBaseObjects)
			{
				var i:int = 0;
				for (i = 0; i < baseObjects.length; ++i)
				{
					if (baseObjects.getItemAt(i) == removedObject)
					{
						baseObjects.removeItemAt(i);
						break;
					}
				}

			}

			removedBaseObjects.removeAll();
		}
	}
}

We’ll start with the EngineManager class. This class will hold all of the code necessary to initialize and run the Alternativa 3D engine. The EngineManager class extends UIComponent, which means we can add it to the root Flex Application class like any other GUI control. We’ll see this in action when we take a look at the Alternativa1.mxml file later.

In the EngineManager constructor we add an event listener which will call the init function once the EngineManager has been added to the on stage display list. If added to the on stage display list doesn’t mean anything to you, don’t worry – all you need to know is that once this event has occurred the stage property is no longer null.

The init function holds the majority of the Alternativa initialization code. We create 4 important Alternativa objects: a Scene3D, a Camera3D, a CameraController and a View. Scene3D is essentially a container in
which our scene objects are placed. The Camera3D is, as the name suggests, the viewpoint into the world. The CameraController is a convenient class that allows us to move the camera with the keyboard and mouse, and also to detect collisions with scene objects. CameraController gives you this ability to move in and
interact with the world with all of 6 lines of code, which is very cool. Finally there is the View object, which basically takes the 3D world that the camera see’s and translates it into a 2D picture to be displayed on your monitor.


You’ll also notice that we call FPS.init(stage). This places a stat counter on the screen which gives you a readout of the memory usage frames per second. It’s useful for monitoring your application, but you’d
want to comment out that line when deploying your final build.


Once the Alternativa engine has been initialized we then add two more event listeners, one to respond to window resizing events (stage.addEventListener(Event.RESIZE, onResize) ), and one to respond to frame drawing events ( stage.addEventListener(Event.ENTER_FRAME, onEnterFrame) ). We catch the window resizing events so we can update the size of the View to match. The frame drawing event will form the basis of our render loop.

Render loop is a general term that refers to the loop that defines how the application runs. The loop consists of two parts. The first is where the application updates itself. Any movement or logic in your game is done in this first part of the render loop, say by moving a rocket further through space. The second is where the 3D engine renders one frame to the screen, thus displaying the changes that were made to the 3D world.

Updating the application (the first part of the render loop) is done via the BaseObject class. You will notice in the onEnterFrame function that we loop through a collection of BaseObject’s calling their enterFrame function. The enterFrame function is the sole purpose for the BaseObject class – it allows any class that extends BaseObject to easily update itself inside the render loop. We’ll see how this works a little later with the MeshObject and RotatingBox classes.

Finally in the init function we call ApplicationManager.Instance.startupApplicationManager(). I said before that we would be splitting the application logic from the engine logic. With the EngineManager class taking care of managing the Alternativa engine and the render loop it’s time to get our program to actually do something. For this we create the ApplicationManager class.

ApplicationManager.as

package
{

	import mx.core.Application;

	/**
	 * 	The ApplicationManager holds all program related logic.
	 */
	public class ApplicationManager
	{
		protected static var instance:ApplicationManager = null;

		public static function get Instance():ApplicationManager
		{
			if (instance == null)
				instance = new ApplicationManager();
			return instance;
		}

		public function ApplicationManager()
		{

		}

		public function startupApplicationManager():ApplicationManager
		{
			var rotatingBox:RotatingBox = new RotatingBox().startupRotatingBox();
			Application.application.engineManager.cameraController.lookAt(rotatingBox.model.coords);

			return this;
		}

	}
}

The ApplicationManager is designed as a Singleton. Although ActionScript does not have the ability to have protected or private constructors (and thus easily implement singletons), and singletons do have some negative consequences, I still find the design pattern useful as a tool for self documentation – if a class has an Instance property then it is a singleton and you should not create objects with the new function.

The ApplicationManager in this example has only one function (apart from the singleton property): startupApplicationManager. It’s in this function that we write the code that relates to the application itself, as opposed to initialising the Alternativa engine. In our case we are creating a RotatingBox and pointing the camera at it. It may seem like overkill to define a class for 2 lines of code, but this separation of engine / application logic will be more useful in complicated applications.

BaseObject.as

package
{
	import mx.core.Application;

	/**
	 *	The BaseObject class allows extending classes to update themselves during the render loop.
	 */
	public class BaseObject
	{
		public function BaseObject()
		{

		}

		/**
		 * 	Must be called by all extending classes when being created. Adds this object to the list of BaseObjects maintained
		 * 	by the EngineManager.
		 */
		public function startupBaseObject():void
		{
			Application.application.engineManager.addBaseObject(this);
		}

		/**
		 * 	Must be called by all extending classes when being destroyed. Removes this object to the list of BaseObjects maintained
		 * 	by the EngineManager.
		 */
		public function shutdown():void
		{
			Application.application.engineManager.removeBaseObject(this);
		} 

		/**
		 * 	This function is called once per frame before the scene is rendered.
		 *
		 * 	@param dt The time in seconds since the last frame was rendered.
		 */
		public function enterFrame(dt:Number):void
		{

		}
	}
}

MeshObject.as

package
{
	import alternativa.engine3d.core.Object3D;
	import alternativa.engine3d.materials.SurfaceMaterial;

	import mx.core.Application;

	public class MeshObject extends BaseObject
	{
		public var model:Object3D = null;

		public function MeshObject()
		{
			super();
		}

		override public function shutdown():void
		{
			super.shutdown();
			Application.application.engineManager.scene.root.removeChild(model);
			model = null;
		}

		public function startupModelObject(object:Object3D):void
		{
			model = object;
			Application.application.engineManager.scene.root.addChild(model);
			super.startupBaseObject();
		}
	}
}

The RotatingBox class is an example of how to extend the BaseObject class, although if you look closely we have actually created an intermediate class called MeshObject. MeshObject extends the BaseObject class and adds an Object3D property, which represents a 3D mesh on the screen. Again it may seem trivial to implement another class for this one property, but the truth is that while most of the elements in you application or game that need updating every frame will also have a 3D mesh representation, occasionally there will be the need for a class to update every frame while not having any 3D mesh associated with it. This is why we have the BaseObject and MeshObject as separate classes: extending BaseObject allows an object to update itself, and MeshObject will be a common base class for those objects that have 3D models – like the RotatingBox.

RotatingBox.as

package
{
	import alternativa.engine3d.materials.WireMaterial;
	import alternativa.engine3d.primitives.Box;

	public class RotatingBox extends MeshObject
	{
		protected static const ROTATION_SPEED:Number = 1;

		public function RotatingBox()
		{
			super();
		}

		public function startupRotatingBox():RotatingBox
		{
			var box:Box = new Box(100, 100, 100, 3, 3, 3);
			box.cloneMaterialToAllSurfaces(new WireMaterial(1, 0x000000));
			super.startupModelObject(box);
			return this;
		}

		public override function enterFrame(dt:Number):void
		{
			model.rotationX += dt * ROTATION_SPEED;
			model.rotationY += dt * ROTATION_SPEED;
			model.rotationZ += dt * ROTATION_SPEED;
		}
	}
}

So let’s take a look at the RotatingBox class. We have two important functions: startupRotatingBox and enterFrame. The startupRotatingBox function is responsible for creating and texturing the mesh that will be displayed on the screen. In this case we use the built in Box primitive, and texture it with a wireframe texture called WireMaterial. The enterFrame function has been overridden from BaseObject, and it is here that we rotate the box by a small amount each frame.

Alternativa1.mxml

<?xml version="1.0" encoding="utf-8"?>
<mx:Application
	xmlns:mx="http://www.adobe.com/2006/mxml"
	layout="absolute"
	xmlns:ns1="*"
	width="640"
	height="480" color="#FFFFFF"
	backgroundGradientAlphas="[1.0, 1.0]"
	backgroundGradientColors="[#FFFFFF, #C0C0C0]">

	<ns1:EngineManager id="engineManager" x="0" y="0" width="100%" height="100%"/>
	<mx:Image x="10" y="10" id="imgAlternativa" source="@Embed(source='../media/alternativa.jpg')"/>

</mx:Application>

Putting it all together is the Alternativa1.mxml file. This is the entry point of the application. As I mentioned earlier the EngineManager class extends the UIComponent class, which allows it to be added as a child of the main Application class. You can see we have added the EngineManager as a child control, just like you would a button or text label.

So what have we created here? The EngineManager class holds the code required to initialize and manage the Alternativa engine. This code will change very little between examples. The ApplicationManager class holds the code that relates to the logic of the application itself. You will find that this code will almost always change between different examples. The BaseObject and MeshObject base classes allow us an easy way to create object that can update themselves in the render loop. These classes will feature heavily in future
tutorials. And finally we have the RotatingBox class that shows you how to extend the MeshObejct class to create a self updating object in the 3D world.


Check out the online demo here, and download the source code here.

Comments (11)

  1. Cool stuff, Matthew! Can we expect more tutorials soon? We’re interested, let me know if you do.

  2. There are several more tutorials in the works. Stay tuned 🙂

  3. That stuff is cool indeed. Have you played around with the Sandy3D, collada and augmented reality yet ? http://ge.ecomagination.com/smartgrid/?c_id=Huff#/augmented_reality

    have fun you are doing great work for designers who arent really into coding.

  4. I’m using Alternativa with Flash CS 4
    (I don’t know Flex Builder)

    could you explain how to replace the MXML with a FLA that would allow your examples to work in Flash – assuming it is that simple?

    many thanks

    .. matt ..

  5. same issue as matt smith, check this

    http://labs.boulevart.be/index.php/2008/06/24/use-alternativa3d-in-flash-not-flex/

    tip: edit .cmd file with notepad

  6. For anyone following alone using Flash Builder (aka Flex4) the required changes to source code examples are:

    import mx.core.Application; = import mx.core.FlexGlobals;
    Application.application = FlexGlobals.topLevelApplication

  7. I am really interested in alternativa3d engine … thank you … and keep introduce more advanced tutorials

  8. Hi! I copied the whole code in the source folder which is inside the project folder. i also copied the media folder inside the project folder.
    I have added a SWC folder for alternativa in the bild path. But when I run the applicaition, it just shows me a blank page. Please help me out.

  9. dharmendra – I had a similar problem – check the Flash player is 10.0.0 is the project settings …

  10. Hi,
    Can you please modify (update) one of your alternativa examples (including engine manager class) to flex 4 sdk which will use spark components application.
    I really need it too much.
    Thanks…

Leave a Reply

Your email address will not be published.

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>