21 September 2011
Prior experience programming with ActionScript 3 is required. A previous background working with 3D graphics is also recommended.
All
This article describes how to work with 3D-engine Alternativa3D version 8. It was developed by AlternativaPlatform and is designed to help you work with 3D-graphics. This 3D-engine leverages Molehill technology, enabling rendering via a graphics processor when working with 3D-graphics. Follow along with these instructions to learn the basics of using Alternativa3D version 8.
To begin working with the engine, read the following theoretical basics and technical details. This information includes the concepts you need to know to start using Alternativa3D.
The 3D world of the Alternativa3D engine has many similarities with the flat Flash Stage environment. Consider the following shared attributes:
Also compare the differences between Alternativa3D and Flash:
To get a better understanding between the heart of 2D and 3D visual objects, consider that Flash graphics are built on points with straight or curved lines. 3D Objects can consist of strokes, fills, or both. Not all Object3d data is visual: the main visual object3d class is mesh. Mesh is also built on points called vertices, but a vertex can hold additional data (such as texture coordinates, normals, tangents, and so on) as well as its own coordinates. Another difference between Flash and Object3D is that verticies can be linked with direct lines only to make a triangle.
The sequence of these triangles form a surface for the 3D object. The surface is comparable to a normal Flash shape, which has a fill but does not have to be flat. The fill in the 3D world is associated with material. Material differs from a fill because each pixel of material can hold data information related to lighting. This information defines the behavior of an object exterior depending on the point of view, the disposition, and the features of lights presented in the 3D world. Some materials are more complex than others, and their complexity may require different sets of data to be successfully rendered.
The simplest material used to fill a 3D object is FillMaterial. This material acts like a solid fill in the Flash environment. FillMaterial does not need any additional data and the surfaces with this material will ignore light settings. The most complex and extensive material in the 3D Alternativa world is StandardMaterial. StandardMaterial requires the maximum additional data in textures as well as in vertex points.
In order for the Surface to work with StandardMaterial, it must contain the following vertex data:
In addition to the vertex data and usual diffuse map used to define image surface fills, StandardMaterial requires a normal map. The texture defines deviations between geometry surface and surface respond to light calculations in order to make the surface look more detailed than its geometry. A flat surface can look rough due to an object's normal map.
To make the surface more attractive and realistic, you can also optionally set additional textures to StandardMaterial fills: glossiness map and specular map.
As mentioned previously, not all Object3d objects are visual. You can work with non-visual classes (including but not limited to working with Mesh). The visual classes are described below.
Visual classes:
Mesh:Static 3D object that consists of surfaces.
Primitives(Box and GeoSphere): Subclasses of mesh which include built-in visual forms generated during the creation processs according to it's name.
Skin: Subclass of mesh which includes joints (bones) to help move and animate parts of solid object.
Decal:subclass of Mesh with a z-fighting suppression engine. Use Decals to add additional marks on top of existing mesh, such as bullet holes and blood splash that may occur as the game is played.
WireFrame:Stroke analogue in the 3D world which displays lines (such as edges of mesh or axis).
SkyBox:Cubic primitive designed to show the 3D environment. It has a few differences with Box because normals are turned inside and permanently exist at the "bottom layer" to ensure SkyBox does not overlap with any other object.
Sprite3d and AnimSprite: Flat pictures/animations which live in the 3D world but are always turned towards the camera; they cannot have any perspective distortion.
Non-visual classes:
Camera3D - Object3D: Determines the view displayed on screen: the point of view, field of view, and the camera's direction.
Light3D: Base class for all lights in Alternativa3D: OmniLight, DirectionalLight, and AmbientLight. Use Light3D to light surfaces with appropriate materials, such as VertexLightMaterial and StandardMaterial.
Joints: These are also known as bones. Use Joints to move (animate) Object3D parts of solid mesh. Joints act like container for a set of vertices but one vertex can be handled by up to 8 joints and each vertex includes a value of power to affect each joint.
Flash Player 11 includes hardware acceleration for rendering 3D. In order to leverage this functionality, you must follow some guidelines in order to be compatible with the player.
A hardware-rendered image is not included in the classic DisplayTree. Instead, it exists in a special stage: stage3d. Any application can have four different stage3d elements, all placed as layers on top of each other but below the DisplayTree. This ensures that flat Flash objects will always overlap the 3D image.
When you work with the hardware API you must load all of the data required to render image with GPU into video memory. The rendering data is associated with resources in Alternativa3D. There are two general types of resources: vertex data and textures. Alternativa3D offers several different ways of dealing with resources. You can begin by using the simplest strategy: obtain all resources of the object and all its children by using the method getResources(true).
In some cases, when you use TexturesLoader to load textures from external files, you must also upload texture resources to video memory. When working with textures, remember that a texture's width and height should divisible by the power of 2. For example, 512 x 256 pixels is a good texture dimension, but there's no way to apply a texture as 200 x 300 pixels.
When you build a normal Flash application, the project is inherited from the DisplayObject class, such as Sprite or MovieClip to add the necessary requirements. In contrast, when building an Alternativa application, you need to set up the 3D world and connect the parts with the flat screen.
First, create the root object of the 3D world. This can be an Object3D instance, named rootContainer.
rootContainer = new Object3D();
Next, add the Camera3D object in rootContainer hierarchy, like this:
camera = new Camera3D(10, 1000);
rootContainer.addChild(camera);
When creating a camera instance, you'll pass two arguments: nearClipping and farClipping which determine the depth of the space the camera can observe, working with the z-buffer used in the hardware rendering engine. The rendering engine draws objects one by one and requires information if some parts of the already drawn objects are placed nearer than those parts that are being processed in the same part of the screen. These values determine areas that should be hidden, rather than drawn. In order to calculate the depth of the visible space, the engine maintains the distance to the camera while drawing each pixel. If the camera's distance value setting is close to a pixel's location, the rendering engine leaves this pixel as is.
GPU has a fixed memory size to keep its distance to the camera (called the z-buffer). As the depth of space increases between nearClipping and farClipping, the precision of distance becomes less accurate. For this reason, it is best to keep the nearClipping and farClipping values closest to each other. Attempts to render multiple objects placed close to each other can cause visual artifacts to occur (called Z-fighting). You can use the Decal class to avoid visual artifacts, because the overlapping marks should be very close to other surfaces.
Finally, you'll create a viewport for the camera. The view determines the size of the visible area, the color of the background, and other properties.
camera.view = new View(stage.stageWidth, stage.stageHeight, false, 0x404040, 0, 4);
The camera view must exist in the regular DisplayTree, so use the addChild method to call it, like this:
addChild(camera.view);
In order to see instances appear on the Stage, be sure to add the camera view to the rootContainer hierarchy too.
After combining the code snippets described above, you can use the full code example to create an application:
package {
import alternativa.engine3d.core.Camera3D;
import alternativa.engine3d.core.Object3D;
import alternativa.engine3d.core.View;
import flash.display.Sprite;
[SWF(backgroundColor h3. "0x909090", width "800", height = "600")]
public class HelloBox extends Sprite {
private var camera:Camera3D;
private var rootContainer:Object3D;
public function Template() {
camera = new Camera3D(0.01, 10000000000);
camera.view = new View(stage.stageWidth, stage.stageHeight, false, 0x404040, 0, 4);
addChild(camera.view);
rootContainer = new Object3D();
rootContainer.addChild(camera);
}
}
}
Whenever you create a new object, it uses the default coordinates of 0,0,0 which are associated with the origin of its parent. In the code example above, the camera was placed in the center of the rootContaner pointing upwards. Even if you added some geometry to rootContainer, the camera would not see anything since the new geometry is added to the center; it will surround the camera. All geometry in Alternativa3D has only one side and can not be seen from inside. (The visible side is determined by the normals of the surface but from the inside unlike from the outside it is invisible—SkyBox is an example of this). In order to see the elements of the project, you'll need to move and turn the camera. You can do so by setting the coordinates and rotation properties of the camera but most projects require other strategies to calculate these values. In many cases, the user may control the camera with the keyboard and the mouse as the application is running; in other projects you can set the camera to point at a given object. However, for the purposes of building a simple application like the one described here, you can use the SimpleObjectController class in Alternativa3D. This camera controls the camera position with the keyboard and mouse and includes a set of methods you can use to handle the camera with ActionScript code, such as the lookAt() method.
camera.x = -50;
camera.y = -300;
camera.z = 100;
controller = new SimpleObjectController(stage, camera, 200);
controller.lookAtXYZ(0,0,0);
Remember these two points when working with the controller:
controller.update() method. If the camera is controlled with a keyboard and a mouse add this call to an enterFrame handler.controller.update() is called. To avoid issues, use the throughcontroller.setPosition() method to change the placement of the camera. In the previous section you learned how to set up the 3D world. In this section you'll learn how to integrate the application with GPU. To begin, you'll set up an instance of Stage3D and its property, context3D, which is associated with the video memory.
stage3D = stage.stage3Ds[0];
In order to use GPU, all resources must be loaded into the video memory. You can use the upload() method to get context3D as a parameter specifying the destination of this upload. You may be surprised that stage3D is not pre-configured to work context3D. You must create it, and then start to do something with context3D after it is initalized.
stage3D.addEventListener(Event.CONTEXT3D_CREATE, init);
stage3D.requestContext3D();
After creating context3D, you can upload resources and start rendering the animation.
private function init(event:Event):void {
for each (var resource:Resource in rootContainer.getResources(true)) {
resource.upload(stage3D.context3D);
}
addEventListener(Event.ENTER_FRAME, enterFrameHandler)
}
If the app requires rendering every frame, place the call to controller.update() in enterFrameHandler function, like this:
private function enterFrameHandler(event:Event):void {
controller.update();
camera.render(stage3D);
}
At this point, you've completed the base application. If desired, you can save a copy of this project to use as a clear template for your future projects. You can create a copy and reset the Template class name.
However, the template does not contain any visible objects yet. You can use the code snippet below to add a cube and rename the class to HelloBox.
box = new Box();
box.setMaterialToAllSurfaces(new FillMaterial(0x804080));
rootContainer.addChild(box);
Any object is invisible until you fill it with material. In the code example below you'll assign the FillMaterial fill to the surfaces (sides) of the cube:
package {
import alternativa.engine3d.controllers.SimpleObjectController;
import alternativa.engine3d.core.Camera3D;
import alternativa.engine3d.core.Object3D;
import alternativa.engine3d.core.Resource;
import alternativa.engine3d.core.View;
import alternativa.engine3d.materials.FillMaterial;
import alternativa.engine3d.primitives.Box;
import flash.display.Sprite;
import flash.display.Stage3D;
import flash.events.Event;
[SWF(backgroundColor = "0x909090", width = "800", height = "600")]
public class HelloBox extends Sprite {
private var stage3D:Stage3D;
private var camera:Camera3D;
private var rootContainer:Object3D;
private var controller:SimpleObjectController;
private var box:Box;
public function HelloBox() {
camera = new Camera3D(0.01, 10000000000);
camera.x = -50;
camera.y = -300;
camera.z = 100;
controller = new SimpleObjectController(stage, camera, 200);
controller.lookAtXYZ(0,0,0);
camera.view = new View(stage.stageWidth, stage.stageHeight, false, 0x404040, 0, 4);
addChild(camera.view);
rootContainer = new Object3D();
rootContainer.addChild(camera);
box = new Box();
box.setMaterialToAllSurfaces(new FillMaterial(0x804080));
rootContainer.addChild(box);
stage3D = stage.stage3Ds[0];
stage3D.addEventListener(Event.CONTEXT3D_CREATE, init);
stage3D.requestContext3D();
}
private function init(event:Event):void {
for each (var resource:Resource in rootContainer.getResources(true)) {
resource.upload(stage3D.context3D);
}
addEventListener(Event.ENTER_FRAME, enterFrameHandler)
}
private function enterFrameHandler(event:Event):void {
controller.update();
camera.render(stage3D);
}
}
}
In this section, you'll set up the development tools and connect to them to work with the Alternativa3D library.
In the Add Flex SDK window that appears, click Browse and navigate to select the Flex SDK (see Figure 5).
After setting the path, click OK (see Figure 6).
To proceed to the next window, click Next (see Figure 7).
Click the Browse button and navigate to the SWC file to specify the path to Alternativa3D 8 SWC file (see Figure 9).
Click Finish to complete the project creation process (see Figure 10).
params.wmode = "direct"; to set the wmode (see Figure 15):
At this point, everything is ready to go. Verify that the project is configured correctly by creating the red cube using the Red Box code described above. Use the article's example file as a template and compile it (see Figure 16).
When the project runs, you'll see a red rotating box; this indicates that everything is configured correctly and you are ready to move on to the next section.
This section includes a training demo to illustrate how to load the provided 3D models into your Flash applications. If you haven't already, be sure to download the sample files provided at the beginning of this article.
In the previous tests you animated a red rotating cube. The cube does not look very interesting at the moment. To display a more complex model, you'll load data from an external file. (You could also optionally choose to programmatically update the cube with ActionScript code, but it is a less common approach because it requires more work).
It doesn't matter how the model data is brought into to application: you can choose to embed it into the SWF file or load the file in at runtime. In this example you'll use the first option, in order to keep the code as clear and compact as possible.
[Embed("resources/model.dae", mimeType="application/octet-stream")] private static const ModelClass:Class;
Next, you'll convert the model data into a set of Alternativa3D's 3D objects. You can use a special parser class to accomplish this. Since the model is built in Collada, you'll use the ParserCollada method to make one:
var parser:ParserCollada = new ParserCollada();
And add the next command to parse the Collada file:
parser.parse(XML(new ModelClass()), "resources/images/", true);
The new ModelClass() method returns model data as a binary stream. You must cast it to XML since Collada is XML-based. The second argument is the path from your SWF file to the textures folder, so make sure that the textures are stored here. The third argument, trim path, is designed for situations when links to textures in the model file kept with a path do not correspond with their real placement.
The parser has indexed all of the 3D objects from inside the model now. You can get this data using the parser.objects property. Or, if you want to maintain the relationship of objects, use the parser.hierarchy property. This property holds root objects only, but all of the remaining objects exist within those root objects, so adding the root objects to the scene is sufficient.
To ensure that all of the model's objects are interacting at once, place them to separate a container:
modelContainer = new Object3D();
rootContainer.addChild(modelContainer);
for each (object in parser.hierarchy) {
modelContainer.addChild(object);
}
Finally, the last thing to work with are the materials applied to the model. All surfaces created by the parser have been assigned the special ParserMaterial property. This material is used for debugging purposes, not for the final production. It is very similar to TextureMaterial, but it stores additional links to different maps such as ambient, emission, diffuse, specular, shininess, reflective, transparent, and bump. The ParserMaterial can use only one map at a time, but you can choose to use any of them. These maps can be used with the more complex StandardMaterial which utilizes all of them at once. To accomplish this, you'll iterate all surfaces and apply a StandardMaterial to each one, passing to this material all maps from the older material.
var object:Object3D;
// Prepare objects materials
for each (object in parser.objects) {
var mesh:Mesh = object as Mesh;
if (mesh != null) {
for (var i:int = 0; i < mesh.numSurfaces; i++) {
var surface:Surface = mesh.getSurface(i);
if (surface.material != null) {
var material:ParserMaterial = ParserMaterial(surface.material);
surface.material = new StandardMaterial(material.textures["diffuse"], material.textures["bump"], material.textures["specular"], null, material.textures["transparent"]);
}
}
}
}
Place all of the code related to working with the model in the addModel() method to keep the project well-structured.
After preparing the model and the materials, you can use context3D to upload resources. Remember that models retain the links only, not the texture images. So you need to load the necessary image files and the TextureLoader class for the project. You can pass a list of ExternalTextureResources to the texturesLoader.loadResources() method. Since the TexturesLoader class uploads the recently loaded textures into the context3D, you can filter those resources while uploading the rest of the files, using this function:
private function onContextCreate(event:Event):void {
// Upload resources
var context3D:Context3D = stage3D.context3D;
var textures:Vector.<ExternalTextureResource> = new Vector.<ExternalTextureResource>();
for each (var resource:Resource in rootContainer.getResources(true)) {
if (resource is ExternalTextureResource) {
textures.push(resource);
} else {
resource.upload(context3D);
}
}
var texturesLoader:TexturesLoader = new TexturesLoader(stage3D.context3D);
texturesLoader.loadResources(textures);
addEventListener(Event.ENTER_FRAME, onEnterFrame);
}
To make the animation more attractive, add a few lights to the scene:
private function addLights():void {
ambient = new AmbientLight(0xc0d0f0);
rootContainer.addChild(ambient);
dLight = new DirectionalLight(0xFFFFFF);
dLight.intensity = 1.5;
dLight.y = -500;
dLight.z = 500;
dLight.lookAt(0, 0, 0);
rootContainer.addChild(dLight);
}
And make the animation more expressive by varying the color, intensity, and direction of the lights:
private function onEnterFrame(e:Event = null):void {
modelContainer.rotationZ += .01;
modelContainer.z = 30 * Math.sin (modelContainer.rotationZ * 3);
dLight.rotationZ -= 0.01;
var kappa:Number = .5 * (Math.sin(2 * dLight.rotationZ) + 1);
dLight.color = 0xff0000 + ((kappa * 0xff) << 8) + (kappa * 0xff);
dLight.intensity = .5 + kappa ;
ambient.intensity = .5 + kappa;
controller.update();
camera.render(stage3D);
}
Here is the complete code example for the project:
package {
import alternativa.engine3d.controllers.SimpleObjectController;
import alternativa.engine3d.core.Camera3D;
import alternativa.engine3d.core.Object3D;
import alternativa.engine3d.core.Resource;
import alternativa.engine3d.core.View;
import alternativa.engine3d.lights.AmbientLight;
import alternativa.engine3d.lights.DirectionalLight;
import alternativa.engine3d.loaders.ParserCollada;
import alternativa.engine3d.loaders.ParserMaterial;
import alternativa.engine3d.loaders.TexturesLoader;
import alternativa.engine3d.materials.StandardMaterial;
import alternativa.engine3d.objects.Mesh;
import alternativa.engine3d.objects.Surface;
import alternativa.engine3d.resources.ExternalTextureResource;
import flash.display.Sprite;
import flash.display.Stage3D;
import flash.display3D.Context3D;
import flash.events.Event;
[SWF(width = 800, height = 800, backgroundColor = 0x303030, frameRate = 100)]
public class GorgylDemo extends Sprite {
[Embed("resources/model.dae", mimeType="application/octet-stream")] private static const ModelClass:Class;
private var stage3D:Stage3D;
private var rootContainer:Object3D = new Object3D();
private var controller:SimpleObjectController;
private var camera:Camera3D;
private var dLight:DirectionalLight;
private var ambient:AmbientLight;
private var modelContainer:Object3D;
public function GorgylDemo() {
camera = new Camera3D(10, 100000);
camera.view = new View(stage.stageWidth, stage.stageHeight, false, stage.color, 1, 4);
addChild(camera.view);
camera.y = -500;
controller = new SimpleObjectController(stage, camera, 100);
controller.lookAtXYZ(0,0,-50);
rootContainer.addChild(camera);
addLights();
addModel();
stage3D = stage.stage3Ds[0];
stage3D.addEventListener(Event.CONTEXT3D_CREATE, onContextCreate);
stage3D.requestContext3D();
}
private function addLights():void {
ambient = new AmbientLight(0xc0d0f0);
rootContainer.addChild(ambient);
dLight = new DirectionalLight(0xFFFFFF);
dLight.intensity = 1.5;
dLight.y = -500;
dLight.z = 500;
dLight.lookAt(0, 0, 0);
rootContainer.addChild(dLight);
}
private function addModel():void {
var parser:ParserCollada = new ParserCollada();
parser.parse(XML(new ModelClass()), "resources/images/", true);
var object:Object3D;
// Prepare objects materials
for each (object in parser.objects) {
var mesh:Mesh = object as Mesh;
if (mesh != null) {
for (var i:int = 0; i < mesh.numSurfaces; i++) {
var surface:Surface = mesh.getSurface(i);
if (surface.material != null) {
var material:ParserMaterial = ParserMaterial(surface.material);
surface.material = new StandardMaterial(material.textures["diffuse"], material.textures["bump"], material.textures["specular"], null, material.textures["transparent"]);
}
}
}
}
modelContainer = new Object3D();
rootContainer.addChild(modelContainer);
for each (object in parser.hierarchy) {
modelContainer.addChild(object);
}
}
private function onContextCreate(event:Event):void {
// Upload resources
var context3D:Context3D = stage3D.context3D;
var textures:Vector.<ExternalTextureResource> = new Vector.<ExternalTextureResource>();
for each (var resource:Resource in rootContainer.getResources(true)) {
if (resource is ExternalTextureResource) {
textures.push(resource);
} else {
resource.upload(context3D);
}
}
var texturesLoader:TexturesLoader = new TexturesLoader(stage3D.context3D);
texturesLoader.loadResources(textures);
addEventListener(Event.ENTER_FRAME, onEnterFrame);
}
private function onEnterFrame(e:Event = null):void {
modelContainer.rotationZ += .01;
modelContainer.z = 30 * Math.sin (modelContainer.rotationZ * 3);
dLight.rotationZ -= 0.01;
var kappa:Number = .5 * ( Math.sin(2 * dLight.rotationZ) + 1);
dLight.color = 0xff0000 + ((kappa * 0xff) << 8) + (kappa * 0xff);
dLight.intensity = .5 + kappa ;
ambient.intensity = .5 + kappa;
controller.update();
camera.render(stage3D);
}
}
}
Compile the completed project to see the result (see Figure 17).
Hopefully this article has helped you learn some basics to continue working with Alternativa3D version 8. For more information visit the following online resources: