Easy Papervision3D space dust tutorial and source
PDFMany space shooters use this concept where you are the pilot that looks out from the front window, lasering down enemies for bounty, right. Ever noticed spacedust? It’s very subtle when it is used, but it adds to the realism of outer space, making the space feel less empty, less static.
Here’s how you can do that in Papervision3D, using ParticleFields. I’ve used the trunk of the papervision code repository, revision 851, but it should work with the last release without to much hassle. Note that I’m no PV3D guru; I just started to learn this stuff and I’m just sharing what I’ve learned so far.
1 | /wp-content/uploads/2016/02/Spacedusttutorial.swf, 600, 300 |
- Here’s how it works:
- divide space into a 3d grid and parts of the spacedust cloud on 8 gridpoints directly around the camera (the ship), we’ll call these dustpockets
- when the camera moves, dustpockets are removed and created to keep only the 8 gridpoints around the ship occupied
Divide space into a 3d grid and snap dust pockets on it
We’re not actually creating a grid, but we’re going to say things like: “Give me the closest 3d point behind the ship in the upper left corner on the invisible grid”, so that we can create a dust pocket in that position. Sounds complicated? Luckily, the math is easy:
Let’s say there’s only the x-axis. Now from 0 to 100, I want the gridpoint on the left of 32 on grid with size 5. Here is how it would look:
As you can see, the x-value 30 is the value to the left of 32 on a grid with size 5. Here’s the equation to get to that answer:
gridpoint left: Xgrid = Xvalue - (Xvalue % gridsize)
gridpoint right: Xgrid = Xvalue - (Xvalue % gridsize) + gridsize
Example gridpoint left for value 32 and grid size 5:
gridpoint left: Xgrid = 32 - (32 % 5)
gridpoint left: Xgrid = 32 - (2)
gridpoint left: Xgrid = 30
Example gridpoint right for value 32 and grid size 5:
gridpoint left: Xgrid = 32 - (32 % 5) + 5
gridpoint left: Xgrid = 32 - (2) + 5
gridpoint left: Xgrid = 35
We’re going to apply this kind of math to find all 8 gridpoints around the camera. So not just the x-axis, but y and z-axis as well.
The reason we need these 8 gridpoints is because we can’t just create a single cloud. Since the ship can move in any direction and look in any direction, but we can’t fill all of space with dust particles due to memory restrictions, we need to dynamically ‘move’ the cloud with the ship, while keeping the dust particles on the same place. The only way to do this is by dividing the single dust cloud into several small ones and create them as the ship goes. At the same time we need to keep the number of particles down, so we need the minimum dust pockets and still cover all sides by create 8 small clouds that together form a big cloud and can grow in any direction without draining too much memory and cpu power.
Here’s how we’re going to place the clouds:
Here’s the code to do this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | public function update(fieldsize:Number = 2500):void { // calculate segment (dustpocket) size const FIELDSIZE_POCKET:Number = fieldsize / 5; // determine the 8 gridpositions around the ship (eg. 1: front bottom left, 2: behind top right) const pocketPositions:Array = new Array(); pocketPositions.push(calcGridPosition(camera.position, FIELDSIZE_POCKET, false, false, false)); pocketPositions.push(calcGridPosition(camera.position, FIELDSIZE_POCKET, true, false, false)); pocketPositions.push(calcGridPosition(camera.position, FIELDSIZE_POCKET, false, true, false)); pocketPositions.push(calcGridPosition(camera.position, FIELDSIZE_POCKET, false, false, true)); pocketPositions.push(calcGridPosition(camera.position, FIELDSIZE_POCKET, true, true, false)); pocketPositions.push(calcGridPosition(camera.position, FIELDSIZE_POCKET, true, false, true)); pocketPositions.push(calcGridPosition(camera.position, FIELDSIZE_POCKET, false, true, true)); pocketPositions.push(calcGridPosition(camera.position, FIELDSIZE_POCKET, true, true, true)); } /** * Calculates a gridpoint around the ship. */ private function calcGridPosition(position:Number3D, gridSize:Number, xMin:Boolean, yMin:Boolean, zMin:Boolean):Number3D { const gridPosition:Number3D = new Number3D(); gridPosition.x = (position.x - (position.x % gridSize)) + ((xMin) ? 0 : gridSize); gridPosition.y = (position.y - (position.y % gridSize)) + ((yMin) ? 0 : gridSize); gridPosition.z = (position.z - (position.z % gridSize)) + ((zMin) ? 0 : gridSize); return gridPosition; } |
This is really all there is to it. The only thing we need to do now is create new dust pockets for gridpoints without one and remove dust pockets for points that are not within range anymore. Over and over again. Here’s the entire Spacedust class:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 | package org.codemonkey.spacedusttutorial.papervision3d { import flash.utils.Dictionary; import org.papervision3d.cameras.Camera3D; import org.papervision3d.core.math.Number3D; import org.papervision3d.materials.special.ParticleMaterial; import org.papervision3d.objects.special.ParticleField; import org.papervision3d.scenes.Scene3D; /** * Spacedust simulator. Creates 8 segments of the dustcloud around the given camera at any point in space. * When the camera moves, new dustpockets are generated and old ones (out of sight) are removed. * * @author Benny Bottema */ public class Spacedust { private static const DEFAULT_PARTICLEMATERIAL:ParticleMaterial = new ParticleMaterial(0xffffff, .5, ParticleMaterial.SHAPE_CIRCLE); private var basic3DSetup:Basic3DSetup; private var camera:Camera3D; private var spacedustCloud:Dictionary; private var fieldsize:Number; private var particleCount:Number; private var dustMaterial:ParticleMaterial = new ParticleMaterial(0xffffff, .5, ParticleMaterial.SHAPE_CIRCLE); public function Spacedust(basic3DSetup:Basic3DSetup, camera:Camera3D = null, fieldsize:Number = 2500, particleCount:Number = 400, dustMaterial:ParticleMaterial = null) { spacedustCloud = new Dictionary(); this.basic3DSetup = basic3DSetup; this.camera = (camera != null) ? camera : basic3DSetup.camera; this.fieldsize = fieldsize; this.particleCount = particleCount; this.dustMaterial = (dustMaterial != null) ? dustMaterial : DEFAULT_PARTICLEMATERIAL; } public function update():void { // calculate segment (dustpocket) size const FIELDSIZE_POCKET:Number = fieldsize / 5; // determine the 8 gridpositions around the ship (eg. 1: front bottom left, 2: behind top right) var pocketPositions:Array = new Array(); pocketPositions.push(calcGridPosition(camera.position, FIELDSIZE_POCKET, false, false, false)); pocketPositions.push(calcGridPosition(camera.position, FIELDSIZE_POCKET, true, false, false)); pocketPositions.push(calcGridPosition(camera.position, FIELDSIZE_POCKET, false, true, false)); pocketPositions.push(calcGridPosition(camera.position, FIELDSIZE_POCKET, false, false, true)); pocketPositions.push(calcGridPosition(camera.position, FIELDSIZE_POCKET, true, true, false)); pocketPositions.push(calcGridPosition(camera.position, FIELDSIZE_POCKET, true, false, true)); pocketPositions.push(calcGridPosition(camera.position, FIELDSIZE_POCKET, false, true, true)); pocketPositions.push(calcGridPosition(camera.position, FIELDSIZE_POCKET, true, true, true)); var newSpacedustCloud:Dictionary = new Dictionary(); // create new dustfields or transfer existing ones (and remove existing ones the scene) const particlesPerPocket:Number = particleCount / pocketPositions.length; for each (var pocketPosition:Number3D in pocketPositions) { var pocketKey:String = pocketPosition.toString(); var reusableDustPocket:ParticleField = spacedustCloud[pocketKey]; // remove so we can delete the particles from the remaining particlefields (which means they are out of sight) delete spacedustCloud[pocketKey]; // add the removed particlefield to the new dustcloud or create one if dustpocket is new for te current gridposition around the ship if (reusableDustPocket != null) { basic3DSetup.removeFromScene(reusableDustPocket); newSpacedustCloud[pocketKey] = reusableDustPocket; } else { newSpacedustCloud[pocketKey] = new ParticleField(dustMaterial, particlesPerPocket, 2, fieldsize, fieldsize, fieldsize); newSpacedustCloud[pocketKey].x = pocketPosition.x; newSpacedustCloud[pocketKey].y = pocketPosition.y; newSpacedustCloud[pocketKey].z = pocketPosition.z; } } // manually remove all the remaining obsolete particles from the old cloud (they are out of sight) for each (var oldDustPocket:ParticleField in spacedustCloud) { oldDustPocket.removeAllParticles(); } // now add the old dustpockets which are still within sight, plus the new dustpockets to the scene for each (var newDustPocket:ParticleField in newSpacedustCloud) { basic3DSetup.addToScene(newDustPocket); } // replace the old cloud with the new cloud spacedustCloud = newSpacedustCloud; } /** * Calculates a gridpoint around the ship. */ private function calcGridPosition(position:Number3D, gridSize:Number, xMin:Boolean, yMin:Boolean, zMin:Boolean):Number3D { const gridPosition:Number3D = new Number3D(); gridPosition.x = (position.x - (position.x % gridSize)) + ((xMin) ? 0 : gridSize); gridPosition.y = (position.y - (position.y % gridSize)) + ((yMin) ? 0 : gridSize); gridPosition.z = (position.z - (position.z % gridSize)) + ((zMin) ? 0 : gridSize); return gridPosition; } } } |
Notice the particel fields we’re using to create the eight dust pockets with.
new ParticleField(dustMaterial, particlesPerPocket, 2, fieldsize, fieldsize, fieldsize);
Also, the Basic3DSetup class I’ve used is simply a convenience class with default camera, scene, viewport etc. It’s much like Papervision’s own BasicView -which I didn’t know about at the time- except even cleaner.
The Spacedust class accepts an optional camera to position the dust cloud around. Since this approach is meant to create the illusion of infinite space dust, it doesn’t make sense to create clouds on non-camera’s. Also, you can specify your own particle count and dust material in the constructor of the Spacedust class. The example at the top of the page uses only the defaults.
Tags: actionscript • game design • Papervision3D • space dust
JBrookes
You don’t need all that.
create a single particleField
then in the renderloop
cloud.copyTransform(camera);
loop through the particles and move the z position
reset z position when particle goes behind camera.
You can do all that and the skybox in under 100 lines.
post on the forum PV forum if you want to know more.
Benny Bottema Post author
Thank you for taking time to response JBrookes, but I’m not sure I follow. In your snippet, you seem to position the particles around the camera and then moving the particles past the camera, while the idea is to have a moving camera (ship) and then particles that sit still in space which you go past, in any direction, not just z.
I feel I’m missing something obvious here.