CAT | Code
25
Creating a Skybox with Papervision and Terragen in Flex
0 Comments | Posted by Chris Jacobsen in Code
A skybox is a cube that is textured with a distant landscape on its 6 sides which is viewed by a camera at its center. This creates the illusion that you are inside a vast area without having to render too many polygons.
Our first step will be creating the textures. I will be using Terragen Classic v0.9 to render the landscape, you can find it here. Once you’ve downloaded, installed, and started up Terragen you’ll be presented with a window like this:

In the ‘Rendering Control’ window match the settings to the image below, starting from the red square at the top.

Now click on the ‘Image Size…’ and then the ‘Camera Settings…’ buttons and match them to the following images. If you want more detailed images you could set the width and height in ‘Render Settings’ to 1024.


Now open the ‘Cloudscape’ window by clicking the icon to the left with the clouds on it.
![]()
Change the ‘Sky size’ value to 8192 and x out.

Lets see what it looks like! Click the ‘Render Preview’ button in the ‘Rendering Control’ window, you should see something like this:

So far so good, lets add some terrain and water. In the ‘Landscape’ window press the ‘Generate Terrain…’ button.

Now press the ‘Generate Terrain’ button in the new window and close it.

Back in the ‘Landscape’ window press the ‘Modify…’ button.

In the new window set the height range from -16 to 16, click the ‘Set Height Range’ button, and close the window.

Lets preview our image again, you should get something like this:

Now we are ready to render our 6 textures for the skybox. For the front texture we will set ‘Camera Orientation’ to 0, 0, 0 (head, pitch, bank) and press the ‘Render Image’ button.

Once it has finished rendering click the ‘Save’ button and name it ‘front.bmp’.

Repeat the last few steps for the top, bottom, left, right, and back textures using the camera orientation values from the image below.

You should now have 6 bmp files, you will need to convert them to JPEGs in order for them to work with Papervision. Lets take a look at the ActionScript, I’m using Flex Builder 3 with Papervision 2. Here is the SkyboxComponent class:
import flash.events.Event;
import mx.core.UIComponent;
import mx.events.ResizeEvent;
import org.papervision3d.cameras.Camera3D;
import org.papervision3d.materials.BitmapFileMaterial;
import org.papervision3d.objects.primitives.Plane;
import org.papervision3d.render.BasicRenderEngine;
import org.papervision3d.scenes.Scene3D;
import org.papervision3d.view.Viewport3D;
public class SkyboxComponent extends UIComponent{
public var boxWidth:int=512;
public var boxHeight:int=512;
public var front:String="images/front.jpg";
public var back:String="images/back.jpg";
public var left:String="images/left.jpg";
public var right:String="images/right.jpg";
public var top:String="images/top.jpg";
public var bottom:String="images/bottom.jpg";
private var viewport:Viewport3D;
private var scene:Scene3D;
private var camera:Camera3D;
private var renderer:BasicRenderEngine;
public function SkyboxComponent(){
super();
//init on 'added_to_stage' event so width and height are set properly
this.addEventListener(Event.ADDED_TO_STAGE,init);
//resize viewport when component resizes
this.addEventListener(ResizeEvent.RESIZE,onResize);
}
private function init(event:Event):void{
//setup papervision
viewport=new Viewport3D(this.width,this.height);
renderer=new BasicRenderEngine();
scene=new Scene3D();
camera=new Camera3D(60,10,Number.POSITIVE_INFINITY,true);
camera.zoom=10;
camera.focus=Math.round(((boxWidth+boxHeight)/2)/20);
camera.x=0;
camera.y=0;
camera.z=0;
this.addChild(viewport);
//here we will place the images, by using planes instead of a cube we can ensure correct image orientation
//front plane
var mat:BitmapFileMaterial=new BitmapFileMaterial(front);
var plane:Plane=new Plane(mat,boxWidth,boxHeight,8,8);
plane.z=boxWidth/2;
scene.addChild(plane);
//back plane
mat=new BitmapFileMaterial(back);
plane=new Plane(mat,boxWidth,boxHeight,8,8);
plane.z=-(boxWidth/2);
plane.rotationY=180;
scene.addChild(plane);
//left plane
mat=new BitmapFileMaterial(left);
plane=new Plane(mat,boxWidth,boxHeight,8,8);
plane.x=-(boxWidth/2);
plane.rotationY=-90;
scene.addChild(plane);
//right plane
mat=new BitmapFileMaterial(right);
plane=new Plane(mat,boxWidth,boxHeight,8,8);
plane.x=boxWidth/2;
plane.rotationY=90;
scene.addChild(plane);
//top plane
mat=new BitmapFileMaterial(top);
plane=new Plane(mat,boxWidth,boxHeight,8,8);
plane.y=boxHeight/2;
plane.rotationX=-90;
scene.addChild(plane);
//bottom plane
mat=new BitmapFileMaterial(bottom);
plane=new Plane(mat,boxWidth,boxHeight,8,8);
plane.y=-(boxHeight/2);
plane.rotationX=90;
scene.addChild(plane);
//render on 'enter_frame' event
this.addEventListener(Event.ENTER_FRAME,onEnterFrame);
}
//the camera will always stay in the center, only its rotation should be changed
public function setCameraRotation(x:Number,y:Number,z:Number):void{
camera.rotationX=x;
camera.rotationY=y;
camera.rotationZ=z;
}
private function onEnterFrame(event:Event):void{
//render
renderer.renderScene(scene,camera,viewport);
}
private function onResize(event:ResizeEvent):void{
if(viewport!=null){
//resize viewport
viewport.viewportWidth=this.width;
viewport.viewportHeight=this.height
}
}
}
}
Here is the class that rotates a camera around some cubes. If you were making a game or application this would contain the 3d objects you are displaying. The reason I separated this from the SkyboxComponent class is because you may want to clip distant polygons by setting the cameras far variable for better performance, by keeping them separated the skybox will never get clipped out.
import flash.events.Event;
import mx.core.UIComponent;
import mx.events.ResizeEvent;
import org.papervision3d.cameras.Camera3D;
import org.papervision3d.materials.ColorMaterial;
import org.papervision3d.materials.WireframeMaterial;
import org.papervision3d.materials.special.CompositeMaterial;
import org.papervision3d.materials.utils.MaterialsList;
import org.papervision3d.objects.primitives.Cube;
import org.papervision3d.render.BasicRenderEngine;
import org.papervision3d.scenes.Scene3D;
import org.papervision3d.view.Viewport3D;
public class RotateAroundCubeComponent extends UIComponent{
private var viewport:Viewport3D;
private var scene:Scene3D;
private var camera:Camera3D;
private var renderer:BasicRenderEngine;
private var rotationX:Number=0;
private var rotationY:Number=0;
private var rotationZ:Number=0;
private var randomizeRotationFrame:int=0;
public function RotateAroundCubeComponent(){
super();
//init on 'added_to_stage' event so width and height are set properly
this.addEventListener(Event.ADDED_TO_STAGE,init);
//resize viewport when component resizes
this.addEventListener(ResizeEvent.RESIZE,onResize);
}
private function init(event:Event):void{
//setup papervision
viewport=new Viewport3D(this.width,this.height);
renderer=new BasicRenderEngine();
scene=new Scene3D();
camera=new Camera3D(60,10,2000,true);
camera.zoom=100;
camera.focus=10;
this.addChild(viewport);
//create cubes material
var cubeMaterial:CompositeMaterial=new CompositeMaterial();
var wireframeMaterial:WireframeMaterial=new WireframeMaterial(0x000000,1,1);
var colorMaterial:ColorMaterial=new ColorMaterial(0xFFFFFF);
cubeMaterial.addMaterial(wireframeMaterial);
cubeMaterial.addMaterial(colorMaterial);
var materialList:MaterialsList=new MaterialsList();
materialList.addMaterial(cubeMaterial,"all");
//create cubes
var cube:Cube=new Cube(materialList,20,20,20);
scene.addChild(cube);
cube=new Cube(materialList,20,20,20);
cube.x=-40;
scene.addChild(cube);
cube=new Cube(materialList,20,20,20);
cube.x=40;
scene.addChild(cube);
cube=new Cube(materialList,20,20,20);
cube.z=40;
scene.addChild(cube);
cube=new Cube(materialList,20,20,20);
cube.z=-40;
scene.addChild(cube);
cube=new Cube(materialList,20,20,20);
cube.y=40;
scene.addChild(cube);
cube=new Cube(materialList,20,20,20);
cube.y=-40;
scene.addChild(cube);
//render on 'enter_frame' event
this.addEventListener(Event.ENTER_FRAME,onEnterFrame);
}
private function onEnterFrame(event:Event):void{
//randomize rotation value
if(randomizeRotationFrame%200==0){
rotationX=(Math.random()/2)-.25;
rotationY=(Math.random())-.5;
rotationZ=(Math.random()/8)-.0625;
}
randomizeRotationFrame++;
//place camera
camera.x=0;
camera.y=0;
camera.z=0;
camera.rotationX+=rotationX;
camera.rotationY+=rotationY;
camera.rotationZ+=rotationZ;
camera.moveBackward(1600);
//render
renderer.renderScene(scene,camera,viewport);
}
public function getCameraRotation():Object{
if(camera!=null){
return {x:camera.rotationX,y:camera.rotationY,z:camera.rotationZ};
}
return null;
}
private function onResize(event:ResizeEvent):void{
if(viewport!=null){
//resize viewport
viewport.viewportWidth=this.width;
viewport.viewportHeight=this.height
}
}
}
}
And here is the applications MXML file. Notice that I’m updating the skybox cameras rotation based on the rotatingCube camera with an enterFrame event.
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" xmlns:local="*">
<mx:Script>
<![CDATA[
//update the skybox camera based on the rotatingCube camera
private function setSkyboxCameraRotation(event:Event):void{
var cameraRotation:Object=rotatingCube.getCameraRotation();
if(cameraRotation!=null){
skybox.setCameraRotation(cameraRotation.x,cameraRotation.y,cameraRotation.z);
}
}
]]>
</mx:Script>
<mx:HBox width="100%" height="100%" verticalAlign="middle" horizontalAlign="center">
<mx:Panel title="Skybox Example" paddingBottom="10" paddingLeft="10" paddingRight="10" paddingTop="10" width="90%" height="90%">
<mx:Canvas width="100%" height="100%">
<local:SkyboxComponent id="skybox" boxWidth="512" boxHeight="512" front="images/front.jpg" back="images/back.jpg" left="images/left.jpg" right="images/right.jpg" top="images/top.jpg" bottom="images/bottom.jpg" width="100%" height="100%"/>
<local:RotateAroundCubeComponent id="rotatingCube" enterFrame="setSkyboxCameraRotation(event);" width="100%" height="100%"/>
</mx:Canvas>
</mx:Panel>
</mx:HBox>
</mx:Application>
You can download the Flex project here.
It may come in handy at times to take advantage of all the UI components in Flex to manipulate a 3d scene in Papervision. In this example I add a Papervision component that renders a sphere to the applications MXML document. I’m using Papervision 2 and Flex Builder 3.
Here is the component class:
import flash.events.Event;
import mx.core.UIComponent;
import mx.events.ResizeEvent;
import org.papervision3d.cameras.Camera3D;
import org.papervision3d.materials.ColorMaterial;
import org.papervision3d.materials.WireframeMaterial;
import org.papervision3d.materials.special.CompositeMaterial;
import org.papervision3d.objects.primitives.Sphere;
import org.papervision3d.render.BasicRenderEngine;
import org.papervision3d.scenes.Scene3D;
import org.papervision3d.view.Viewport3D;
public class PV3DComponent extends UIComponent{
private var viewport:Viewport3D;
private var scene:Scene3D;
private var camera:Camera3D;
private var renderer:BasicRenderEngine;
private var sphere:Sphere;
private var rotationX:Number=.5;
private var rotationY:Number=-.5;
private var rotationZ:Number=.5;
public function PV3DComponent(){
super();
//init on 'added_to_stage' event so width and height are set properly
this.addEventListener(Event.ADDED_TO_STAGE,init);
//resize viewport when component resizes
this.addEventListener(ResizeEvent.RESIZE,onResize);
}
private function init(event:Event):void{
//setup papervision
viewport=new Viewport3D(this.width,this.height);
renderer=new BasicRenderEngine();
scene=new Scene3D();
camera=new Camera3D(90,10,2000,true);
camera.z=-100
camera.zoom=10;
camera.focus=100;
this.addChild(viewport);
//create sphere
var sphereMaterial:CompositeMaterial=new CompositeMaterial();
var wireframeMaterial:WireframeMaterial=new WireframeMaterial(0x000000,1,1);
var colorMaterial:ColorMaterial=new ColorMaterial(0xFFFFFF);
sphereMaterial.addMaterial(wireframeMaterial);
sphereMaterial.addMaterial(colorMaterial);
sphere=new Sphere(sphereMaterial,10,16,16);
scene.addChild(sphere);
//render on 'enter_frame' event
this.addEventListener(Event.ENTER_FRAME,onEnterFrame);
}
private function onEnterFrame(event:Event):void{
//rotate sphere
sphere.rotationX+=rotationX;
sphere.rotationY+=rotationY;
sphere.rotationZ+=rotationZ;
//render
renderer.renderScene(scene,camera,viewport);
}
private function onResize(event:ResizeEvent):void{
if(viewport!=null){
//resize viewport
viewport.viewportWidth=this.width;
viewport.viewportHeight=this.height
}
}
public function setSphereRotation(x:Number,y:Number,z:Number):void{
rotationX=x;
rotationY=y;
rotationZ=z;
}
}
}
And here is the applications MXML:
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" xmlns:classes="classes.*" xmlns:local="*">
<mx:Script>
<![CDATA[
import mx.events.SliderEvent;
private function onRotationSliderChange(event:SliderEvent):void{
pv3dComponent.setSphereRotation(xRotationSlider.value,yRotationSlider.value,zRotationSlider.value);
}
]]>
</mx:Script>
<mx:HBox width="100%" height="100%" verticalAlign="middle" horizontalAlign="center">
<mx:Panel title="Papervision3D Flex Component Example" paddingBottom="10" paddingLeft="10" paddingRight="10" paddingTop="10" width="90%" height="90%">
<mx:VBox width="100%" height="100%">
<local:PV3DComponent id="pv3dComponent" width="100%" height="100%"/>
<mx:HRule width="100%"/>
<mx:HBox width="100%">
<mx:Label text="X Rotation:" fontWeight="bold"/>
<mx:HSlider id="xRotationSlider" minimum="-5" maximum="5" value=".5" change="onRotationSliderChange(event);" width="100%"/>
</mx:HBox>
<mx:HBox width="100%">
<mx:Label text="Y Rotation:" fontWeight="bold"/>
<mx:HSlider id="yRotationSlider" minimum="-5" maximum="5" value="-.5" change="onRotationSliderChange(event);" width="100%"/>
</mx:HBox>
<mx:HBox width="100%">
<mx:Label text="Z Rotation:" fontWeight="bold"/>
<mx:HSlider id="zRotationSlider" minimum="-5" maximum="5" value=".5" change="onRotationSliderChange(event);" width="100%"/>
</mx:HBox>
</mx:VBox>
</mx:Panel>
</mx:HBox>
</mx:Application>
You can download the Flex project here.
