Monday, May 28, 2007

lets have a 3D walk

We need to extend the knowledge of Flex to create a basis for the whole 3D application. However, the MXML files should only posses presentation layer not the logic itself. So we move all the logic to another AS class and just invoke it from main MXML file. Of course we would start with the Application tag and some Panel inside it to actually be able to view the data. But what we need to do next is to create a new graphical component to use for PV3D (remember that originally PV3D was developed for Flash not for Flex). Designing own components gives also great benefits which we will use. Probably the main reason of doing so is to make the component and all the logic that it will be doing as an independent part (PV3D visualization in this case). We would have a reusable component in that case that we can use several times, and easily port it to other projects for instance. In Adobe Flex 2.0 we can create components in several ways, depending on what we want to achieve. Before creating new component verify all the existing ones so that you don’t double the work. If you find something that you can use you can extend it (for example customized Button, Tree or DataGrid components). We would however focus only on developing components in ActionScript not in MXML which is also an option, but gives much less customizability. So practically you have three ways you want to create your component:


· Compile several components into one more sophisticated.

· Extend existing component, giving some additional functionality to it.

· Create a whole new component by extending the fundamental UIcompoenent class.


Of course as you can expect the third option on creating new components is most beneficial for us because we can mess with the code at the basis and this is the way we are going to design new UIcomponent for PV3D.

If you look at the component structure you will see that all the visual components are subclasses of UIComponent class. If you extend that class to create a new one, you will get all the methods and properties that the class was holding. The same happens here. The minimum for you to fulfill is to create a class constructor. Overriding the rest of the methods depends on what you want to get. There are some fundamental methods that component has. You don’t invoke those methods manually. There are invoked automatically in the component life cycle. Here are some of them (you can of course override one or few of them if you want, we will do that later).


· layoutChrome() – if your component would be serving as a container class this is where you can define a border area around the container. The method is invoked automatically when a call to method invalidateDisplayList() occurs.

· commitProperties() – commits the changes to component properties. Invoked with invalidateProperties() method.

· measure() – sets the size of the component, default and minimum. Invoked along with invalidateSize() method.

· createChildren() – if the component has any children, this is where they are created. For instance ComboBox control contains TextInput which would be created here. Invoked when addChild() method was used.

· updateDisplayList() – draws the component and the structure of its children on the screen using component properties. The parent container for the component determines the size of the component itself. Nothing is displayed until that method is invoked. invalidateDisplayList()is the method which calls it. The updateDisplayList() method is called exactly after next render event appears after calling invalidateDisplayList().


As I mentioned before the invocation of those methods is based on the lifecycle which describes the order of steps that are taken to create a UIComponent. All this lifecycle is happening behind the stage but it will be good for us just to have an outline how it works, when the methods are called, what events are dispatched and when the component is visible. Let’s consider a fairly simple example:


var container:Box = new Box();

var button:Button = new Button();

b.label = “Aloha World!”;

container.addChild(button);


In the first line the component constructor is invoked. Then some of its properties are set, nothing special at all. However the component setter method could already call invalidateProperties(), invalidateSize(), or invalidateDisplayList() methods. In the last line the component is added to its parent. After that Flex releases the flow:


· Sets the parent property of component.

· Computes the style settings.

· Dispatches the pre-initialize event on the component.

· Calls the createChildren() method.

· Calls invalidation methods (Properties, Size, DisplayList) so that they trigger calls to the methods described earlier.

· Dispatches the initialize event on the component. The component is not laid out yet.

· Dispatches the childAdd event on the container.

· Dispatches the initialize event on the parent container.


Next render event brings following actions:


· Call the commitProperties()

· Call the measure()

· Call layoutChrome()

· Call updateDisplayList()

· Dispatch updateComplete event on component.


When the last render event occurs Flex performs actions:


· Property visible = true. Makes it actually visible.

· Dispatches creationComplete event on the component (only once). Component is seized and processed for layout.

· Dispatches the additional updateComplete event which is also dispatched whenever position, layout, size or other visual properties are changed.


I know it might look a bit complex, so let me simplify this flow into just few steps that actually should be interesting for us and worth remembering. The events indicate when the component is created, plotted, drawn. I will put it graphically for you to get a better perspective.

pre-initialize

Initialize

creationComplete

updateComplete


Each of these shapes is an event that is dispatched during the component creation lifecycle. If it comes to a component that is a container which means that it has some other components inside it, the whole process is a bit extended (includes creation cycle for each child that container has). There is also an event that is dispatched at the very far end, after Application container tag has created everything. The Application object dispatches the applicationComplete event which is the last event during the startup.

So, after digesting all that knowledge first of all lets write a new UIComponent that would be a kind of canvas for our KML object. How we do that? We start with the ActionScript file and class clause that extends UIComponent class. Then we need to put the body, which will be in our case a Sprite object and Rectangle object. The Sprite Object as I mentioned in the technology chapter, is a substitute for MovieClip in new version of AS. The Sprite object is considered as a basic display list building block. It can be used as a Display Object or as a DisplayObjectContainer and posses children. As you might remembered we will also use this object because we want to create a Scene3D object from PV3D engine in order to keep all the objects inside. And Scene3D object uses Sprite object in its constructor. So this sprite will be exactly a display area for the three dimensional world. Another object, the rectangle object we will use to build a bounding box for view. We will create the rectangle depending on the specific container height and width values, so that we can organize a better layout structure of the application. The rectangle will be then sent to be the scrollRect object of IUIComponent, which is an interface of UIComponent and will automatically create a bounding box for us. If we have those two variables declared we then create a constructor (don’t forget to invoke super method inside constructor first).

In our case we will need to override just two of the functions from the UIComponent package. The first one is createChildren() method. As advised by Adobe we have to check in the beginning if the children were already created. If not then we create them, set their properties and finally use addChild() method. The reason of checking if children were created is for future purposes (further extensions of class). In our case we just create a new Sprite object and add it to the component. The second one is of course the updateDisplayList() which is responsible for actually drawing the whole structure (more detailed description for both methods can be found above).

We also set the Rectangle object boundaries with the parameters that were given to the method. These parameters we would set in mxml file while creating the whole application (width and height attributes in a tag). After that we can set the rectangle as a scrollRect object from UIComponent interface. We do that by simply putting a line:


scrollRect = clipRect


We created the UIComponent that we can use as a canvas and fetch it to PV3D engine. The next thing is a main mxml file that will hold the Application tag and all other components, including the one that we just designed. So we start with Application tag, we put a normal mxml namespace as attribute. We then add our namespace, where we hold our component class. Then we put a Panel Component to keep the layout (inside the Application tag of course) and as his child we add our component which will be Canvas3D class.


If we have an outline, we are ready to dig the details. First of all we will need id attributes for Panel and Canvas3D object. So we name them as “mainPanel” and “mainCanvas” respectively. The exact thing what we want to do is after the mxml file is laid out is, we want to pass the Canvas3D object to PV3D engine. Let us consider PV3D engine as an object for now (an AS class). How we do that? We use the applicationComplete event that is dispatched after the whole mxml file is laid out (last event that occurs) to invoke a PV3D engine object with our canvas as a parameter. To catch the object we put a new attribute into Application tag “applicationComplete” which will point at the method that we want to invoke after the event is dispatched. Just after the opening Application tag we put an ActionScript section which will have body of the method that we want to invoke. The AS section has to be wrapped with specific tags which I will show you in a second (so that they are not confused with mxml coding by compiler). The body of the function would have to create a new kml object from given URL (later we might use a text box to pass the URL) and a new PV3D engine object to which we pass our canvas object and created kml object (we pass them to constructor).


In the function we passed the canvas and kml object to the PV3D object that will take over the drawing logic of the whole world from those two ingredients. So now the thing that we need is that PV3D engine class that will bind the given kml data with the PV3D engine and draw things on the given canvas that will be visible later in swf file.

Let’s create a new class. In constructor we save the references to canvas and kml object that were passed. We also initialize the stage properties. To achieve the stage object (a main container of the whole application – like stage in old flash) we can use the static method of the Application class or just the stage property of canvas that we were given. A flash application has only one stage object. Every DisplayObject (object that can be put into display list) has a stage property which refers to the same stage object. We can set the world graphics quality on stage object using its quality property and static values LOW, MEDIUM, HIGH from StageQuality class. The next thing to remember is that we will need the stage listeners to handle events like keyboard hits for instance. So as for now we set three listeners which will be as follows. I explain in detail how they work later.


· onEnterFrame - would be a method responsible for updating the view. The event that it is going to catch this listener is ENTER_FRAME event which will be dispatched with speed equal to one that we set as a framerate for the whole application (in other words can be considered as frame render).

· keyDownHandler - would catch the KEY_DOWN event. Used for navigation with keyboard.

· keyUpHandler would catch the KEY_UP event. Same as above.


We add the listeners to the stage object in a following manner:


stageObject.addEventListener(‘event name’, ‘method to handle event’);


We are ready now to set the stage with some objects. First of all we create the Scene3D object which would be a main container for the world. As mentioned before we use Sprite object from our Canvas3D component. That Sprite we fetched to the constructor of the class that we are developing at the moment. We use it as one and only parameter in Scene3D constructor. After that we will have a DisplayObjectContainer3D (Scene3D class) based on given DisplayObjectContainer (Sprite class) from Flex Application.

Then we create a camera object. We use the FreeCamera3D object from the PV3D engine. The camera object takes several parameters. We will focus just on two of them. This is quite important issue because it affects the way how the objects would be visible, the clipping planes, etc. We will set the first parameter to 6 which is the zoom value. The second one would be the front clipping plane, in other words how close we can get to object before it vanishes. We set its value to 100. If we set the values like that we need to place the camera really far away which will be 5000 units away in this case. The values I gave here are result of my experiments, which give considerably good viewing results. Free feel to tweak them anyway you want.

We will need also a main root object which will hold all the rest of objects so that we would be able to rotate the whole world around its pivot for instance. We add the rootNode which will be a DisplayObject3D object from PV3D to our Scene3D object which is DisplayObjectContainer3D. If you look at the inheritance tree of AS3 you will find a very similar dependency of DisplayObject and DisplayObjectContainer (pure Flex) with DisplayObject3D and DisplayObjectContainer3D (PV3D engine). Understanding the basic structure of classes for setting a 3D world in Flex is quite important and easy, so let me quickly review again the dependencies. Creating the 3D world in Flex would be setting an object based on a DisplayObjectContainer (Sprite for instance), then creating a DisplayObjectContainer3D based on the latter object (viewing 3D is after all a sequence of 2D images). After that we add DisplayObject3D objects to our DisplayObjectContainer3D if it is connected with 3d graphics, and additional DisplayObject objects to DisplayObjectContainer if those are panels, text inputs or buttons. That way we can project a quite nice layout with component for viewing 3D graphics in it.

Now we add the rootNode (which is an empty object) just like any other child using addChild method to scene3D. After that all new children that we add, we add to rootNode. With the constructor we were given also a kml object which was filled with data from a file specified (statically for now) in main mxml file. So of course we have access to the all the data that is available from within kml object. For us, as we are ready to add any data that is suitable for PV3D engine, we will use MESH_ARRAY objects available by simply accessing the MESH_ARRAY array from kml object. So how we do that? We project a simple loop that would be going through all the elements of MESH_ARRAY and for each child we get the reference to mesh3D object, then translate it with the translation vector calculated before by Kml class and available from MESH_TRANSLATION array. We translate meshes using it’s translate method among each axis with given distance (we don’t need to translate along the z axis, (0,0,1) case). The algorithm should be something like that.


loop (i=0; i <>

ourMesh = kml.MESH_ARRAY[i]

distances =kml.MESH_TRANSLATION[i]

ourMesh.translate (distances.x , new Number3D (1,0,0))

ourMesh.translate (distances.y , new Number3D (0,1,0))

rootNode.addChild(ourMesh)


After that we have added all the meshes that were created within kml object to rootNode object. The next step is finally rendering the screen and actually seeing something. To do that we have to execute the renderCamera method from the Scene3D object. It takes a camera object as a parameter, which we have already created and added. This method however will have to be invoked not in initialization process but exactly inside the method that is responsible for updating the view which is in our case connected with the ENTER_FRAME event and method onEnterFrame. So that renderCamera method is invoked at framerate speed that we’ve set. So let there be light, we can see some objects! In next step we will set up a bird-eye camera and get 3d models inside...



By Marcin Czech

No comments: