Author: Max Pellizzaro
Date: Jannuary 5th 2008
Update: March 15th 2008
version: 3.0.2
In the previous version of this tutorial (Sandy 3.0.1) we have learned to use two important methods:
Now with version 3.0.2 you these methods has been replaced with two attributes of Shape3DEvent
The Document class must be changed to Example020.as The name of the class in the .as file and the name of the constructor now is: Example020.
The new updated version can be found here:
package {
import flash.display.BitmapData;
import flash.display.Sprite;
import flash.events.Event;
import flash.events.KeyboardEvent;
import flash.events.MouseEvent;
import flash.geom.Point;
import flash.geom.Rectangle;
import flash.geom.Matrix;
import flash.ui.Keyboard;
import flash.ui.*;
import flash.text.*;
import sandy.core.World3D;
import sandy.core.data.Polygon;
import sandy.core.data.UVCoord;
import sandy.core.data.Vector;
import sandy.core.data.Vertex;
import sandy.core.scenegraph.Camera3D;
import sandy.core.scenegraph.Group;
import sandy.core.scenegraph.Shape3D;
import sandy.core.scenegraph.TransformGroup;
import sandy.events.*;
import sandy.materials.*;
import sandy.materials.attributes.*;
import sandy.math.VectorMath;
import sandy.primitive.Line3D;
import sandy.primitive.Sphere;
import sandy.primitive.Box;
import sandy.primitive.Torus;
import sandy.util.NumberUtil;
import sandy.core.Scene3D;
public class Example020 extends Sprite {
private var myBox:Box;
private var mySphere:Sphere;
private var myTorus:Torus;
private var scene:Scene3D;
private var lCamera:Camera3D;
private var textureTwo:BitmapData = new BitmapData( 200, 200, false, 0x0000FF );
private var textureThree:BitmapData = new BitmapData( 200, 200, false, 0x00CC33 );
private var lTg:TransformGroup = new TransformGroup("rotation");
private var l_oPoly:Polygon;
public function Example020():void {
lCamera = new Camera3D( 650, 500 );
lCamera.z = -450;
lCamera.y = 90;
lCamera.lookAt( 0, 0, 0 );
var root:Group = createScene3D();
scene = new Scene3D( "scene", this, lCamera, root );
stage.addEventListener(KeyboardEvent.KEY_DOWN, keyPressedHandler);
addEventListener( Event.ENTER_FRAME, enterFrameHandler );
}
private function createScene3D():Group {
var lG:Group = new Group();
myBox = new Box( "theBox", 100, 100, 100, "tri", 2 );
myBox.x = -170;
myBox.y = 70;
myBox.useSingleContainer = false;
myBox.enableBackFaceCulling = true;
myBox.enableNearClipping = true;
var materialAttr:MaterialAttributes = new MaterialAttributes( new LightAttributes( true, 0.1) );
var material:Material = new ColorMaterial( 0xFFCC33, 1, materialAttr );
material.lightingEnable = true;
myBox.appearance = new Appearance( material );
myBox.enableEvents = true;
myBox.addEventListener( MouseEvent.CLICK, onClickOne );
mySphere = new Sphere( "theSphere", 60, 10, 10 );;
mySphere.x = 170;
mySphere.y = -70;
mySphere.useSingleContainer = false;
mySphere.enableBackFaceCulling = true;
mySphere.enableNearClipping = true;
var l_oAttr:MaterialAttributes = new MaterialAttributes( new LineAttributes() );
mySphere.appearance = new Appearance( new BitmapMaterial( textureTwo, l_oAttr ) );
mySphere.enableEvents = true;
mySphere.addEventListener( MouseEvent.CLICK, onClickTwo );
myTorus = new Torus( "theTorus", 70, 20 );
myTorus.useSingleContainer = false;
myTorus.enableBackFaceCulling = true;
myTorus.enableNearClipping = true;
myTorus.appearance = new Appearance( new BitmapMaterial( textureThree, l_oAttr ) );
myTorus.enableEvents = true;
myTorus.addEventListener( MouseEvent.CLICK, onClickThree );
lTg.addChild( mySphere );
lTg.addChild( myBox );
lTg.addChild( myTorus );
lG.addChild( lTg );
return lG;
}
private function enterFrameHandler( event : Event ) : void {
scene.render();
}
private function onClickOne( p_eEvent:Shape3DEvent ):void {
var v:Vector = p_eEvent.point;
var top:Vector = p_eEvent.polygon.normal.getVector().clone();
top.scale( 20 );
top.add( v );
var m_oLine3D = new Line3D("normal", v, top );
lTg.addChild( m_oLine3D );
}
private function onClickTwo( p_eEvent:Shape3DEvent ):void {
l_oPoly = p_eEvent.polygon;
var l_oPoint:Point = new Point( scene.container.mouseX, scene.container.mouseY );
var l_oIntersectionUV:UVCoord = p_eEvent.uv;
var l_oMaterial:BitmapMaterial = (l_oPoly.visible ?
l_oPoly.appearance.frontMaterial :
l_oPoly.appearance.backMaterial
) as BitmapMaterial;
if( l_oMaterial == null ) {
trace("This material must be a MovieMaterial");
} else {
var l_oRealTexturePosition:UVCoord = new UVCoord(
l_oIntersectionUV.u * l_oMaterial.texture.width,
l_oIntersectionUV.v * l_oMaterial.texture.height
);
var l_oTexture:BitmapData = l_oMaterial.texture;
l_oTexture.fillRect(
new Rectangle(
l_oRealTexturePosition.u-2,
l_oRealTexturePosition.v-2,
4, 4
),
0xAA999FF999FF00
);
l_oMaterial.texture = l_oTexture;
}
}
private function onClickThree( p_eEvent:Shape3DEvent ):void {
l_oPoly = p_eEvent.polygon;
var l_oPoint:Point = new Point( scene.container.mouseX, scene.container.mouseY );
var v:Vector = p_eEvent.point;
var top:Vector = l_oPoly.normal.getVector().clone();
top.scale( 20 );
top.add( v );
var m_oLine3D = new Line3D("normal", v, top );
lTg.addChild( m_oLine3D );
var l_oIntersectionUV:UVCoord = p_eEvent.uv;
var l_oMaterial:BitmapMaterial = (l_oPoly.visible ?
l_oPoly.appearance.frontMaterial :
l_oPoly.appearance.backMaterial
) as BitmapMaterial;
if( l_oMaterial == null ) {
trace("This material must be a MovieMaterial");
} else {
var l_oRealTexturePosition:UVCoord = new UVCoord(
l_oIntersectionUV.u * l_oMaterial.texture.width,
l_oIntersectionUV.v * l_oMaterial.texture.height
);
var l_oTexture:BitmapData = l_oMaterial.texture;
l_oTexture.fillRect(
new Rectangle(
l_oRealTexturePosition.u-2,
l_oRealTexturePosition.v-2,
4, 4
),
0x990000
);
l_oMaterial.texture = l_oTexture;
}
}
private function keyPressedHandler(event:flash.events.KeyboardEvent):void {
switch(event.keyCode) {
case Keyboard.UP:
lTg.tilt +=3;
break;
case Keyboard.DOWN:
lTg.tilt -=3;
break;
case Keyboard.LEFT:
lTg.rotateY -=3;
break;
case Keyboard.RIGHT:
lTg.rotateY +=3;
break;
case Keyboard.PAGE_DOWN:
lCamera.z -= 5;
break;
case Keyboard.PAGE_UP:
lCamera.z += 5;
break;
}
}
}
}
For our tutorial we will draw three primitives: a cube, a sphere and a torus. I will not go through how to build them and how to add them on the Scene (you should be able to do it by now). What it is important to notice is that on each primitive we will enableEvents and we will add a different listener to each primitive. So let’s examine these three different listeners.
private function onClickOne( p_eEvent:Shape3DEvent ):void
With the first listener we will allow the user to draw a 3DLine from the point the user clicks with the mouse. Let's find the 3D coordinates of the point we are clicking
var v:Vector = p_eEvent.point;
Now we want to draw a Line that is perpendicular to the face we are clicking on. Each face (polygon) has a normal vector, as the name says it is perpendicular to the face we are clicking. We get this vector with this command:
var top:Vector = p_eEvent.polygon.normal.getVector().clone();
If we add to the previous vector (the one we have retrieve the point we have clicked), this vector, we find the position of a new point that is perpendicular to the previous position. We now have two points and then we can draw a line:
var m_oLine3D = new Line3D("normal", v, top );
Let’s now examine the second call back function:
private function onClickTwo( p_eEvent:Shape3DEvent ):void
This function, that is applied on the sphere, is responsible to draw a little rectangular on the sphere. What we are actually doing is to draw a square using the drawing API on a bitmapMaterial object. This is why we cannot do this operation on the cube that use a ColorMaterial, be we can do only on material that is a BitmapMaterial (like the sphere or the torus).
So first of all we will get the UV Coordinates of the BitmapMaterial that we have previously applied on the sphere as its appearance.
var l_oIntersectionUV:UVCoord = p_eEvent.uv;
Now to get the real texture position, we need to multiply these coordinates with the height and the width of the texture:
var l_oRealTexturePosition:UVCoord = new UVCoord( l_oIntersectionUV.u * l_oMaterial.texture.width, l_oIntersectionUV.v * l_oMaterial.texture.height );
It's time to draw a little square and replace the bitmap texture with the one that we have just modified.
var l_oTexture:BitmapData = l_oMaterial.texture; l_oTexture.fillRect( new Rectangle( l_oRealTexturePosition.u-2, l_oRealTexturePosition.v-2, 4, 4 ), 0x999FF00 ); l_oMaterial.texture = l_oTexture;
The third callback function, the one used for the torus, just combine the two previous callback.
And now let’s see the result !
Click on the primitives to draw 3D Lines, and use the arrow keys to rotate. PAGE_UP and DOWN to zoom.