Marble Madness like game

Hi All

I’d like to build a marble madness style game - but I’ve been struggling to think of a way to represent the surface that the ball rolls around on. The first way that popped into my brain was a standard heightmap, but the height maps can’t have vertical sections.

I’d like to avoid having to build models in blender or something, I’d like to be able to build the ground geometry in a similar manner to the heightmap - ie represent the map in some sort of bitmap.

Any ideas? Perhaps I should investigate hacking the heightmap to be able to have vertical sections, or perhaps I should roll my own terrain generator. Am I thinking about this too hard?

First of all, there’s no such thing as thinking too hard.

It depends how you want to design the maps. If you want a smooth surface, e.g. the hills from the windows xp background, you should use the heightmaps.
If you want only ramps and straight sections (vertical or horizontal) you can either use one big model per level, or multiple small “blocks”: one mesh for the ramps, one for cuboids, one for pyramids, etc. You can make custom meshes (like the Cube from the first tutorial) in jME3.

What do you want the maps to look like?

And if you choose the second option, you could make your own files, with their own extension, or something like that. Or just read a bitmap.

I guess if you look at a terrain like this:

and try to imagine it projected vertically onto a 2d array like a heightmap - you loose a bit of information. What tiles are connected to what tiles, what angle are they at, when do I draw a wall. So, you have to make assumptions. I just can’t think what those assumptions are.

I had a play last night, I basically read in a square png and read the colour value of each pixel producing a quad for each one translating the quad in space to correspond to its place in the png. That gives me a flat square surface to walk/roll around on.

Next, I modified the height that each quad appears at according to the colour value (I actually added the red, green, and blue channels together then divided by 3 because I don’t know what I am doing) - the height was a number between 0 and 1, I multiplied by 10 and that was the height. That gave me a bunch of quads floating in the air.

Then, and this is where I am struggling, I look at the pixel above, to the left, to the right, and below the current pixel to see if the colour is the same - if its different I tilt the quad towards it 45 degrees. I choose 45 degrees because I needed a number, but I think this last bit of the algorithm is a dead end. This doesn’t suggest an easy way to build walls either.

I know I could open blender and 3d model this but I don’t know how to use blender to make 3d models, and I kinda wan’t to keep the map format really simple.

Instead of quads, perhaps it should be a box-per-pixel, and the height of the box depends on the colour of the pixel. That doesn’t give me ramps but it does give me walls for free.

I could then deform the boxes, or place a quad, if the neighbouring boxes are close enough - IE if the heights of its neighbours are within some threshold. That would give me something similar enough to the pic above to roll around on.

If you want to use something simple like imagery then if it were me, I’d make it 3x3 pixels per quad. (That would let you have the pointy pyramid pieces.)

And by 3x3 I mean not adjoined. So (if you imagine one dimension for a second) pixels 0, 1, 2 are one quad. 3, 4, 5 are the next, and so on. That way you can precisely control where the drops and gradients are right in the image. So if 2 and 3 are the same value then you know that it’s part of a ramp or drivable space. If 2 and 3 are different heights then you know there is a wall there.

The algorithm for turning that into a mesh is pretty simple and you can have any angle gradient that you want. You could even have troughs and ridges and stuff.

So the difference between the left and rightmost pixel defines the slope, and the pixels themselves define the height. What’s the middle pixel for - couldn’t I use a 2x2 pixel per quad layout?

Yes, you could. But in the marble madness picture there were little pyramids and stuff. Also, a little extra width will make it easier to ‘see’ the levels in the image. By dropping or raising the middle pixels you could also make little trenches and ridges in the middles of grid cells.

Up to you, though.

Oooh of course. Thanks for the advice :smiley:

Projecting the terrain vertically onto a 2d structure means that cannot represent angles greater than 45 degrees.

I am just going to have to learn to use blender, and model the levels properly.

???

Yeah, you can’t use terrain. Never use terrain. As I said, though, it’s trivial to generate the data from the image.

Every three pixels block, emit 4 quads. It supports 90 degree walls and also angles as big as your height map resolution supports… which is arbitrarily up to you.

Sorry, I meant terrain in a general sense, not specifically the terrain class in jmonkey :slight_smile:

I was emitting 1 quad per block, and angling and lengthening it to be 1 unit horizontally.

I am not sure how 4 quads per 3x3 pixel block is supposed to work. Do they overlap?

I count four:

Not sure how else 3x3 points would be made into anything. I also don’t see how this limits you to 45 degrees.

The y value of each of those points is the pixel value * whatever scale you want. If your overall square is 1 meter and your scale is 100000 then the maximum slope would be quite steep. Just up to you.

Right so here is my attempt at reading in an image in chunks of 3x3 pixels, making 4 quads, and placing them in space beside each other. I can’t work out how to manipulate the y values of each quads corners though.

I just had a thought though - is the x and z of each quad proportional? What I mean is, if the colours range between 0 and 1 (black b to white w), and say we have 4 quads made like this:

b b w
b b w
b b w

The x distance of whole thing should be 1 unit, as is the z distance. The top left quad is:

b b
b b

That would be 1 units x and z. The top right quad is:

b w
b w

That would be 0 unit x and 1 unit z and 1 unit y and would look like this:

Say we had this arrangement:

b b b
b b b
b b b

Both the top left and top right are:

b b
b b

and it would look like this:

I feel like I am really close, but, I can’t think how to do the proportional part. Its like the bigger the difference in pixel values between neighbor pixels is the more vertical space it takes and the less horizontal space…

Here is my code i have so far, sans any height stuff:

public GameEnvironment(AssetManager assetManager, BulletAppState bulletAppState) {
    mat_terrain = new Material(assetManager, "Common/MatDefs/Terrain/Terrain.j3md");
    mat_terrain.setTexture("Alpha", assetManager.loadTexture("Textures/alphamap.png"));

    /**
     * Add grid texture into the blue layer (Tex3)
     */
    Texture surfaceTexture = assetManager.loadTexture("Textures/grid8.png");
    surfaceTexture.setWrap(Texture.WrapMode.Repeat);
    mat_terrain.setTexture("Tex3", surfaceTexture);
    mat_terrain.setFloat("Tex3Scale", 2048f);

    Texture theMap;
    theMap = assetManager.loadTexture("Textures/play.png");

    Image colorImage = theMap.getImage();

    int imageWidth = colorImage.getWidth();
    int imageHeight = colorImage.getHeight();

    if (imageWidth % 3 != 0 || imageHeight % 3 != 0) {
        throw new RuntimeException("imageWidth: " + imageWidth + " or imageHeight: " + imageHeight + " not divisible by 3");
    }
    
    System.out.println("number of thingys " + imageHeight + "  " + imageWidth * imageHeight);
    ImageRaster raster = ImageRaster.create(colorImage);

    ColorRGBA colorStore = new ColorRGBA();

    for (int h0 = 0; h0 < imageHeight; h0+=3) {
        int h1 = h0+1;
        int h2 = h0+2;
        
        for (int w0 = 0; w0 < imageWidth; w0+=3) {
            int w1 = w0+1;
            int w2 = w0+2;
            
            Node tempNode = new Node(w0 + "," + h0);
            
            float[][] colour = new float[3][3];
            
            colour[0][0] = this.getColourAt(w0, h0, raster);
            colour[0][1] = this.getColourAt(w0, h1, raster);
            colour[0][2] = this.getColourAt(w0, h2, raster);
            colour[1][0] = this.getColourAt(w1, h0, raster);
            colour[1][1] = this.getColourAt(w1, h1, raster);
            colour[1][2] = this.getColourAt(w1, h2, raster);
            colour[2][0] = this.getColourAt(w2, h0, raster);
            colour[2][1] = this.getColourAt(w2, h1, raster);
            colour[2][2] = this.getColourAt(w2, h2, raster);
            
            System.out.println(colour[0][0] + "," + colour[0][1] + "," + colour[0][2]);
            System.out.println(colour[1][0] + "," + colour[1][1] + "," + colour[1][2]);
            System.out.println(colour[2][0] + "," + colour[2][1] + "," + colour[2][2]);
            
            /* we add/remove tileWidthHeight/2 because the rotation isn't about the centre, I think its off to one side */
            Vector3f transTopLeft = new Vector3f((1 * tileWidthHeight) - tileWidthHeight / 2, 0, (1 * tileWidthHeight) + tileWidthHeight / 2);
            Vector3f transTopRight = new Vector3f((2 * tileWidthHeight) - tileWidthHeight / 2, 0, (1 * tileWidthHeight) + tileWidthHeight / 2);
            Vector3f transBottomLeft = new Vector3f((1 * tileWidthHeight) - tileWidthHeight / 2, 0, (2 * tileWidthHeight) + tileWidthHeight / 2);
            Vector3f transBottomRight = new Vector3f((2 * tileWidthHeight) - tileWidthHeight / 2, 0, (2 * tileWidthHeight) + tileWidthHeight / 2);
            /* make a mesh and stick it in a geometry, and rotate it properly */

            Geometry topleft = new Geometry("topleft", new Quad(tileWidthHeight, tileWidthHeight));
            Geometry topright = new Geometry("topright", new Quad(tileWidthHeight, tileWidthHeight));
            Geometry bottomleft = new Geometry("bottomleft", new Quad(tileWidthHeight, tileWidthHeight));
            Geometry bottomright = new Geometry("bottomright", new Quad(tileWidthHeight, tileWidthHeight));
            
            //flatten
            topleft.rotate((float) (Math.PI / 2) * 3, 0, 0);
            topright.rotate((float) (Math.PI / 2) * 3, 0, 0);
            bottomleft.rotate((float) (Math.PI / 2) * 3, 0, 0);
            bottomright.rotate((float) (Math.PI / 2) * 3, 0, 0);
            
            topleft.setLocalTranslation(transTopLeft);
            topright.setLocalTranslation(transTopRight);
            bottomleft.setLocalTranslation(transBottomLeft);
            bottomright.setLocalTranslation(transBottomRight);
                    
            topleft.setMaterial(mat_terrain);
            topright.setMaterial(mat_terrain);
            bottomleft.setMaterial(mat_terrain);
            bottomright.setMaterial(mat_terrain);
            
            tempNode.attachChild(topleft);
            tempNode.attachChild(topright);
            tempNode.attachChild(bottomleft);
            tempNode.attachChild(bottomright);
            
            tempNode.setLocalTranslation(new Vector3f((w0/3)*2, 0, (h0/3)*2));
                    
            environment.attachChild(tempNode);
        }

        /* make terrain solid */
        CollisionShape sceneShape = CollisionShapeFactory.createMeshShape(environment);
        landscape = new RigidBodyControl(sceneShape, 0);
        environment.addControl(landscape);
        bulletAppState.getPhysicsSpace().add(landscape);
    }
}

private Float getColourAt(int x, int y, ImageRaster r) {
    if (x < 0 || y < 0 || x >= r.getWidth() || y >= r.getHeight()) {
        return null;
    }
    float blue = r.getPixel(x, y).getBlue();
    float red = r.getPixel(x, y).getRed();
    float green = r.getPixel(x, y).getGreen();

    return (blue + red + green) / 3;
}

The image pixel value IS the y value without scaling. So pick what you want your maximum y to be for a level and multiply that by the pixel color.

You also go through a TON of work just to have quad oriented properly. Just set the corner positions directly instead of all of that weird rotate stuff. (In fact, you are ultimately better off making your own mesh.)

Something like:

quad = new Quad(1, 1); // which we'll blow away
quad.setBuffer(Type.Position, 3, new float[]{x0, y0, z0,
                                             x1, y1, z0,
                                             x1, y2, z1,
                                             x0, y3, z1
                                             });

whoa. seriously, thanks.

I’ve made some pretty good progress, but, I have gotten really lost. For example this image:

gives me unexpected results.

I imagined that the image above would (if looking down upon it) would have 4 triangles from 4 quads flat in a diamond shape, and four triangles rising up and away from the centre.

But I have mixed myself up a bit and some of the triangles face the wrong way (so you can only see them from the bottom) and I have the wrong corners being tall.

I think perhaps the order in which you set the quads corners matters, and I am not sure if I am doing it correctly. Also, the fact the images are read from bottom-up rather than top-down is confusing me.

I know its a big ask, but can someone have a squizz at my code and perhaps spot what I cannot?

https://bitbucket.org/jakebriggs/maptest