PDA

View Full Version : Isometric rotating height map experiment



Tesseract
November 4th, 2007, 05:41 PM
Hi all

An experiment I have been working on has just hit the point where I feel comfortable showing it off: A rotate-able 3d-ish chunk of terrain, created using Perlin noise and the threshold capabilities of the BitmapData object.

Viddy it here (http://www.eccesignum.org/flash/scorched/071104/).

Warning: In its present incarnation it is something of a processor hog.

Code to follow, after cleanup and performance optimization. I look forward to critiques and comments.

Sirisian
November 4th, 2007, 08:33 PM
Barely real-time, but awesome. What are you planning to make with this?

mlk
November 4th, 2007, 10:38 PM
I look forward to a faster version, but I love the experiment ! (and will gladly take a peek at the code)

Tesseract
November 5th, 2007, 12:09 AM
Eventually I hope to make a 3d-ish version of Scorched Earth. Yeah, not to speedy now, but I have some ideas to help with that. Double-buffering, for instance.

Jerryscript
November 5th, 2007, 02:33 AM
That's very nice!

Have you considered rendering a perspective view? The extra calculations may give you fits while optimizing the performance, but the effect is pretty nice.

I've got a similar project, in AS1 for development purposes, so the rendering is very slow. I've outputted a static rendering to PNG and then loaded it and used the same perlinNoise heightmap to offset objects moving over the terrain. I imagine in AS3 you could eventually do this in real time, though you may have to give up resolution in exchange for performance. Here's a couple of examples from early in my project:

Pre-Rendered (http://www.geocities.com/webwizardsways/flash/prerender.html)

Rendering (Warning -- takes up processes and takes forever!) (http://www.geocities.com/webwizardsways/flash/render.html)

Tesseract
November 5th, 2007, 08:15 AM
Jerryscript-
We are thinking along the same lines! My algorithm builds from bottom-up, rather than back-to-front, but once rendered, they will do the same thing.

Ultimately I want to make the terrain deformable, by drawing various size "holes" onto the source bitmap, then re-rendering the appropriate sections of the terrain. Once I have that piece in place, then the game logic can by added, and I will be 90% finished.

Tesseract
November 6th, 2007, 12:49 PM
I did some optimization and posted the latest prototype (http://www.eccesignum.org/flash/scorched/071105/). That's about all the time I have for this thing right now; I will post the code this evening.

Thanks for the appreciation and feedback, everyone.

Tesseract
November 6th, 2007, 10:36 PM
Here is the source for the experiment



package {
import flash.display.*;
import flash.geom.*;
import flash.events.*;
[SWF(backgroundColor="0x000000",width="480",height="480", frameRate="25")]
public class ThresholdTest extends Sprite {
private var bitmapData1:BitmapData; // perlin bitmapdata
private var bitmapData2:BitmapData; // threshold bitmapdata
private var displayBitmapData:BitmapData;
private var bufferBitmapData:BitmapData;
private var displayBitmap:Bitmap;
private var iterator:Number = 1;
private var w:Number;
private var h:Number;
private var stageW:Number;
private var stageH:Number;
private var dataLayers:Array;
private var filledLayers:Array;
private var filledLayersLength:Number = 0;
private var thresholds:Array;
private var colors:Array;
private var displayMatrix:Matrix;
private var minHeight:Number;
private var maxHeight:Number;
private static const MAX_COLORS:Number = 256;
private static const POINT:Point = new Point(0,0);
private static const STEP:Number = Math.PI*2/360;
private var worldIterator:Number = 0;

public function ThresholdTest() {
addEventListener(Event.ENTER_FRAME,init);
}


/* re-render the bitmap */
private function onClick(e:MouseEvent):void {
removeEventListener(Event.ENTER_FRAME,rotateBitmap );
displayBitmapData.fillRect(displayBitmapData.rect, 0xff000000);
worldIterator = 0;
initGraphics();
}

/* set variables */
private function init(e:Event):void {
if(!stage) return;
removeEventListener(Event.ENTER_FRAME,init);
stage.addEventListener(MouseEvent.CLICK,onClick);
stage.scaleMode = StageScaleMode.NO_SCALE;
w=250;
h=250;
stageW = stage.stageWidth;
stageH = stage.stageHeight;
bufferBitmapData = new BitmapData(stage.stageWidth,stage.stageHeight,true ,0xff000000);
displayBitmapData = new BitmapData(stage.stageWidth,stage.stageHeight,true ,0xff000000);
displayBitmap = new Bitmap(displayBitmapData);
addChild(displayBitmap);
bitmapData1 = new BitmapData(w,h,true,0xffffffff);
initGraphics();
}

/* create the bitmap */
private function initGraphics():void {
stage.removeEventListener(MouseEvent.CLICK,onClick );
bitmapData1.perlinNoise(
Math.round(Math.random()*150)+150, // baseX
Math.round(Math.random()*150)+150, // baseY
Math.round(Math.random()*12), // numOctaves
Math.random()*100000, // randomSeed
false, // stitch
Math.random()>.5?true:false, // fractalNoise on or off
Math.round(Math.random()*7), // channelOptions
true // grayScale
);
dataLayers = [];
thresholds = [];
colors = [];
filledLayers = [];

/* of the 256 grayscale colors, find which ones are in this bitmap */
for(var i:Number = 0;i<MAX_COLORS;i++) {
thresholds[i] = 0<<24|i<<16|0<<8|0
colors[i] = 255<<24|i<<16|i<<8|i;
dataLayers[i] = new BitmapData(w,h,true,0x00000000);
var c1:Number = bitmapData1.getColorBoundsRect(0xffffffff,colors[i],true).width;
if(c1>0) { filledLayers.push(i); }
}
filledLayersLength = filledLayers.length;
displayMatrix = new Matrix();
addEventListener(Event.ENTER_FRAME,refreshBitmap);
}

/* rotate and re-draw the layers */
private function rotateBitmap(e:Event):void {
bufferBitmapData.lock();
displayBitmapData.lock();
bufferBitmapData.fillRect(bufferBitmapData.rect,0x ff000000);
displayBitmapData.fillRect(displayBitmapData.rect, 0xff000000);
var ro:Number = STEP*worldIterator;
var i:Number = 0;
while(i<filledLayersLength) {
displayMatrix.identity();
displayMatrix.translate(-w>>1,-h>>1);
displayMatrix.rotate(ro);
displayMatrix.translate(stageW >> 1,stageH*.8 - (dataLayers[i].height >> 1) - (i >> 1));
bufferBitmapData.draw(dataLayers[filledLayers[i]],displayMatrix);
i++;
}
bufferBitmapData.unlock();
displayBitmapData.copyPixels(bufferBitmapData,buff erBitmapData.rect,POINT);
displayBitmapData.unlock();
worldIterator++;
}

/* build the individual color layers */
private function refreshBitmap(e:Event):void {
if(iterator==dataLayers.length) {
iterator = 0;
removeEventListener(Event.ENTER_FRAME,refreshBitma p);
addEventListener(Event.ENTER_FRAME,rotateBitmap);
stage.addEventListener(MouseEvent.CLICK,onClick);
return;
}
dataLayers[iterator].threshold(
bitmapData1, // source bitmap
bitmapData1.rect, // source rect
new Point(0,0), // destination point
">=", // operation - "==", ">=" and ">" all look cool
thresholds[iterator], // threshold ONLY ONE CHANNEL!!!!!!!!
colors[iterator], // color
0x00ff0000, // mask ONLY ONE CHANNEL!!!!!!!!!
false // copy source?
);
displayMatrix.identity();
displayMatrix.translate(-w>>1,-h>>1);
displayMatrix.translate(stageW >> 1,stageH*.8 - (dataLayers[iterator].height >> 1) - (iterator >> 1));
displayBitmapData.draw(dataLayers[iterator],displayMatrix);
iterator++;
}
}
}



It is actually Actionscript 3, not PHP.

If any of you have any advice on performance optimization without obfusticating the code too badly, I would sure appreciate it.

Jerryscript
November 6th, 2007, 11:52 PM
The most obvious increase you can achieve is by using less resolution. However, in your method, that seems to mean replicating layers to fill the void. This could help, though I'm not sure of the results with your bottom up rendering method.

I think you need to make a tough decision: Resolution -- Performance. After all, you are using Flash, and even top of the line games on the PS3 or XBOX360 don't use pixel level resolution. Consider using a mesh technique with interpolation for your textures. Combined with some form of backface culling and or occlusion techniques, this should give much faster performance.

And of course, there's always preprocessing with a limited freedom of camera movement.

The good news is, once you are happy with your rendering technique, your holes will be much faster to implement than the terrain itself!

Tesseract
November 7th, 2007, 08:38 AM
I think pre-processing may be the way to go. Creating the bitmap is easy on the processor. So is dividing the layers. So is writing it to the display bitmap. The heavy stuff happens when I need to rotate and re-draw the bitmap data. I think I can shift the load from processor to RAM by pre-rendering a set number of views and then just turning the individual bitmaps on and off. Problem is, a 480x480 bitmap takes up around 650k or memory space. Multiply that by, say, 100, and I will run out memory pretty quickly.

All in all I am happy with this experiment. It was meant as a proof-of-concept, and I would call it a success.

Sirisian
November 7th, 2007, 11:31 AM
I know you're using a form of voxel rendering for realistic deformation. But you could see a massive speed boost if you learn how to render terrains how games do it. For instance a quadtree/grid based terrain.