So, after a few problems with my old blogger account I've finally moved to this new address : blog.tartiflop.com!
Its not been as straight-forward as I was hoping for so sorry for the delay since my last First Steps post... but promise, the next one shouldn't be too far away! Thanks for all the encouragement I've been getting over the last few weeks... its been a real surprise and I'm really happy that people have found these posts useful...
Anyway, hope there's no problems with the change in address... but of course there's been quite a bit of work necessary to get all the links altered correctly so if you do find anything wrong just drop me a line.
As I mentioned before, the next First Steps post should be along soon... see you hopefully before the end of the month!
Tuesday, September 23, 2008
Sunday, September 7, 2008
Musical Interlude - PJ Harvey
Blog has moved - please update your bookmarks!
You're being redirected to its new location at : http://blog.tartiflop.com
Labels:
music
Saturday, September 6, 2008
First steps in Papervision3D : Part 8 - Movie materials
Blog has moved - please update your bookmarks!
You're being redirected to its new location at : http://blog.tartiflop.com
Previous articles summary :
- First steps in Papervision3D : Part 1
Creation of a new Papervision3D project within eclipse or Flex Builder 3 and a simple example of a 3D scene illustrating some basic Papervision3D classes. - First steps in Papervision3D : Part 2
Illustration of use of new Papervision3D v2.0 class BasicView that encapsulates essential elements to rapidly start developing new 3D scenes. - First steps in Papervision3D : Part 3 - animation
Adding some basic animation to the scene by modifying 3D object parameters when entering a frame. - First steps in Papervision3D : Part 4 - lighting and shading
A point light source is added to the scene. Fours materials with different characteristics are added to spheres to illustrate how Papervision3D performs shading. - First steps in Papervision3D : Part 5 - scene interaction
Listen to mouse events to interact with the scene and with individual rendered objects. - First steps in Papervision3D : Part 6 - Texture mapping
Create materials using bitmap data with the aim of having more detailed or realistic objects. - First steps in Papervision3D : Part 7 - Texture mapping with lighting, bump mapping and environment mapping
Advanced materials using specific shaders to enhance texture mapped materials with bump mapping and environment mapping.
This article continues investigating the wide range of materials available in Papervision3D and probably represents the last one of this theme. Whereas the previous examples tried to improve the realism of a 3D scene, this article takes a look at the more dynamic materials available with Papervision3D.
First, apologies for the length of this entry : actually, the more you look at what is available in Papervision3D the more you realise how much it offers! The aim here is to look at MovieMaterials. These offer the ability of having interactive and dynamic surfaces on 3D objects either with Flash movies or Flash video streams. Summarizing this to a few lines probably wouldn't do it justice so I've tried to illustrate here some of the more exciting features offered... and even in doing so am probably still missing a lot!
Anyway, this article introduces two new kinds of materials: MovieMaterial and VideoStreamMaterial (which inherits from the former).
The MovieMaterial allows us to create a material using a pre-existing Flash movie (embedded in a Papervision3D movie) or simply from any MovieClip / Sprite inheriting class instance. Papervision3D provides mapping functions that allow us to interact with these Flash movies with mouse clicks and movements even in a 3D environment.
The VideoStreamMaterial, as its name implies, allows us to stream flash video streams (flv files) onto a 3D object.
The example shown in this article includes these three possibilities including: a flash video stream from a given URL, an embedded standard (non-3D) Flash movie and an example showing a Papervision3D scene being animated as a material in another Papervision3D scene... did I mention before that this article might be quite long?!
So, what we essentially have here are three Actionscript classes: the main 3D scene, a non-3D Sprite-inheriting class and another, secondary Papervision3D scene. I'm only going to discuss the first one here but I'll include the source for the others at the end.
The main source code is shown below. The example shows the three movie materials projected onto a specific face of three projectors (Cube instances), all rotating about the y-axis. The projectors can be double-clicked to enlarge them, stop them from rotating and provide a more simple means of interacting with them. Double-clicking again puts them back with the others. The whole scene can be rotated by clicking on the background and moving the mouse. The code, as warned, is a little longer than hoped for, but we'll look at each part in more details afterwards and really there's nothing very complicated there. I'm using the Tweener library again to provide smoother visual effects (see Part 3 - animation - for more details).
package {
 
  import caurina.transitions.Tweener;
 
  import flash.display.MovieClip;
  import flash.events.Event;
  import flash.events.MouseEvent;
  import flash.media.Video;
  import flash.net.NetConnection;
  import flash.net.NetStream;
 
  import org.papervision3d.core.proto.MaterialObject3D;
  import org.papervision3d.events.InteractiveScene3DEvent;
  import org.papervision3d.lights.PointLight3D;
  import org.papervision3d.materials.MovieMaterial;
  import org.papervision3d.materials.VideoStreamMaterial;
  import org.papervision3d.materials.shadematerials.FlatShadeMaterial;
  import org.papervision3d.materials.utils.MaterialsList;
  import org.papervision3d.objects.DisplayObject3D;
  import org.papervision3d.objects.primitives.Cube;
  import org.papervision3d.view.BasicView;
  [SWF(backgroundColor="#222222")]
  public class Example008 extends BasicView {
 
    private static const ORBITAL_RADIUS:Number = 400;
    [Embed(source="/../assets/DrawTool.swf")]
    private var DrawTool:Class;
    private var exampleMovie:MovieClip;
    private var videoURL:String = "http://www.tartiflop.com/pv3d/FirstSteps/Radiohead_HOC.flv";
    private var video:Video;
    private var stream:NetStream;
    private var connection:NetConnection;
    private var objectGroup:DisplayObject3D;
    private var light:PointLight3D;
    private var currentActiveObject:DisplayObject3D = null;
   
    private var projectors:Array = new Array();
   
    private var doRotation:Boolean = false;
    private var canRotate:Boolean = true;
    private var lastMouseX:int;
    private var lastMouseY:int;
    private var cameraPitch:Number = 60;
    private var cameraYaw:Number = -60;
   
    public function Example008() {
      super(0, 0, true, true);
      // Initialise Papervision3D
      init3D();
      // create video stream for VideoStreamMaterial
      createVideoStream();
      // create the 3D Objects
      createScene();
      // Listen to mouse up and down events on the stage
      stage.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown);
      stage.addEventListener(MouseEvent.MOUSE_UP, onMouseUp);
      // Start rendering the scene
      startRendering();
    }
   
    private function init3D():void {
      // position the camera
      camera.z = -1000;
      camera.fov = 60;
      camera.orbit(cameraPitch, cameraYaw);
    }
    private function createVideoStream():void {
      // Create a NetConnection. 2-way connection not necessary: connect to null
      connection = new NetConnection();
      connection.connect(null);
      // Create a new NetStream to obtain the flv stream. Ignore client messages so use a simple Object
      stream = new NetStream(connection);
      stream.client = new Object();
     
      // create a new video player
      video = new Video();
     
      // start streaming the video from the given URL and play it on the video player
      stream.play(videoURL);
      video.attachNetStream(stream);
    }
    private function createScene():void {
      // Specify a point light source and its location
      light = new PointLight3D();
      light.x = 400;
      light.y = 1000;
      light.z = -400;
      // Create a 3D object to group the projectors
      objectGroup = new DisplayObject3D();
      // Create a new video stream material with precise rendering.
      var videoMaterial:VideoStreamMaterial = new VideoStreamMaterial(video, stream, true);
      addProjector(videoMaterial);
         
      // Create a new flash movie material from an actionscript class (not transparent, animated and precise rendering)
      var movieMaterial1:MovieMaterial = new MovieMaterial(new Example006b(), false, true, true);
      addProjector(movieMaterial1);
      // Create a new flash movie material from an embedded flash movie (not transparent, animated and precise rendering)
      var movieMaterial2:MovieMaterial = new MovieMaterial(new DrawTool(), false, true, true);
      addProjector(movieMaterial2);
   
      // add the object group and light
      scene.addChild(objectGroup);
      scene.addChild(light);
      // set up the projector positions in the scene
      organiseProjectors();
    }
   
    private function addProjector(material:MovieMaterial):void {
      // materials are smooth rendred, interactive and resize to the 3D object.
      material.smooth = true;
      material.interactive = true;
      material.allowAutoResize = true;
      // simple flat shaded material as default for the projector
      var flatShadedMaterial:MaterialObject3D = new FlatShadeMaterial(light, 0x554D33, 0x1A120C);
      flatShadedMaterial.interactive = true;
     
      // Material list with MovieMaterial used on the front, the rest being flat shaded
      var materialList:MaterialsList = new MaterialsList({"all":flatShadedMaterial, "front":material});
      // create a new interactive projector
      var projector:Cube = new Cube(materialList, 320, 10, 240);
      projector.addEventListener(InteractiveScene3DEvent.OBJECT_DOUBLE_CLICK, onMouseDoubleClickOnObject);
      projector.addEventListener(InteractiveScene3DEvent.OBJECT_OVER, onMouseOverObject);
      projector.addEventListener(InteractiveScene3DEvent.OBJECT_OUT, onMouseOutObject);
      // add the projector to the scene, being part of the object group
      objectGroup.addChild(projector);
     
      // store projector in an array
      projectors.push(projector);
    }
   
    private function organiseProjectors():void {
      // calculate angle between projectors
      var theta:Number = 360 / projectors.length;
     
      // set up each projector so that they are distributed in a circle and facing outwards
      for (var i:int = 0; i < projectors.length; i++) {
        var projector:Cube = projectors[i];
       
        // specifc angle for projector
        var angle:Number = i * theta - 180;
        var angleRadians:Number = angle * 2 * Math.PI / 360.;
        // position of projector
        var x:Number = Math.sin(angleRadians) * ORBITAL_RADIUS;
        var z:Number = Math.cos(angleRadians) * ORBITAL_RADIUS;
        // create tween to position, rotate and scale projector smoothly over 1 second
        Tweener.addTween(projector, {x:x, y:-150, z:z, rotationY:angle, scale:0.8, time:1, transition:"linear" });
      }
    }
   
    override protected function onRenderTick(event:Event=null):void {
      // rotate the object group: angle kept between 0 and 360 degrees
      objectGroup.rotationY += 1;
      if (objectGroup.rotationY > 360) {
        objectGroup.rotationY -= 360;
      }
     
      // if an object is active (double clicked) rotate it in the opposite direction
      // to the group so that it is stationary
      if (currentActiveObject != null) {
        currentActiveObject.rotationY -=1;
        if (currentActiveObject.rotationY < 0) {
          currentActiveObject.rotationY += 360;
        }
      }
     
      // If the mouse button has been clicked then update the camera position     
      if (doRotation && canRotate) {
        // convert the change in mouse position into a change in camera angle
        var dPitch:Number = (mouseY - lastMouseY) / 2;
        var dYaw:Number = (mouseX - lastMouseX) / 2;
       
        // update the camera angles
        cameraPitch -= dPitch;
        cameraYaw -= dYaw;
        // limit the pitch of the camera
        if (cameraPitch <= 0) {
          cameraPitch = 0.1;
        } else if (cameraPitch >= 180) {
          cameraPitch = 179.9;
        }
     
        // reset the last mouse position
        lastMouseX = mouseX;
        lastMouseY = mouseY;
       
        // reposition the camera
        camera.orbit(cameraPitch, cameraYaw);
      }
     
      // call the renderer
      super.onRenderTick(event);
    }
    // called when mouse down on stage
    private function onMouseDown(event:MouseEvent):void {
      doRotation = true;
      lastMouseX = event.stageX;
      lastMouseY = event.stageY;
    }
    // called when mouse up on stage
    private function onMouseUp(event:MouseEvent):void {
      doRotation = false;
    }
   
    // called when mouse double clicked on a projector
    private function onMouseDoubleClickOnObject(event:InteractiveScene3DEvent):void {
      var object:DisplayObject3D = event.displayObject3D;
     
      // determine if the object is to be activated (placed in center) or deactivated
      if (object == currentActiveObject) {
        deactivate(object);
      } else {
        activate(object);
      }
    }
   
    // disable camera rotation when mouse is over a projector
    private function onMouseOverObject(event:InteractiveScene3DEvent):void {
      canRotate = false;
    }
   
    // re-enable camera rotation when mouse is out of a projector
    private function onMouseOutObject(event:InteractiveScene3DEvent):void {
      canRotate = true;
    }
   
    // places a projector in the center
    private function activate(object:DisplayObject3D):void {
      // remove projector from rotating projectors array
      projectors.splice(projectors.indexOf(object), 1);
     
      // if a projector is active already, put it back in the array of rotating projectors
      if (currentActiveObject != null) {
        projectors.push(currentActiveObject);
      }
     
      // create a tween to place selected projector in the center
      Tweener.addTween(object, {y:100, x:0, z:0, rotationY:-objectGroup.rotationY, scale:2, time:1, transition:"linear" });
      currentActiveObject = object;
      // re-organise the other projectors
      organiseProjectors();     
    }
   
    // puts an activated projector back into the main pack of rotating projectors
    private function deactivate(object:DisplayObject3D):void {
      // put the projector back into the rotating projectors array
      projectors.push(currentActiveObject);
      currentActiveObject = null; 
   
      // re-organise all projectors
      organiseProjectors();     
    }
   
  }
}
All of this provides the following Flash movie. As mentioned above, double-click on an object to activate it (this actually just means that the object is magnified and stops spinning - it doesn't change any of the object characteristics). Double-click on an activated one to deactivate it (put it back with the others). Two projectors allow for user interactions at any point in time: you can draw on one and rotate the 3D scene on the other. The final projector streams House Of Cards by Radiohead (another Paperivision3D example?!). The whole scene can be rotated by clicking on the background and moving the mouse. Click on the image below to see it all in action.

So, as with the other articles in this series lets take a look at how the scene is constructed step-by-step. As usual, the code is organised in more of less the same way as previous examples. The main difference comes from creating and attaching a video stream and modifying the animation and object interaction.
Let's start with the constructor. The only difference here is the initialisation of the video stream. If you take a look at the source code for the VideoStreamMaterial you'll see that it takes two objects: a Video and a NetStream. These are pure Actionscript objects necessary for streaming the data and displaying it. The Flex language reference for NetConnection came in handy here to see what these objects do and how to create them. A slightly cut-down method is used here but it remains in principal the same.
    private function createVideoStream():void {
      // Create a NetConnection. 2-way connection not necessary: connect to null
      connection = new NetConnection();
      connection.connect(null);
      // Create a new NetStream to obtain the flv stream. Ignore client messages so use a simple Object
      stream = new NetStream(connection);
      stream.client = new Object();
     
      // create a new video player
      video = new Video();
     
      // start streaming the video from the given URL and play it on the video player
      stream.play(localVideoURL);
      video.attachNetStream(stream);
    }
As you can see in the example shown in the Flex livedocs, there are ways to listen to events occurring during the streaming but for this example I've just done a minimum to restrict the length of the code a bit.
Next we come to the scene creation. This again is based on previous examples so we have a light source, an object group to simplify the rotation of a number of objects and then the individual 3D objects, each one with a different MovieMaterial.
    private function createScene():void {
      // Specify a point light source and its location
      light = new PointLight3D();
      light.x = 400;
      light.y = 1000;
      light.z = -400;
      // Create a 3D object to group the projectors
      objectGroup = new DisplayObject3D();
      // Create a new video stream material with precise rendering.
      var videoMaterial:VideoStreamMaterial = new VideoStreamMaterial(video, stream, true);
      addProjector(videoMaterial);
         
      // Create a new flash movie material from an actionscript class (not transparent, animated and precise rendering)
      var movieMaterial1:MovieMaterial = new MovieMaterial(new Example006b(), false, true, true);
      addProjector(movieMaterial1);
      // Create a new flash movie material from an embedded flash movie (not transparent, animated and precise rendering)
      var movieMaterial2:MovieMaterial = new MovieMaterial(new DrawTool(), false, true, true);
      addProjector(movieMaterial2);
   
      // add the object group and light
      scene.addChild(objectGroup);
      scene.addChild(light);
      // set up the projector positions in the scene
      organiseProjectors();
    }
As you can see the three MovieMaterials (VideoStreamMaterial inherits from this) are simple to create. Firstly the VideoStreamMaterial takes the Video and NetStream we created just before and I've chosen precise rendering to minimise perspective distortions. The other two MovieMaterials take in one case a Actionscript object and an embedded Flash movie in the other (see the start of the class definition to see the embedding, which is identical to how we embedded images in previous examples). The three boolean values are associated with transparent, animated and precise rendering arguments. So since the Flash movie objects are animated we need to specify true for the animated argument to ensure that the scenes are updated.
The scene is then populated with the object group (containing the 3D objects) and the light. The positioning of the 3D objects is delegated to the organiseProjectors function which we'll come to shortly.
In this example I'm using the Cube primitive. Each one has a specific face (the "front") showing the MovieMaterial and since each one has essentially the same characteristics I've factorised the code to initialise each one identically.
    private function addProjector(material:MovieMaterial):void {
      // materials are smooth rendred, interactive and resize to the 3D object.
      material.smooth = true;
      material.interactive = true;
      material.allowAutoResize = true;
      // simple flat shaded material as default for the projector
      var flatShadedMaterial:MaterialObject3D = new FlatShadeMaterial(light, 0x554D33, 0x1A120C);
      flatShadedMaterial.interactive = true;
     
      // Material list with MovieMaterial used on the front, the rest being flat shaded
      var materialList:MaterialsList = new MaterialsList({"all":flatShadedMaterial, "front":material});
      // create a new interactive projector
      var projector:Cube = new Cube(materialList, 320, 10, 240);
      projector.addEventListener(InteractiveScene3DEvent.OBJECT_DOUBLE_CLICK, onMouseDoubleClickOnObject);
      projector.addEventListener(InteractiveScene3DEvent.OBJECT_OVER, onMouseOverObject);
      projector.addEventListener(InteractiveScene3DEvent.OBJECT_OUT, onMouseOutObject);
      // add the projector to the scene, being part of the object group
      objectGroup.addChild(projector);
     
      // store projector in an array
      projectors.push(projector);
    }
Each MovieMaterial is smoothed (to appear less pixelated), made interactive (so that the 3D object responds to mouse events) and auto-resized so that they resize automatically to the cube dimensions. The other five faces of the cube are covered in a simple flat-shaded material (as seen in Part 4) which is also interactive. The cube is then constructed with a MaterialList containing these two different materials. Event listeners are then added to the cube so that it responds to double-click events (to activate and deactivate it) and mouse over and out events which, as we'll see later, are used to restrict the stage mouse listeners for rotating the scene (essentially they stop the scene from rotating when a user is interacting with one of the 3D objets).
The new cube is then added to the object group (so that it is rendered) and stored in an Array to allow us to access it later.
As you see in the example, the non-activated projectors are spaced evenly in a circle, facing outwards (the rotation comes simply from a rotation of the object group, handled separately). The function organiseProjectors performs the necessary calculations and animation.
    private function organiseProjectors():void {
      // calculate angle between projectors
      var theta:Number = 360 / projectors.length;
     
      // set up each projector so that they are distributed in a circle and facing outwards
      for (var i:int = 0; i < projectors.length; i++) {
        var projector:Cube = projectors[i];
       
        // specifc angle for projector
        var angle:Number = i * theta - 180;
        var angleRadians:Number = angle * 2 * Math.PI / 360.;
        // position of projector
        var x:Number = Math.sin(angleRadians) * ORBITAL_RADIUS;
        var z:Number = Math.cos(angleRadians) * ORBITAL_RADIUS;
        // create tween to position, rotate and scale projector smoothly over 1 second
        Tweener.addTween(projector, {x:x, y:-150, z:z, rotationY:angle, scale:0.8, time:1, transition:"linear" });
      }
    }
This function quite simply calculates the angle between each projector (Cube instance) and positions them in the x-z plane accordingly. To smoothly position each of them I've used a linear tween to modify the x, y, z, rotationY and scale properties of each of them, taking one second to animate. Thanks to Tweener this is very simple to perform!
Next we come to the onRenderTick function which updates the scene at every movie frame. This is essentially the same as for previous examples in the series
    override protected function onRenderTick(event:Event=null):void {
      // rotate the object group: angle kept between 0 and 360 degrees
      objectGroup.rotationY += 1;
      if (objectGroup.rotationY > 360) {
        objectGroup.rotationY -= 360;
      }
     
      // if an object is active (double clicked) rotate it in the opposite direction
      // to the group so that it is stationary
      if (currentActiveObject != null) {
        currentActiveObject.rotationY -=1;
        if (currentActiveObject.rotationY < 0) {
          currentActiveObject.rotationY += 360;
        }
      }
     
      // If the mouse button has been clicked then update the camera position     
      if (doRotation && canRotate) {
        // convert the change in mouse position into a change in camera angle
        var dPitch:Number = (mouseY - lastMouseY) / 2;
        var dYaw:Number = (mouseX - lastMouseX) / 2;
       
        // update the camera angles
        cameraPitch -= dPitch;
        cameraYaw -= dYaw;
        // limit the pitch of the camera
        if (cameraPitch <= 0) {
          cameraPitch = 0.1;
        } else if (cameraPitch >= 180) {
          cameraPitch = 179.9;
        }
     
        // reset the last mouse position
        lastMouseX = mouseX;
        lastMouseY = mouseY;
       
        // reposition the camera
        camera.orbit(cameraPitch, cameraYaw);
      }
     
      // call the renderer
      super.onRenderTick(event);
    }
The object group is rotated as in previous examples. This time I'm using the rotationY property rather than the function yaw to have a better control of the angle of rotation. The activated object is spun in the opposite direction at the same time so that it is effectively stationary.
The camera rotation is essentially the same as before except that we use the canRotate boolean value to restrict the rotation when the mouse is over a 3D object.
The rest of the code is essentially to handle the mouse events. The onMouseDown, onMouseUp are the same as before to initiate and stop the scene rotation. onMouseOverObject and onMouseOutObject add to this by limiting the rotation when the user is interacting with a 3D object.
To activate and deactivate a projector the double-click event on a 3D object is used.
    // called when mouse double clicked on a projector
    private function onMouseDoubleClickOnObject(event:InteractiveScene3DEvent):void {
      var object:DisplayObject3D = event.displayObject3D;
     
      // determine if the object is to be activated (placed in center) or deactivated
      if (object == currentActiveObject) {
        deactivate(object);
      } else {
        activate(object);
      }
    }
Simply, if the object clicked is the current active object then we deactivate it. If not we activate it.
    // places a projector in the center
    private function activate(object:DisplayObject3D):void {
      // remove projector from rotating projectors array
      projectors.splice(projectors.indexOf(object), 1);
     
      // if a projector is active already, put it back in the array of rotating projectors
      if (currentActiveObject != null) {
        projectors.push(currentActiveObject);
      }
     
      // create a tween to place selected projector in the center
      Tweener.addTween(object, {y:100, x:0, z:0, rotationY:-objectGroup.rotationY, scale:2, time:1, transition:"linear" });
      currentActiveObject = object;
      // re-organise the other projectors
      organiseProjectors();     
    }
When activating a projector, it is removed from the Array of spinning projectors. If another projector is already activated then we put it back into this group. A simple linear tween is then used to reposition the newly activated projector in the center and to rescale it so that it is bigger than the others. We then recall organiseProjectors to reposition the remaining projectors around a circle.
Deactivating a projector simply involves putting it back into the Array and repositioning all of them around a circle.
    // puts an activated projector back into the main pack of rotating projectors
    private function deactivate(object:DisplayObject3D):void {
      // put the projector back into the rotating projectors array
      projectors.push(currentActiveObject);
      currentActiveObject = null; 
   
      // re-organise all projectors
      organiseProjectors();     
    }
So that's all there is to it! Really Papervision3D and Tweener do all the complicated work to display and animated the 3D scene: all that's new here is the creation of the movie materials. Once again I hope this shows that Papervision3D is really very simple to use. Looking at the Papervision3D source code really helps a lot to understand how the materials are created and you'll see that I haven't covered everything but hopefully this gives a good starting point in creating your own 3D scenes with movie materials!
Just for completeness I've included below the source code for the animated movies used for the MovieMaterials. One is a simple 2D, standard Flash animation that reacts to mouse events. The other is based on a previous Papervision3D example shown in this series (from Part 6) but without the InteractiveScene3DEvent handlers. I found it really amazing that one 3D scene can be used as a material in another 3D scene - good work Papervision3D!
Here's drawTool.as...
package {
  import flash.display.Sprite;
  import flash.events.MouseEvent;
  import flash.text.TextField;
  import flash.text.TextFieldAutoSize;
  import flash.text.TextFormat;
 
  public class DrawTool extends Sprite {
    private var isDrawing:Boolean = false;
    public function DrawTool() {
      // create a drawing surface
      graphics.beginFill(0xEEEEEE);
      graphics.moveTo(0, 0);
      graphics.lineTo(320, 0);
      graphics.lineTo(320, 240);
      graphics.lineTo(0, 240);
      graphics.endFill();
     
      // create text and format
      var textFormat:TextFormat = new TextFormat();
      textFormat.size = 30;
      textFormat.font = "Arial";
     
      var text:TextField = new TextField();
      text.x = 50;
      text.y = 100;
      text.textColor = 0x222222;
      text.text = "click to draw!";
      text.setTextFormat(textFormat);
      text.autoSize = TextFieldAutoSize.LEFT;
      text.selectable = false;
      addChild(text);
     
      // listen to mouse events
      this.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown);
      this.addEventListener(MouseEvent.MOUSE_UP, onMouseUp);
      this.addEventListener(MouseEvent.MOUSE_MOVE, onMouseMove);
     
    }
   
    // start drawing circles
    private function onMouseDown(event:MouseEvent):void {
      isDrawing = true;
      drawCircle(event.stageX, event.stageY);
    }
   
    // stop drawing circles
    private function onMouseUp(event:MouseEvent):void {
      isDrawing = false;
    }
   
    // draw a circle
    private function onMouseMove(event:MouseEvent):void {
      if (isDrawing) {
        drawCircle(event.stageX, event.stageY);
      }
    }
    // circle drawing function
    private function drawCircle(x:int, y:int):void {
      graphics.beginFill(Math.random() * 0xFFFFFF, 0.5);
      graphics.drawCircle(x, y, 5);
      graphics.endFill();
    }
   
  }
}
... and finally Example006b.as :
package {
 
  import flash.display.Bitmap;
  import flash.display.Sprite;
  import flash.events.Event;
  import flash.events.MouseEvent;
 
  import org.papervision3d.materials.BitmapMaterial;
  import org.papervision3d.materials.utils.MaterialsList;
  import org.papervision3d.objects.DisplayObject3D;
  import org.papervision3d.objects.primitives.Cube;
  import org.papervision3d.objects.primitives.Sphere;
  import org.papervision3d.view.BasicView;
  [SWF(backgroundColor="#FFFFFF")]
  public class Example006b extends BasicView {
 
    [Embed(source="/../assets/pv3d.png")] private var PV3D:Class;
    private static const ORBITAL_RADIUS:Number = 100;
 
    private var bitmap:Bitmap = new PV3D();
    private var cube1:Cube;
    private var cube2:Cube;
    private var sphere1:Sphere;
    private var sphere2:Sphere;
    private var objectGroup:DisplayObject3D;
   
    private var doRotation:Boolean = false;
    private var lastMouseX:int;
    private var lastMouseY:int;
    private var cameraPitch:Number = 60;
    private var cameraYaw:Number = -60;
   
    public function Example006b() {
      var background:Sprite = new Sprite();
      background.graphics.beginFill(0x000000);
      background.graphics.moveTo(0, 0);
      background.graphics.lineTo(320, 0);
      background.graphics.lineTo(320, 240);
      background.graphics.lineTo(0, 240);
      background.graphics.endFill();
      addChild(background);
     
      super(320, 240, true, false);
      // Initialise Papervision3D
      init3D();
     
      // Create the 3D objects
      createScene();
      // Listen to mouse up and down events on the stage
      background.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown);
      background.addEventListener(MouseEvent.MOUSE_MOVE, onMouseMove);
      background.addEventListener(MouseEvent.MOUSE_UP, onMouseUp);
      // Start rendering the scene
      startRendering();
    }
   
    private function init3D():void {
      // position the camera
      camera.z = -500;
      camera.orbit(cameraPitch, cameraYaw);
    }
    private function createScene():void {
      // create interactive bitmap material
      var bitmapMaterial:BitmapMaterial = new BitmapMaterial(bitmap.bitmapData, false);
      // create an interactive tiled bitmap material (bitmap tiled as 2 x 2)
      var tiledBitmapMaterial:BitmapMaterial = new BitmapMaterial(bitmap.bitmapData, false);
      tiledBitmapMaterial.tiled = true;
      tiledBitmapMaterial.maxU = 2;
      tiledBitmapMaterial.maxV = 2;
     
      // create cube with simple bitmap material
      cube1 = new Cube(getBitmapMaterials(bitmapMaterial), 50, 50, 50);
      cube1.x = ORBITAL_RADIUS;
      // create cube with tiled bitmap material
      cube2 = new Cube(getBitmapMaterials(tiledBitmapMaterial), 50, 50, 50);
      cube2.x = -ORBITAL_RADIUS;
 
      // create sphere with simple bitmap material
      sphere1 = new Sphere(bitmapMaterial, 25, 10, 10);
      sphere1.z = ORBITAL_RADIUS;
      // create sphere with tiled bitmap material
      sphere2 = new Sphere(tiledBitmapMaterial, 25, 10, 10);
      sphere2.z = -ORBITAL_RADIUS;
      // Create a 3D object to group the spheres
      objectGroup = new DisplayObject3D();
      objectGroup.addChild(cube1);
      objectGroup.addChild(cube2);
      objectGroup.addChild(sphere1);
      objectGroup.addChild(sphere2);
      // Add the light and spheres to the scene
      scene.addChild(objectGroup);
    }
   
    private function getBitmapMaterials(bitmapMaterial:BitmapMaterial):MaterialsList {
      // create list of materials for all faces of the cube,
      // all with the same bitmap material
      var materials:MaterialsList = new MaterialsList();
      materials.addMaterial(bitmapMaterial, "all");
     
      return materials;
    }
   
    override protected function onRenderTick(event:Event=null):void {
      // rotate the objects
      cube1.yaw(-3);
      cube2.yaw(-3);
      sphere1.yaw(-3);
      sphere2.yaw(-3);
     
      // rotate the group of objects
      objectGroup.yaw(1);
      // call the renderer
      super.onRenderTick(event);
    }
    // called when mouse down on stage
    public function onMouseDown(event:MouseEvent):void {
      doRotation = true;
      lastMouseX = event.stageX;
      lastMouseY = event.stageY;
    }
    // called when mouse up on stage
    public function onMouseUp(event:MouseEvent):void {
      doRotation = false;
    }
   
    // called when the mouse moves over the stage
    public function onMouseMove(event:MouseEvent):void {
      // If the mouse button has been clicked then update the camera position     
      if (doRotation) {
       
        // convert the change in mouse position into a change in camera angle
        var dPitch:Number = (event.stageY - lastMouseY) / 2;
        var dYaw:Number = (event.stageX - lastMouseX) / 2;
       
        // update the camera angles
        cameraPitch -= dPitch;
        cameraYaw -= dYaw;
        // limit the pitch of the camera
        if (cameraPitch <= 0) {
          cameraPitch = 0.1;
        } else if (cameraPitch >= 180) {
          cameraPitch = 179.9;
        }
     
        // reset the last mouse position
        lastMouseX = event.stageX;
        lastMouseY = event.stageY;
       
        // reposition the camera
        camera.orbit(cameraPitch, cameraYaw);
      }
     
    }
   
  }
}
Labels:
actionscript,
animation,
movie material,
Papervision3D,
Tweener,
video stream
Wednesday, August 27, 2008
Musical Interlude - Nine Inch Nails
Blog has moved - please update your bookmarks!
You're being redirected to its new location at : http://blog.tartiflop.com
Labels:
music
Tuesday, August 26, 2008
First steps in Papervision3D : Part 7 - Texture mapping with lighting, bump mapping and environment mapping
Blog has moved - please update your bookmarks!
You're being redirected to its new location at : http://blog.tartiflop.com
Previous articles summary :
- First steps in Papervision3D : Part 1
Creation of a new Papervision3D project within eclipse or Flex Builder 3 and a simple example of a 3D scene illustrating some basic Papervision3D classes. - First steps in Papervision3D : Part 2
Illustration of use of new Papervision3D v2.0 class BasicView that encapsulates essential elements to rapidly start developing new 3D scenes. - First steps in Papervision3D : Part 3 - animation
Adding some basic animation to the scene by modifying 3D object parameters when entering a frame. - First steps in Papervision3D : Part 4 - lighting and shading
A point light source is added to the scene. Fours materials with different characteristics are added to spheres to illustrate how Papervision3D performs shading. - First steps in Papervision3D : Part 5 - scene interaction
Listen to mouse events to interact with the scene and with individual rendered objects. - First steps in Papervision3D : Part 6 - Texture mapping
Create materials using bitmap data with the aim of having more detailed or realistic objects.
This article is essentially a continuation of the previous one, building on the texture mapped materials. As I said in my last post, the aim now is to add lighting to add more realism and provide depth to the scene - much like we did in Part 4.
Previously for the shading we used specific materials to account for the different shading methods such as GouraudMaterial or PhongMaterial (as you will find in the package org.papervision3d.materials.shadematerials of the pv3d source). These however are based on coloured materials rather than textured materials.
To mix both texture maps and shading we need to create a Shader and add this with the BitmapMaterial (like we created in Part 6) together in a ShadedMaterial. Papervision3D does all the magic necessary to mix the two to create a shaded bitmap material.
As you can see if you look in the org.papervision3d.materials.shaders package in the pv3d source there are a number of different shaders available - some of which we recognise from Part 4. The list includes FlatShader, GouraudShader, PhongShader, CellShader and EnvMapShader.
The last one of the above allows us to add an environment map to the material which essentially is like adding lighting from a surrounding environment and makes the material look like it is highly reflective. Take a look at this wikipedia page on reflection mapping for more details.
Another concept that is important in producing more realistic scenes is bump mapping. This is basically an optimised way of modifying the lighting of a surface so that it appears as if the surface has bumps in it. It can make a big difference to a rendered object in terms of realism without having to specify many object vertices. Again, wikipedia has lots of information on bump mapping. With Papervision3D we can add bump maps to enhance both the Phong and environment shaders.
So, on with the code... As usual, I'm modifying the code from the previous article so there's not a huge change. However to account for the different number of shaders, rather than show all of them at once I'm only showing one sphere at a time so that you get a better idea of what each shader does. To change the shader I've modified the scene interaction so that clicking on the sphere changes the material dynamically - another great feature of Papervision3D! Anyway here's the code:
package {
 
  import flash.display.Bitmap;
  import flash.display.StageAlign;
  import flash.display.StageScaleMode;
  import flash.events.Event;
  import flash.events.MouseEvent;
  import flash.text.TextField;
  import flash.text.TextFieldAutoSize;
  import flash.text.TextFormat;
 
  import org.papervision3d.events.InteractiveScene3DEvent;
  import org.papervision3d.lights.PointLight3D;
  import org.papervision3d.materials.BitmapMaterial;
  import org.papervision3d.materials.shaders.CellShader;
  import org.papervision3d.materials.shaders.EnvMapShader;
  import org.papervision3d.materials.shaders.FlatShader;
  import org.papervision3d.materials.shaders.GouraudShader;
  import org.papervision3d.materials.shaders.PhongShader;
  import org.papervision3d.materials.shaders.ShadedMaterial;
  import org.papervision3d.materials.shaders.Shader;
  import org.papervision3d.objects.DisplayObject3D;
  import org.papervision3d.objects.primitives.Sphere;
  import org.papervision3d.view.BasicView;
  public class Example007 extends BasicView {
 
    [Embed(source="/../assets/pv3d.png")] private var Pv3dBitmapImage:Class;
    [Embed(source="/../assets/randomBump.png")] private var BumpImage:Class;
    [Embed(source="/../assets/mountains.png")] private var EnvImage:Class;
    private var pv3dBitmap:Bitmap = new Pv3dBitmapImage();
    private var bumpMap:Bitmap = new BumpImage();
    private var envMap:Bitmap = new EnvImage();
    private var bitmapMaterial:BitmapMaterial;
   
    private var sphere:Sphere;
    private var light:PointLight3D;
   
    private var doRotation:Boolean = false;
    private var lastMouseX:int;
    private var lastMouseY:int;
    private var cameraPitch:Number = 60;
    private var cameraYaw:Number = -60;
   
    private var shaders:Array = ["flat", "cell", "gouraud", "phong", "phongBump", "env", "envBump"];
    private var shaderIndex:int = 0;
   
    private var shaderText:TextField;
    private var textFormat:TextFormat;
    public function Example007() {
      super(0, 0, true, true);
     
      // set up the stage
      stage.align = StageAlign.TOP_LEFT;
      stage.scaleMode = StageScaleMode.NO_SCALE;
      // Initialise Papervision3D
      init3D();
     
      // Create the 3D objects
      createScene();
      // Listen to mouse up and down events on the stage
      stage.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown);
      stage.addEventListener(MouseEvent.MOUSE_UP, onMouseUp);
      stage.addChild(shaderText);
      // Start rendering the scene
      startRendering();
    }
   
    private function init3D():void {
      // position the camera
      camera.z = -500;
      camera.orbit(60, -60);
    }
    private function createScene():void {
      // create text and format to display current shader type
      textFormat = new TextFormat();
      textFormat.size = 20;
      textFormat.font = "Arial";
     
      shaderText = new TextField();
      shaderText.x = 50;
      shaderText.y = 50;
      shaderText.textColor = 0xFFFFFF;
      shaderText.text = "flat";
      shaderText.setTextFormat(textFormat);
      shaderText.autoSize = TextFieldAutoSize.LEFT;
      // Specify a point light source and its location
      light = new PointLight3D(true);
      light.x = 500;
      light.y = 500;
      light.z = -200;
      // create bitmap material with smoothing
      bitmapMaterial = new BitmapMaterial(pv3dBitmap.bitmapData, false);
      bitmapMaterial.smooth = true;
 
      // create sphere
      sphere = new Sphere(getShadedBitmapMaterial(bitmapMaterial, "flat"), 150, 20, 20);
      // Add a listener to the spheres to listen to InteractiveScene3DEvent events
      sphere.addEventListener(InteractiveScene3DEvent.OBJECT_CLICK, onMouseDownOnObject);
      // Add the light and sphere to the scene
      scene.addChild(sphere);
      scene.addChild(light);
    }
   
    private function getShadedBitmapMaterial(bitmapMaterial:BitmapMaterial, shaderType:String):ShadedMaterial {
      var shader:Shader;
     
      if (shaderType == "flat") {
        // create new flat shader
        shader = new FlatShader(light, 0xFFFFFF, 0x333333);
      } else if (shaderType == "cell") {
        // create new cell shader with 5 colour levels
        shader = new CellShader(light, 0xFFFFFF, 0x333333, 5);
      } else if (shaderType == "gouraud") {
        // create new gouraud shader
        shader = new GouraudShader(light, 0xFFFFFF, 0x333333);
      } else if (shaderType == "phong") {
        // create new phong shader
        shader = new PhongShader(light, 0xFFFFFF, 0x333333, 50);
      } else if (shaderType == "phongBump") {
        // create new phong shader with bump map
        shader = new PhongShader(light, 0xFFFFFF, 0x333333, 50, bumpMap.bitmapData);
      } else if (shaderType == "env") {
        // create new environment map shader
        shader = new EnvMapShader(light, envMap.bitmapData, envMap.bitmapData, 0x333333);
      } else if (shaderType == "envBump") {
        // create new environment map shader with bump map
        shader = new EnvMapShader(light, envMap.bitmapData, envMap.bitmapData, 0x333333, bumpMap.bitmapData);
      }
      // create new shaded material by combining the bitmap material with shader
      var shadedMaterial:ShadedMaterial = new ShadedMaterial(bitmapMaterial, shader);
      shadedMaterial.interactive = true;
     
      return shadedMaterial;
    }
   
    override protected function onRenderTick(event:Event=null):void {
      // rotate the sphere
      sphere.yaw(-1);
     
      // If the mouse button has been clicked then update the camera position     
      if (doRotation) {
       
        // convert the change in mouse position into a change in camera angle
        var dPitch:Number = (mouseY - lastMouseY) / 2;
        var dYaw:Number = (mouseX - lastMouseX) / 2;
       
        // update the camera angles
        cameraPitch -= dPitch;
        cameraYaw -= dYaw;
        // limit the pitch of the camera
        if (cameraPitch <= 0) {
          cameraPitch = 0.1;
        } else if (cameraPitch >= 180) {
          cameraPitch = 179.9;
        }
     
        // reset the last mouse position
        lastMouseX = mouseX;
        lastMouseY = mouseY;
       
        // reposition the camera
        camera.orbit(cameraPitch, cameraYaw);
      }
     
      // call the renderer
      super.onRenderTick(event);
    }
    // called when mouse down on stage
    private function onMouseDown(event:MouseEvent):void {
      doRotation = true;
      lastMouseX = event.stageX;
      lastMouseY = event.stageY;
    }
    // called when mouse up on stage
    private function onMouseUp(event:MouseEvent):void {
      doRotation = false;
    }
   
    // called when mouse down on a sphere
    private function onMouseDownOnObject(event:InteractiveScene3DEvent):void {
      var object:DisplayObject3D = event.displayObject3D;
     
      // calculate index of next shader
      shaderIndex++;
      if (shaderIndex == shaders.length) {
        shaderIndex = 0;
      }
     
      // dynamically modify the material of the object and update text
      object.material = getShadedBitmapMaterial(bitmapMaterial, shaders[shaderIndex]);
      shaderText.text = shaders[shaderIndex];
      shaderText.setTextFormat(textFormat);
    }
  }
}
Click on the image below to see this in action. Clicking on the sphere will change the material - in this case change the shader associated with the textured material - and moving the mouse while clicking anywhere in the window will rotate the camera around the sphere.

Compared to the previous posts the construction, initialisation of 3D and scene renderer remain virually the same so I won't go into details here. You'll see that, as with the texture map, the bump map and environment map are embedded in the flash animation and come from image files. These are then converted into Bitmap objects.
    [Embed(source="/../assets/pv3d.png")] private var Pv3dBitmapImage:Class;
    [Embed(source="/../assets/randomBump.png")] private var BumpImage:Class;
    [Embed(source="/../assets/mountains.png")] private var EnvImage:Class;
    private var pv3dBitmap:Bitmap = new Pv3dBitmapImage();
    private var bumpMap:Bitmap = new BumpImage();
    private var envMap:Bitmap = new EnvImage();
For sake of completeness, the images I use are below. The pv3d symbol comes directly from the papervision3d developer site, the bump map I made myself just by adding random noise to a blank image and blurring it (thanks to Gimp) and the environment image comes from my neighbourhood Alps.
Texture map image:

Bump map:

Environment map:

Remark: Actually I had to invert the environment map image for this demo to have it appear the right way up when rendered. If anyone can tell me why I'd be happy to know...
The main modification to the code compared to the previous post occurs in the createScene method. Firstly a simple Text object is created to display the current shader type - nothing really interesting here!
      // create text and format to display current shader type
      textFormat = new TextFormat();
      textFormat.size = 20;
      textFormat.font = "Arial";
     
      shaderText = new TextField();
      shaderText.x = 50;
      shaderText.y = 50;
      shaderText.textColor = 0xFFFFFF;
      shaderText.text = "flat";
      shaderText.setTextFormat(textFormat);
      shaderText.autoSize = TextFieldAutoSize.LEFT;
We then put the light back in that we removed in the last article - each shader is associated with a single light source.
      // Specify a point light source and its location
      light = new PointLight3D(true);
      light.x = 500;
      light.y = 500;
      light.z = -200;
As with Part 6, we create a BitmapMaterial using the texture map data. As before I'm using normal rather than accurate perspective (the second parameter of the constructor set to false) to improve the performance. I am however choosing to smooth the material which produces a much nicer looking texture map. This is of course at the cost of performance...
      // create bitmap material with smoothing
      bitmapMaterial = new BitmapMaterial(pv3dBitmap.bitmapData, false);
      bitmapMaterial.smooth = true;
The sphere is then created using a call to getShadedBitmapMaterial to enhance the bitmap material with a shader - to start off with, just a simple flat shader - and an event listener added to allow us to change the material.
      // create sphere
      sphere = new Sphere(getShadedBitmapMaterial(bitmapMaterial, "flat"), 150, 20, 20);
      // Add a listener to the spheres to listen to InteractiveScene3DEvent events
      sphere.addEventListener(InteractiveScene3DEvent.OBJECT_CLICK, onMouseDownOnObject);
So how do we add a shader to a BitmapMaterial? The different types of shaders used in this demo are created in the getShadedBitmapMaterial method
    private function getShadedBitmapMaterial(bitmapMaterial:BitmapMaterial, shaderType:String):ShadedMaterial {
      var shader:Shader;
     
      if (shaderType == "flat") {
        // create new flat shader
        shader = new FlatShader(light, 0xFFFFFF, 0x333333);
      } else if (shaderType == "cell") {
        // create new cell shader with 5 colour levels
        shader = new CellShader(light, 0xFFFFFF, 0x333333, 5);
      } else if (shaderType == "gouraud") {
        // create new gouraud shader
        shader = new GouraudShader(light, 0xFFFFFF, 0x333333);
      } else if (shaderType == "phong") {
        // create new phong shader
        shader = new PhongShader(light, 0xFFFFFF, 0x333333, 50);
      } else if (shaderType == "phongBump") {
        // create new phong shader with bump map
        shader = new PhongShader(light, 0xFFFFFF, 0x333333, 50, bumpMap.bitmapData);
      } else if (shaderType == "env") {
        // create new environment map shader
        shader = new EnvMapShader(light, envMap.bitmapData, envMap.bitmapData, 0x333333);
      } else if (shaderType == "envBump") {
        // create new environment map shader with bump map
        shader = new EnvMapShader(light, envMap.bitmapData, envMap.bitmapData, 0x333333, bumpMap.bitmapData);
      }
      // create new shaded material by combining the bitmap material with shader
      var shadedMaterial:ShadedMaterial = new ShadedMaterial(bitmapMaterial, shader);
      shadedMaterial.interactive = true;
     
      return shadedMaterial;
    }
As you can see, the simple shaders ("flat", "gouraud", "cell" and "phong") are created exactly the same as for the ShadeMaterials we introduced in Part 4 using essentially a light colour and an ambient colour. The new types of shaders here come from using the bump map and environment map.
The PhongShader allows us to add bump map data. This is added very simply by using the bump map image data we loaded before. As you can see with the demo this changes a lot the appearance of the sphere - we really get the impression that it has a bumpy surface.
The EnvMapShader is also very simple to use. It takes a light source as for all the other shaders then two texture maps for the environment - a front and back. Here for simplicity I just used the same image for both. It then takes an ambient colour for when it is not facing the light source. To add a bump map we simple add as well the bitmap data of the bump map image.
Finally, what we create is a ShadedMaterial: this simply takes the material texture map and the shader and combines the two.
The only other major difference from previous articles is the dynamic changing of object materials. This is performed when a mouse click event is detected on the sphere.
    // called when mouse down on a sphere
    private function onMouseDownOnObject(event:InteractiveScene3DEvent):void {
      var object:DisplayObject3D = event.displayObject3D;
     
      // calculate index of next shader
      shaderIndex++;
      if (shaderIndex == shaders.length) {
        shaderIndex = 0;
      }
     
      // dynamically modify the material of the object and update text
      object.material = getShadedBitmapMaterial(bitmapMaterial, shaders[shaderIndex]);
      shaderText.text = shaders[shaderIndex];
      shaderText.setTextFormat(textFormat);
    }
Dynamically changing the material of an object is a new feature in Papervision3D and is very simple to implement: simply change the value of the material member of the object! In the above method we just cycle through the array of shader types and update the text associated... almost too easy!
And that's all there is too it! I've not really gone into the mechanics behind bump mapping and environment mapping because I'm sure you can find explanations much better that I could give elsewhere on the web. But hopefully this has given some insight into how just a few simple lines can totally change the appearance of a rendered object. Comments and suggestions, as always, are very welcome!
Next article:
- First steps in Papervision3D : Part 8 - Movie materials
Show animated and interactive Flash video streams and movies on surfaces of 3D objects.
Sunday, August 24, 2008
First steps in Papervision3D : Part 6 - Texture mapping
Blog has moved - please update your bookmarks!
You're being redirected to its new location at : http://blog.tartiflop.com
Previous articles summary :
- First steps in Papervision3D : Part 1
Creation of a new Papervision3D project within eclipse or Flex Builder 3 and a simple example of a 3D scene illustrating some basic Papervision3D classes. - First steps in Papervision3D : Part 2
Illustration of use of new Papervision3D v2.0 class BasicView that encapsulates essential elements to rapidly start developing new 3D scenes. - First steps in Papervision3D : Part 3 - animation
Adding some basic animation to the scene by modifying 3D object parameters when entering a frame. - First steps in Papervision3D : Part 4 - lighting and shading
A point light source is added to the scene. Fours materials with different characteristics are added to spheres to illustrate how Papervision3D performs shading. - First steps in Papervision3D : Part 5 - scene interaction
Listen to mouse events to interact with the scene and with individual rendered objects.
To start with some useful links that provide a good overview of texture mapping with Papervision3D :
- dispatchEvent() : Papervision3D Part2 : Features
Provides a good introduction to some of the essentials of texture mapping (as well as other features in Papervision3D) - insideRIA : Textures (WireFrame, Bitmap, MovieAsset, Video, etc.) PaperVision3D
Lots of useful information on different material types including some essential texture mapping details.
What I'd like to do in this article is to start off very simply and show how to add a bitmap texture to cubes and spheres, with and without tiling. So, no shading for the moment - that'll be covered in the next article - and lets just see how to cover a shape with a bitmap material.
The code below is based on code from the previous article. However, I'm removing the light source for the time being, adding cubes as well as spheres and, of course, changing the materials to use bitmap data.
package {
 
  import caurina.transitions.Tweener;
 
  import flash.display.Bitmap;
  import flash.display.StageAlign;
  import flash.display.StageScaleMode;
  import flash.events.Event;
  import flash.events.MouseEvent;
 
  import org.papervision3d.events.InteractiveScene3DEvent;
  import org.papervision3d.materials.BitmapMaterial;
  import org.papervision3d.materials.utils.MaterialsList;
  import org.papervision3d.objects.DisplayObject3D;
  import org.papervision3d.objects.primitives.Cube;
  import org.papervision3d.objects.primitives.Sphere;
  import org.papervision3d.view.BasicView;
  public class Example006 extends BasicView {
 
    [Embed(source="/../assets/pv3d.png")] private var MyTextureImage:Class;
    private static const ORBITAL_RADIUS:Number = 200;
 
    private var bitmap:Bitmap = new MyTextureImage();
    private var cube1:Cube;
    private var cube2:Cube;
    private var sphere1:Sphere;
    private var sphere2:Sphere;
    private var objectGroup:DisplayObject3D;
   
    private var doRotation:Boolean = false;
    private var lastMouseX:int;
    private var lastMouseY:int;
    private var cameraPitch:Number = 60;
    private var cameraYaw:Number = -60;
   
    public function Example006() {
      super(0, 0, true, true);
     
      // set up the stage
      stage.align = StageAlign.TOP_LEFT;
      stage.scaleMode = StageScaleMode.NO_SCALE;
      // Initialise Papervision3D
      init3D();
     
      // Create the 3D objects
      createScene();
      // Listen to mouse up and down events on the stage
      stage.addEventListener(MouseEvent.MOUSE_DOWN, onMouseDown);
      stage.addEventListener(MouseEvent.MOUSE_UP, onMouseUp);
      // Start rendering the scene
      startRendering();
    }
   
    private function init3D():void {
      // position the camera
      camera.z = -500;
      camera.orbit(60, -60);
    }
    private function createScene():void {
      // create interactive bitmap material
      var bitmapMaterial:BitmapMaterial = new BitmapMaterial(bitmap.bitmapData, false);
      bitmapMaterial.interactive = true;
      // create an interactive tiled bitmap material (bitmap tiled as 2 x 2)
      var tiledBitmapMaterial:BitmapMaterial = new BitmapMaterial(bitmap.bitmapData, false);
      tiledBitmapMaterial.interactive = true;
      tiledBitmapMaterial.tiled = true;
      tiledBitmapMaterial.maxU = 2;
      tiledBitmapMaterial.maxV = 2;
     
      // create cube with simple bitmap material
      cube1 = new Cube(getBitmapMaterials(bitmapMaterial), 100, 100, 100);
      cube1.x = ORBITAL_RADIUS;
      // create cube with tiled bitmap material
      cube2 = new Cube(getBitmapMaterials(tiledBitmapMaterial), 100, 100, 100);
      cube2.x = -ORBITAL_RADIUS;
 
      // create sphere with simple bitmap material
      sphere1 = new Sphere(bitmapMaterial, 50, 10, 10);
      sphere1.z = ORBITAL_RADIUS;
      // create sphere with tiled bitmap material
      sphere2 = new Sphere(tiledBitmapMaterial, 50, 10, 10);
      sphere2.z = -ORBITAL_RADIUS;
      // Create a 3D object to group the spheres
      objectGroup = new DisplayObject3D();
      objectGroup.addChild(cube1);
      objectGroup.addChild(cube2);
      objectGroup.addChild(sphere1);
      objectGroup.addChild(sphere2);
      // Add a listener to each of the spheres to listen to InteractiveScene3DEvent events
      cube1.addEventListener(InteractiveScene3DEvent.OBJECT_CLICK, onMouseDownOnObject);
      cube2.addEventListener(InteractiveScene3DEvent.OBJECT_CLICK, onMouseDownOnObject);
      sphere1.addEventListener(InteractiveScene3DEvent.OBJECT_CLICK, onMouseDownOnObject);
      sphere2.addEventListener(InteractiveScene3DEvent.OBJECT_CLICK, onMouseDownOnObject);
      // Add the light and spheres to the scene
      scene.addChild(objectGroup);
    }
   
    private function getBitmapMaterials(bitmapMaterial:BitmapMaterial):MaterialsList {
      // create list of materials for all faces of the cube,
      // all with the same bitmap material
      var materials:MaterialsList = new MaterialsList();
      materials.addMaterial(bitmapMaterial, "all");
     
      return materials;
    }
   
    override protected function onRenderTick(event:Event=null):void {
      // rotate the objects
      cube1.yaw(-3);
      cube2.yaw(-3);
      sphere1.yaw(-3);
      sphere2.yaw(-3);
     
      // rotate the group of objects
      objectGroup.yaw(1);
      // If the mouse button has been clicked then update the camera position     
      if (doRotation) {
       
        // convert the change in mouse position into a change in camera angle
        var dPitch:Number = (mouseY - lastMouseY) / 2;
        var dYaw:Number = (mouseX - lastMouseX) / 2;
       
        // update the camera angles
        cameraPitch -= dPitch;
        cameraYaw -= dYaw;
        // limit the pitch of the camera
        if (cameraPitch <= 0) {
          cameraPitch = 0.1;
        } else if (cameraPitch >= 180) {
          cameraPitch = 179.9;
        }
     
        // reset the last mouse position
        lastMouseX = mouseX;
        lastMouseY = mouseY;
       
        // reposition the camera
        camera.orbit(cameraPitch, cameraYaw);
      }
     
      // call the renderer
      super.onRenderTick(event);
    }
    // called when mouse down on stage
    private function onMouseDown(event:MouseEvent):void {
      doRotation = true;
      lastMouseX = event.stageX;
      lastMouseY = event.stageY;
    }
    // called when mouse up on stage
    private function onMouseUp(event:MouseEvent):void {
      doRotation = false;
    }
   
    // called when mouse down on a sphere
    private function onMouseDownOnObject(event:InteractiveScene3DEvent):void {
      var object:DisplayObject3D = event.displayObject3D;
      Tweener.addTween(object, {y:200, time:1, transition:"easeOutSine", onComplete:function():void {goBack(object);} });
    }
   
    // called when a tween created in onMouseDownOnObject has terminated
    private function goBack(object:DisplayObject3D):void {
      Tweener.addTween(object, {y:0, time:2, transition:"easeOutBounce"});
    }
  }
}
This produces the following Flash animation (click on image below) where we see the four spinning objects rotating about the origin. As with Part 5, you can interact with the scene by clicking and moving the mouse (to rotate the scene) and by clicking on the objects to make them jump. What we now see though are the objects covered with a material using bitmap data.

The first thing to note with this code is the embedding of an image in the compiled Flash file :
    [Embed(source="/../assets/pv3d.png")] private var MyTextureImage:Class;
This is then converted into a Bitmap as follows :
    private var bitmap:Bitmap = new MyTextureImage();
The embed meta-tag allows assets to be embedded in to the swf file. Rather than going into the details of embedding here, you can find some useful links at assertTrue and with the Flex livedocs. You'll note that the source is "../assets" : this is because I have my assets folder at the same level as the src folder - when the Actionscript is compiled, the root folder is the src folder. If you want to compile the above source then simply create an assets folder, put any image file in it and change the above line to point to your own image.
The constructor, intialisation of the the 3D and the event handlers are all identical to the previous article in this series so I won't go into details here. The main changes are in the createScene method.
Rather than coat our objects with shaded materials, we are going to create BitmapMaterials. A BitmapMaterial simply takes bitmap data (which comes from our embedded image) :
      // create interactive bitmap material
      var bitmapMaterial:BitmapMaterial = new BitmapMaterial(bitmap.bitmapData, false);
      bitmapMaterial.interactive = true;
The BitmapMaterial constructor takes the bitmap data from the image we created earlier and a boolean value to indicate whether we want normal or accurate rendering to occur. Texture mapping requires a lot of CPU so Papervision3D gives the possibility to speed up the rendering at the cost of less accurate perspective. You can see an example of the distortion that occurs with this Papervision3D demo. I've selected false here to have quick rendering.
To create a tiled bitmap material, we create another BitmapMaterial and modify certain parameters :
      // create an interactive tiled bitmap material (bitmap tiled as 2 x 2)
      var tiledBitmapMaterial:BitmapMaterial = new BitmapMaterial(bitmap.bitmapData, false);
      tiledBitmapMaterial.interactive = true;
      tiledBitmapMaterial.tiled = true;
      tiledBitmapMaterial.maxU = 2;
      tiledBitmapMaterial.maxV = 2;
Here we set tiled to true to allow tiling to occur. maxU and maxV are then modified to indicate how many tiles we want to cover the material. I've specified 2 by 2 tiling.
These materials are then simply added to the cubes and spheres exactly as we did with the shaded materials before.
      // create cube with simple bitmap material
      cube1 = new Cube(getBitmapMaterials(bitmapMaterial), 100, 100, 100);
      cube1.x = ORBITAL_RADIUS;
      // create cube with tiled bitmap material
      cube2 = new Cube(getBitmapMaterials(tiledBitmapMaterial), 100, 100, 100);
      cube2.x = -ORBITAL_RADIUS;
 
      // create sphere with simple bitmap material
      sphere1 = new Sphere(bitmapMaterial, 50, 10, 10);
      sphere1.z = ORBITAL_RADIUS;
      // create sphere with tiled bitmap material
      sphere2 = new Sphere(tiledBitmapMaterial, 50, 10, 10);
      sphere2.z = -ORBITAL_RADIUS;
Actually, this is the first time that I've put a cube into one of these examples and I should note that rather than taking a simple material object, they take a MaterialsList that allows us to specify a different material for each face. For this demo I want all the faces to be the same so the material is added to the list using the "all" tag.
    private function getBitmapMaterials(bitmapMaterial:BitmapMaterial):MaterialsList {
      // create list of materials for all faces of the cube,
      // all with the same bitmap material
      var materials:MaterialsList = new MaterialsList();
      materials.addMaterial(bitmapMaterial, "all");
     
      return materials;
    }
You can add different materials using "front", "back", "top", "bottom", "left" and "right" tags.
So, at its very simplest, that's all there is to do to create texture mapped objects. As always I'd recommend taking a look at the Papervision3D sources as there's a lot of stuff not covered here. For example you'll see that there are a number of different Bitmap materials in the org.papervision3d.materials package, for example the BitmapFileMaterial that allows you to load a bitmap at run time from a URL.
In the next article I'll introduce (or rather put back) shading on top of the texture map. To do this we need in effect a kind of composite material : one that has the texture map and another that has the shaded colour... anyway, I'll leave that for next time! I hope at least that for the time being this gives some help in getting started!
Next article:
- First steps in Papervision3D : Part 7 - Texture mapping with lighting, bump mapping and environment mapping
Advanced materials using specific shaders to enhance texture mapped materials with bump mapping and environment mapping.
Labels:
actionscript,
Papervision3D,
texture mapping
Wednesday, August 20, 2008
Musical Interlude : Massive Attack
Blog has moved - please update your bookmarks!
You're being redirected to its new location at : http://blog.tartiflop.com
Labels:
music
Subscribe to:
Posts (Atom)