0
\$\begingroup\$

I watched videos of games using mode 7 like Outrun or Space Harrier and I want to make a game which imitates this classic graphic. I was inspired by Notch's Prelude Of The Chambered too.

After experimenting with pseudo 3D solutions from here StackOverflow and reading about 3D computer graphic I tried to make my own Mode 7 implementation with 3D affine transformations. (I've read here on stackoverflow too that using matrices and transformations is better than use pseudo 3d technics.)

A came up with this solution, but it doesn't work perfectly:

void mode7() {
    // width and height of texture
    int texW = 1024, texH = 1024;
    // ... of screen        
    int screenW = 800, screenH = 800;

    // precalculated values
    int scrWH = (int) (screenW * screenH);
    int imgWH = (int) (texW * texH);

    // center of perspective projection -- the point I look at
    int toCenterOffsetX = screenW >> 1;
    int toCenterOffsetY = screenH >> 1;

    // depth
    float z = 1f;

    int textureIndex;
    int screenIndex;

    double tempX, tempY, tempZ;

    // screen coordinates
    double screenX, screenY, screenZ;
    // after perspective division
    int perX, perY;

    // scale
    //float scale = 2f;

    for(int pixelY = toCenterOffsetY; pixelY < screenH; pixelY++) {
        for(int pixelX = 0; pixelX < screenW; pixelX++) {
            // z
            screenZ = z;

            // translating to center
            screenY = pixelY - (texH >> 1);
            screenX = pixelX - (texW >> 1);             

            // transformation               

            // rotating -- glitch like graphic somehow              
            //tempX = screenX * Math.cos(rotY*RAD) - screenY * Math.sin(rotY*RAD);
            //tempY = screenX * Math.sin(rotY*RAD) + screenY * Math.cos(rotY*RAD);              
            //screenX = tempX;
            //screenY = tempY;

            // 3D Rx
            //               | x |
            //               | y |
            //               | z |
            // ----------------------------------------------
            // | 1  0    0   | 1 * x + 0 * y + 0 * z     = x'
            // | 0  cos -sin | 0 * x + cos * y - sin * z = y'
            // | 0  sin  cos | 0 * x + sin * y + cos * z = z'               
            tempX = screenX * 1 + 0 * screenY + 0 * screenZ; 
            tempY = screenX * 0 + screenY * Math.cos(rotX*RAD) - screenZ * Math.sin(rotX*RAD);
            tempZ = screenX * 0 + screenY * Math.sin(rotX*RAD) + screenZ * Math.cos(rotX*RAD);

            // rotating
            tempX = screenX * Math.cos(rotY*RAD) - screenY * Math.sin(rotY*RAD); // rotates Z instead of Y
            tempY = screenX * Math.sin(rotY*RAD) + screenY * Math.cos(rotY*RAD);                
            screenX = tempX;
            screenY = tempY;

            // translating back
            screenY = (float) tempY; // + (texH >> 1);
            screenX = (float) tempX; // + (texW >> 1);      

            // perspective divison and set horizon
            perX = (int) (screenX / tempZ) + toCenterOffsetX;
            perY = (int) (screenY / tempZ) + toCenterOffsetY; 

            // index
            //pixelX *= scale; // not working, blank blue screen
            //pixelY *= scale;

            // removing gaps with nearest neighbour like scaling
            //pixelX *= texW / (texW / tempZ);
            //pixelY *= texH / (texH / tempZ);
            // simplified
            //pixelX *= tempZ;
            //pixelY *= tempZ; // not working...even one frame won't be drawn, just white screen

            textureIndex = (int) ( pixelY * texW + pixelX );

            screenIndex = (int) ( perY * screenW + perX );

            // write pixels
            if( (textureIndex >= 0 && textureIndex < imgWH) && 
                (screenIndex >= 0 && screenIndex < scrWH)
            ) {
                output[screenIndex] = input[textureIndex];
            }
        }
    }
}

If I'm right, then I should use something similar to the nearest neighbour scaling to remove the gaps.

Games like Outrun doesn't have a huge map eg. 3000 x 3000 (I think) but it'll take minutes to drive through the picture. I first used a 64 * 64 texture for this but ther results were almost exactly the same. How could I make the map scaled to the proper size?

I leave the optimizations for later.

The input / output arrays are due to the DataBufferInt and getRGB(x, y, w, h... stuff I used to avoid setRGB and other slow methods.

I don't ask for exact, full solutions, I ask for some hints, guides, or mathematical explanations.

I'd like to learn enough so I can create a simple 3D game like Notch did. I know he's already had years of experience of game programming but I hope it's something I can learn without 10+ year of game programming.

Here are the results and the used texture:

(Sorry for my bad English.)

enter image description here

enter image description here

enter image description here

\$\endgroup\$
1
\$\begingroup\$

Outrun and Space Harrier don’t use mode7 like engines. I recommend reading Louis Pseudo3D page for indepth reading. Also Jake Gordon’s Javascript Outrun for code and technique examples.

In general the road is split up in segments. Think of each segment being the width of the road and about a meter deep. The road is then defined as an array of these segments. Each segment defines an angle if the segment creates a curve. One could include more information in each segment- roadside objects or properies of the road like surface type (ice, mud or asphalt) to have gameplay elements interact with it. This is how many racegames define the long routes.

Each segment is then drawn using the painters algorithm starting with the one in the distance until the segment near the camera is reached.

Drawing a segment is translating a 3D rectangle to screenspace. Thus the entire road is a stretch of rectangles stacked after oneother. This looks much like your attempt with pseudo 3D code- however it can be done equally with matrices etc.

Thus the effect you are looking for (Outrun, Space Harrier) is not a Mode7 effect. Mode7 is used in Mariokart and F-Zero (SNES) it produces a different type of road with smaller- usually looped laps. Also Notch game likely uses a different technique called raycasting also seen in the original Wolfenstein3D and Doom games.

\$\endgroup\$
0
\$\begingroup\$

You have to make slices closer to the camera wider. pick a large enough value and divide it by the z coordinate. If there are still gaps, try making that arbitary value larger

\$\endgroup\$

Your Answer

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Not the answer you're looking for? Browse other questions tagged or ask your own question.