How it's made? Blender + Papervision3D = Flash car drive

How it's made? Blender + Papervision3D = Car drive in flash

Polonez Borewicz - click to drive it!

Welcome to flashsimulations.com!

Hello.

If you’ve been to this website before, you might be suprised, because it has changed significantly. I decided to run a blog under flashsimulations.com to archive my experiments involving various technologies.
If you’re looking for the car demo that used to be the landing page, read on. You’ll find it and even more.
This site’s name is flashsimulations.com, but it’s not going to cover only topics that have something to do with Adobe Flash Platform.

Let’s start blogging !

Long time ago, in April of 2008 while experimenting with 3d in flash, I created a car driving experiment that used to be under this address – you can find it here. In October 2008 my demo has been  selected as one of the 9 cool experiments on Papervision3D official blog, since then I got a lot of feedback and questions from enthusiasts of flash and 3d.

flashsimulations-pv3d-blog

Fig. 1: Polonez Borewicz showcased on PV3D blog, October 2008

I decided to write this tutorial after I got many e-mails from people asking how that car demo was made. Unfortunately, I didn’t have time to write everyone back, due to the complexity of this topic.

1981 FSO Polonez “Borewicz”, the car

Recently, I updated the original flash demo to work with the latest version of Papervision3D 2 and I slightly upgraded the car model itself. In this tutorial I will describe how you can export your model from Blender 2.49 to Flash and create a 3D car driving simulation using ActionScript 3 and Papervision3D engine.
I guess that most of you aren’t familliar with the car that I’ve modeled, it’s the  1981 FSO Polonez, nicknamed “Borewicz”.

Here’s the test drive of Polonez by Jeremy Clarkson.
YouTube Preview Image

Preparing the model in Blender

First of all, you need to export model from the 3d application of your choice – Papervision 2 accepts formats such as: .3ds, Collada, .MD2 and a few other.

I created Polonez model using Blender 2.49 and exported it to Collada 1.4 (.dae). I admit – the model isn’t pretty, but it was the first model I ever did. My priority was to keep the number of triangles as low as possible, to get better rendering speed in flash.
After many Papervision performance tests I managed to get a reasonable frame rate (at least 15) when the model had less than 2,000 triangles. Of course the performance of the application varies depending on the user’s processor  – I wanted this demo (in April 2008) to run smoothly even on low budget computers, so I tested it on Intel Core2Duo T 5250@1.50Ghz with 2GB RAM laptop. The performance is no longer such an issuse as it was back then, as Papervision has been improved and the performance increased.

Since we want to be able to control the car wheels to simulate steering and movement, we need to provide proper name to each element of the car. On the following picture you can see the names I gave to wheels and the ‘Transforn Properties’ window (‘N’ key shotcut), where you enter the name.

Naming car model elements in Blender

Fig. 2: Naming car model elements in Blender

When the model is finished, it’s time to export it to Collada 1.4. Select File -> Export -> Collada 1.4(.dae) , set the plugin options like on the picture below; click ‘Export & Close’ and that’s it.

Blender - Collada 1.4 export plugin options

Fig. 3: Blender - Collada 1.4 export plugin options

Importing the model to Papervision

When the model is ready, it’s time to do some coding. You’ll need the latest version of PV3D, get it here. The code of the car simulation  is pretty straightforward (I also commented it heavily), so I’m going to comment it just briefly.
Main class is based on PV3D’s BasicView and here’s the init function, where we setup everything.

1
2
3
4
5
6
7
8
private function init(e:Event = null):void  {
    removeEventListener(Event.ADDED_TO_STAGE, init);
    initCameras();
    initTextures();
    initModel();
    //...
    //pv3d statistics and other UI stuff
}

Initialize four cameras – their positions will be set in other function.

1
2
3
4
5
6
7
8
9
10
private function initCameras():void {
    cameras[CAMERA_DEFAULT] = new Camera3D();
    cameras[CAMERA_DEFAULT].y = 5;
    cameras[CAMERA_DEFAULT].z = - 60;
    cameras[CAMERA_DEFAULT].zoom = 50;cameras[CAMERA_CAR_HOOD] = new Camera3D();
    cameras[CAMERA_CAR_BEHIND] = new Camera3D();
    cameras[CAMERA_CAR_WHEEL] = new Camera3D();
    //set the camera to default
    this._camera = cameras[CAMERA_DEFAULT];
}

Init material from embedded texture .jpg.

1
2
3
4
5
private function initTextures():void {
    carModelTexture = new BitmapMaterial((new carTextureAsset as Bitmap).bitmapData);
    //looks better, but the framerate drops
    carModelTexture.smooth = true;
}

The loaded model will be parented to an empty container because we won’t apply any tranlations nor rotations to the model itself.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private function initModel():void {
    carModelParent = new DisplayObject3D();
    scene.addChild(carModelParent);
    //car texture
    var mats:MaterialsList = new MaterialsList();
    mats.addMaterial(carModelTexture, "all");carModel = new DAE();
    //initial position
    carModelParent.x = 50;
    carModelParent.y = 0;
    carModelParent.z = 1050;
    carModelParent.rotationY = -35;
    carModel.addEventListener(FileLoadEvent.LOAD_COMPLETE, onLoad);
    carModel.load(XML(new carModelAsset()), mats);//we add the Collada model to the parent DO3D
    carModelParent.addChild(carModel);
}

To control the wheels roll and steer angle we have to create a series of instances of DisplayObject3D class that will hold the reference to each part of the car – that’s the moment when the knowledge car elements’ names comes in handy. The same rule applies to our defined cameras and their targets.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private function onLoad(e:Event):void {
    carModel.scale = 100;
    viewport.getChildLayer(carModel).layerIndex = 1;
    //set references to car wheels;
    wheelFrontRight = carModel.getChildByName( "Wheel_Front_Right", true);
    wheelFrontLeft = carModel.getChildByName( "Wheel_Front_Left", true );
    wheelRearLeft = carModel.getChildByName( "Wheel_Rear_Left", true );
    wheelRearRight = carModel.getChildByName( "Wheel_Rear_Right", true );
    //set targets for all cameras
    cameraTargets[CAMERA_CAR_HOOD] = carModel.getChildByName( "Camera_Hood_Target", true );
    cameraTargets[CAMERA_DEFAULT] = carModelParent;
    cameraTargets[CAMERA_CAR_BEHIND] = carModel.getChildByName( "Licence_Plate_Front", true );
    cameraTargets[CAMERA_CAR_WHEEL] = carModel.getChildByName( "Camera_Wheel_Target", true );enableListeners(); //KEY_UP, KEY_DOWN and ENTER_FRAME
}

Here’s the ENTER_FRAME handler

1
2
3
4
5
6
7
public function tick(e:Event):void {
    updateCamerasPositions();
    camera.lookAt(cameraTargets[currentCamera]);
    driveCar();
    updateCarState();
    singleRender();
}

The last thing I’m going to describe is the updateCarStateFunction. All the coefficients that you can see have been matched by trial & error method. Change them to get different car behaviour.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private function updateCarState():void {
    var movingForward:Boolean = speed > 0;
    //adjust the angular velocity of the wheel
    var wheelRoll:Number = (0.7) * speed;wheelFrontRight.rotationY  -= wheelRoll;
    wheelFrontLeft.rotationY += wheelRoll;
    wheelRearLeft.rotationY -=  wheelRoll;
    wheelRearRight.rotationY -=  wheelRoll;
    //90 and -90 are here to fix the rotation - normal vectors of front wheels must be opposite
    wheelFrontRight.rotationZ = 90-wheelAngle;
    wheelFrontLeft.rotationZ = -90-wheelAngle;
    carModelParent.yaw(speed * wheelAngle * (0.002 + 0.0004 * Number(movingForward)) );
    // we use different speed for car rotation around it's Z axis
    //(lower value when the car is reversing - simple technique, but adds a bit of realism
    carModelParent.moveBackward( speed * (0.9 + 0.2 * Number(movingForward)) );
}

Get this project sources (FlashDevelop AS3 project). Requires Flex 3.4 SDK.