Coding: Bring the Noise

%26lt;0011 Em

C#/XNA: Heatmaps

leave a comment »

I ported the Iterative Diamond-Square Algorithm from Python, and added a few features.

random_generation

The biggest addition is interactivity, and the biggest optimization is using shaders for updating/drawing.  If I have time in the next few days, I might try to move the random generation to a shader.  Shouldn’t be to hard, and it would be a significant gain in speed.  In the meantime, I’ve got generation running on a separate thread (thank you C#’s anonymous functions!)

The code is available on Github.  Follow the break for usage instructions, screenshots, and code highlights.

Usage

  • Camera:
    • Pan with W,A,S,D
    • Zoom with Q,E
  • Random Generation:
    • Space to generate a new map using the IDSA
  • Color Maps:
    • Tab to toggle between colored and BW intensity
    • (Shift+tab is set to cycle color maps, but there’s only one included at the moment)
  • Map Settings:
    • Right square bracket “]” to increase map size (maximum 2049×2049)
    • Left square bracket “[” to decrease map size (minimum 3×3)
  • Pen Interaction:
    • Left click and drag to add intensity.  This is a continuous effect.
    • Right click and drag to subtract intensity.  This is a continuous effect.
  • Pen Settings:
    • Arrow right to increase pressure (or flow rate)
    • Arrow left to decrease pressure (or flow rate)
    • Arrow up to increase pen radius
    • Arrow down to decrease pen radius

Walkthrough

  • Initial Screen – when you first start the app, a nice solid blue greets you, with the current map/pen information available in the lower left corner.  Exciting!

  • Random generation – Note the “Generating…” in the lower right.  Feel free to pan/zoom around the current texture and continue to interact with it while the new heatmap is generated.

  • Random generator sample – You might notice the map size has decreased from the last screenshot.  The laptop I was using struggled with the snip tool while it was generating the new texture and drawing on the old texture all at once, so I tried to help it out.

  • Editing – adding some intensity gives us some nice dark reds

  • Toggling colormap/intensity – same picture as above, but toggling to the intensity map without coloring (using tab)

  • Editing – subtracting with right click, we’ve cut a small inlet into the red/brown that was added above.

Possible improvements

  • I don’t really clean up old maps well, I just null the RenderTargets.  I’m pretty sure there’s a better way to do this.  Maybe not.
  • Keybinds could use a slight tweaking so that pen size/pressure are closer to WASD and more intuitive
  • No indication of pen radius- it would be nice to see size changes in real time.  Even a simple circle around the cursor
  • No indication of pressure- maybe use opacity of the circle around the cursor (from previous point).  Make this toggleable so it doesn’t get in the way of drawing.
  • No saving to file – though I’m not entirely sure who would want this for actual content generation 🙂
  • Random generation is done using an array of floats- could probably be done very quickly in a shader
  • Still a bit new to threads, and right now the last statement in the generator is the thread setting itself to null.  I’m not sure if that’s 1) legal and 2) acceptable

Code Highlights

2D Array Indexing

I didn’t want to have to use two loops, but it was a bad idea to keep arrays of arrays, so instead we keep a single array of values, and use some modular math to get the right elements.  Now we don’t have to worry about the order that we nest our row, column loops for performance.  Woohoo!


// Code is in the Array2D.cs file

public class Array2D{
    public Array2D(int width, int height, float initalValue = 0){
        this.width = width;
        this.height = height;
        this.data = new float[width * height];
        Clear(initalValue);
    }

    public void Clear(float value){
        for (int i = 0; i < width * height; i++)
            data[i] = value;
    }

    public float this[int row, int col]{
        get { return data[width * row + col]; }
        set { data[width * row + col] = value; }
    }
}

Threaded Generator

Having already written a method to generate the data, I simply needed to create a wrapper around that.  This shouldn’t have race conditions because our input is polled, not event-driven.  What that means is that per frame, there can only be ONE trigger of the threaded generate method.  Also, this method itself isn’t handled in a separate thread, so our thread generation is done sequentially.  That guarantees that once that method stops blocking, there will be a non-null thread, which will fail the next attempt to create a thread.


// Code is in MapGenerator.cs

public static class MapGenerator{
    static Random random = new Random(0);
    static Thread generatorThread;

    public static void ThreadedGenerateRandomHeight(Array2D array, Func<float, float, int, float> noiseFunction, Action onComplete=null){
        if (generatorThread != null)
            return;
        if (onComplete == null)
            onComplete = FunctionUtils.NoneAction;
        generatorThread = new Thread(() => { GenerateRandomHeight(array, noiseFunction); onComplete(); generatorThread = null; });
        generatorThread.Start();
}
}

It’s worth noting that this can only generate one map at a time- a static instance of the generatorThread is pure laziness, in that I didn’t want to instantiate a mapGenerator and manage one.  The two better solutions are:

  1. Drop the static, use instances of MapGenerators
  2. Create a static thread pool, and manage available threads

Input class

I’m not going to paste the entire source for input, but the basic is that I wanted to have a very simple way of querying input state.  XNA already provides a simple interface to the Keyboard, Mouse, and GameControllers.  I can check if the “W” key is down with Keyboard.GetState().IsKeyDown(Keys.W).  However, this is what I get with a little work:


// Code is in Game.cs  (app entry point)

//... Somewhere in initialization
Input input = new Input();
input.AddKeyBinding("pan_up", Keys.W);
input.AddKeyBinding("pan_down", Keys.S);
input.AddKeyBinding("pan_left", Keys.A);
input.AddKeyBinding("pan_right", Keys.D);

//... Somewhere in the Update loop, handling input

if (input.IsKeyBindingActive("zoom_out"))
    camera.Scale /= 1.05f;
if (input.IsKeyBindingActive("zoom_in"))
    camera.Scale *= 1.05f;
if (input.IsKeyBindingPress("generate_random_map"))
    map.GenerateRandomHeight();

Ah, much better.  Improve readability in the handleInput function, and make keybinding changes easy (find the block in initialization).  This also means bindings could be loaded from a file…

Utility functions and classes

Shaders- there’s a lot of copying between textures, and most of the shaders only care about blendmode and a single Effect.  I found these in one of the Microsoft demos.


// Code is in Util.cs

public static class ShaderUtil{
    static GraphicsDevice graphicsDevice;
    static SpriteBatch spriteBatch;

    public static void LoadContent(GraphicsDevice graphicsDevice){
        ShaderUtil.graphicsDevice = graphicsDevice;
        ShaderUtil.spriteBatch = new SpriteBatch(graphicsDevice);
    }

    public static void DrawFullscreenQuad(Texture2D texture, RenderTarget2D renderTarget,
                                          BlendState blendState, Effect effect){
        graphicsDevice.SetRenderTarget(renderTarget);
        DrawFullscreenQuad(texture, blendState, renderTarget.Width, renderTarget.Height, effect);
    }

    public static void DrawFullscreenQuad(Texture2D texture, BlendState blendState,
                                          int width, int height, Effect effect){
        spriteBatch.Begin(0, blendState, null, null, null, effect);
        spriteBatch.Draw(texture, new Rectangle(0, 0, width, height), Color.White);
        spriteBatch.End();
    }
}

This isn’t a global solution- it requires setting a static GraphicsDevice, and assumes that the spriteBatch isn’t begun.  It will be bad in situations where you’re doing tons of shader work, since it begins/ends for each draw.  It isn’t parralelizable, though I’m not sure how one does that for multiple shaders.  For basic use, though, it’s quite nice.  Here’s an example of copying the contents of one RenderTarget2D to another:


// Pass 1: copy srcRenderTarget2D to dstRenderTarget2D
ShaderUtil.DrawFullscreenQuad(srcRenderTarget2D, dstRenderTarget2D, BlendState.Opaque, null);
// Pass 2: do cool pixel shader stuff...

Advertisements

Written by delwinna

May 7, 2012 at 10:04 pm

Posted in Projects

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: