SNES Mode-7

Posted By: Tails01

SNES Mode-7 - 08/13/12 21:05

SNES MODE 7 examples:

FZERO 1990 (c)Nintendo


MARIO KART 1992 (c)Nintendo



Do you remember these times
Almost no one reminds himself back to the beginning of 3d, where everthing
starts with a stretched plane that simulated a 3d-space mixed with some nice painted sprites.


But if you want to inform yourself about the "making of" you only meet faulty or incomplete code.
So I've decided to publish a version of mine here.

For those who doesn't know what Mode-7 actually is.
MODE 7 WIKIPEDIA^^

I hope the lines are well commented and I hope you guys have some fun with it.
Maybe I hand in the mathematical part later, it's to hard to explain it in engl.
PS. some feedback would be nice and some hints about performance-improvement.
Thanks

Code:
// Pseudo-Mode-7!
/* written in August 2012 by Tommy - "tommy.draeger@googlemail.com" */

#include <acknex.h>
#include <litec.h>

#define xres 320
#define yres 240

BMAP* blank_16;
BMAP* blank_32;
BMAP* sphere 			= "...";
BMAP* displace 		= "...";
BMAP* background 		= "...";

PANEL* framebuffer 	= {	layer = 10;	flags = VISIBLE; }
PANEL* framedepth  	= {	layer = 11;	flags = VISIBLE; }
PANEL* obj_01 			= {	layer = 12; flags = VISIBLE | OVERLAY; bmap = sphere; }

function mode_7_level(BMAP* screen, BMAP* source, BMAP* distfog, BMAP* backdrop);
function mode_7_sprite(PANEL* sprite,int px, int py, float obj_dir, float obj_org);

typedef struct /* MODE 7 SETTINGS */
{
	int foclength;//FOCALLENGTH
	int horizon;
	int scale;//SCALEFACTOR FOR LEVELGROUND
	int obj_scale;//SCALEFACTOR FOR SPRITES
}M7_OPT;


//THESE ARE VALUES THAT WORKS FINE FOR
//ME AT A RESOLUTION OF 320 X 240
M7_OPT* m7 =
{
	foclength = 400;
	horizon = 80;
	scale = 60;
	obj_scale = 15;
}

float angle = 0;
float xOffset = 0;
float yOffset = 0;

//SCREEN AND DISTFOG ARE THE BMAPS
//TO DRAW ON. THE OTHERS SERVES AS
//LEVELGROUND AND SKYBACKDROP
function mode_7_level(BMAP* screen, BMAP* source, BMAP* distfog, BMAP* backdrop)
{
fixed count;
DWORD blend;

int x,y;
//FRAME
int frm;
//JUST FOR PERFORMANCE IMPROVEMENT
int dx,dy;
int mask_x,mask_y;
int mask_x_backdrop;

float space_x, space_y, space_z;
float screen_x, screen_y;
float px, py, pz;
float sx, sy;

mask_x = bmap_width(source) 	-1;
mask_y = bmap_height(source) 	-1;
mask_x_backdrop = bmap_width(backdrop) 	-1;

	while(1)
	{
	//STEERING
	angle 	+= (key_cur - key_cul)*time_step / 15;
	xOffset 	+= (key_cuu - key_cud)*sin(angle)*time_step*8;
	yOffset 	-= (key_cuu - key_cud)*cos(angle)*time_step*8;
	
	bmap_lock(screen,4444);
	bmap_lock(source,888);
	bmap_lock(distfog,4444);
	bmap_lock(backdrop,888);
	
	for(y = -yres/2; y < yres/2; y++)
	{
		dy = y+yres/2;
		
		//"BLEND" HOLDS THE ALPHAVALUE
		//FOR THE DISTANCE-FOGGING
		blend = ((dy)-60) * -100/110 - 10;
		blend = clamp(blend,-100,0);
		//JUST TO CREATE A NON-LINEAR EFFECT
		//505 WAS DETERMINE BY ME 
		blend &= 505;
		
		//MAKE A SLICE AT 70
		//TO RENDER THE BACKDROP
		if(y >= (-yres/2)+70)
		{
		for(x = -xres/2; x < xres/2; x++)
		{
			dx = x+xres/2;
			
			/******* ACTUAL MODE 7 EFFECT ********/
			
			//ASSIGN SOME TEMPVARS FOR
			//A BETTER OVERVIEW
			px = x; 
			py = y + m7.foclength;
			pz = y + m7.horizon;
			
			//MATHEMATICAL PROJECTION 
			//FOR TURNING A 3D POINT TO
			//A 2D SCREENPIXEL
			//Y STANDS FOR Z RESP. THE DEPTH
			space_x = px / pz;
			space_y = py / pz * -1;
			//INVERT THE Y DIRECTION
			//SO THAT EVERTHING IS 
			//IN A CORRECT WAY
			
			//A TRIGONOMETRIC EQUATION TO BE ABLE
			//TO ROTATE THE BMAP SO YOU CAN 
			//LOOK AROUND 360°
			screen_x = space_x * cos(angle) - space_y * sin(angle);
  			screen_y = space_x * sin(angle) + space_y * cos(angle);
			
			//FINAL TRANSFORMATION AND SCALING
			sx = screen_x * m7.scale + xOffset;
			sy = screen_y * m7.scale + yOffset;
			
			//USE THE AND-OPERATOR TO CREATE AN INFINTIY-PATTERN
			pixel_to_bmap(screen,dx,dy,pixel_for_bmap(source,(int)sx & mask_x,(int)sy & mask_y)+0x001200);
			pixel_to_bmap(distfog,dx,dy,pixel_for_vec(vector(240,254,160),blend,8888));
			
			/***************************************/
		}
		}
		else
		{
		for(x = 0; x < xres; x++)
			pixel_to_bmap(screen,x,dy,pixel_for_bmap(backdrop,(int)(x+angle*200) & mask_x_backdrop,dy));	
		}
	}
	
	//EVERYTIME YOU WANT TO ADD AN OBJECT YOU'VE
	//TO PLACE A NEW MODE_7_SPRITE FUNCTION HERE
	//IT'S PLACED HERE BECAUSE OF THE PERFORMANCE
	mode_7_sprite(obj_01, 10 , -100 );
	
	bmap_unlock(backdrop);
	bmap_unlock(distfog);
	bmap_unlock(source);
	bmap_unlock(screen);
	
	//THE NEXT LINES AREN'T NECESSARY 
	//THESE JUST CREATING AN ANIMATED 
	//WATERSURFACE, YOU CAN DELETE
	//THESE IF YOU LIKE
	
	//*******//
	
	count += (int)4*time_step;
	if(count > 10){count %= 10;frm++;}
	if(frm >= 7)frm = 0;	//if(frm == 7)frm %= 7;	//frm &= 7; 
	
	switch(frm)
	{
		case 0:bmap_purge(source);
		case 1:bmap_load(source,"...",0);
		case 2:bmap_load(source,"...",0);
		case 3:bmap_load(source,"...",0);
		case 4:bmap_load(source,"...",0);
		case 5:bmap_load(source,"...",0);
		case 6:bmap_load(source,"...",0);
		case 7:bmap_load(source,"...",0);
	}
	
	//*******//
	
	wait(1);
	}
}


function mode_7_sprite(PANEL* sprite, int px, int py, float obj_dir, float obj_org)
{
	
	float width, height;
	float space_x, space_y;
	float screen_x, screen_y;
	
	float obj_x = px + xOffset; 
	float obj_y = py - yOffset; 

	//EQUATIONS STANDS IN EVERY FORMULARY
	float distance = sqrt(pow(obj_x,2)+pow(obj_y,2));
	
	space_x = obj_x * cos(angle) - obj_y * sin(angle);
  	space_y = obj_x * sin(angle) + obj_y * cos(angle);

	//SPACE_Y IS THE DEPTH
	//IF YOU WANT TO REPRODUCE THESE EQUATION JUST LOOK
	//FOR "MATHEMATICAL PROJECTION" RESP. "SIMILAR TRIANGLES"
	screen_x = xres/2 + (space_x * m7.foclength) / space_y;
	screen_y = (m7.foclength / space_y) + m7.horizon - 10;
	
	//CALCULATE THE NEW HEIGHT:
	//INVERSELY PROPORTIONAL TO THE
	//DISTANCE
	height	= (m7.scale / distance);
	width		= (m7.scale / distance);
	
	sprite.scale_x = width;
	sprite.scale_y = height;

	sprite.pos_x = screen_x - (width*sprite.size_x/2);
	sprite.pos_y = screen_y + (height*sprite.size_y/2);	

	//JUST A SMALL SOLUTION FOR CULLING 
	//SO YOU DOESN'T SEE THE SPRITE TWICE

	//CONVERT VECTOR TO +-180 ANGLE
	vec_to_angle(obj_dir,vector(space_x,space_y,0));
	vec_to_angle(obj_org,vector(px,py,0));
	
	//CONVERT +-180 ANGLE TO 0...360 ANGLE
	obj_dir = cycle(obj_dir,0,360);
	obj_org = cycle(obj_org,0,360);
	
	//DETERMINE WHETER THE OBJECT IS IN EYESIGHT
	if(obj_dir > (obj_org - 90) && obj_dir < (obj_org + 90))	{	sprite.flags |= VISIBLE;	}
	else																	 	{	sprite.flags &= ~VISIBLE;	}
	return;
}

function main()
{
	level_load("");
	wait(2);
	video_set(xres,yres,16,0);
	
	//CREATE TWO BLANK BMAPS FOR THE SCREEN 
	//AND THE DISTANCE FOGGING (BECAUSE OF
	//THE TRANSPERANTY WE WANT TO USE)
	blank_16 = bmap_createblack(xres, yres, 16);
	blank_32 = bmap_createblack(xres, yres, 32);
	
	framebuffer.bmap = blank_16;
	framedepth.bmap = blank_32;
	
	//fps_max = 20;	
	mode_7_level(blank_16, displace, blank_32, background);
}



So this is what it looks like.


ANIMATED VERSION
DOWNLOAD Mode7_Example

Gruß Tommy
Posted By: ratchet

Re: SNES Mode-7 - 08/13/12 21:43

On PC with 3DGS ? No interest at all, or for fun programming only ? We have polygons, textures, shaders , why going back laugh ?
Perhaps some retro game in Minecraft style , but with racing features ?
Posted By: Error014

Re: SNES Mode-7 - 08/13/12 22:12

Ah, thats neat! laugh

Don't have the time to test it, sadly. I know this is a lot to ask, but I'd love to see a video or animated gif from that nice, relaxing, vacation-like scene you have up there!


Also, while I have no use for something like that at this point, thanks for contributing that! We need more people like you contributing what they've made. laugh

Do you plan on creating a game or something with that? Or was it a pure "just-for-fun"/learning exercise?
Posted By: Tails01

Re: SNES Mode-7 - 08/14/12 00:56

@Error014
yepp, these mode is an sailing-extension for a 2D rpg-game that was allready finished. But now I want to enhance it a little bit more. Because this project was a qualifying examination, I'm not allowed to publish it yet.
your requested animated scene

@ratchet
Quote:
We have polygons, textures, shaders , why going back laugh ?
Perhaps some retro game in Minecraft style , but with racing features ?


I think 2d-games are very much underestimated. The choice of the appearance for a game it's a matter of taste, even if the majority only likes 3dimensional games frown .

there isn't only the feature for racing, you can handle other effects with it too.
think back of street fighter, where this technique was used to
create an sidescroll-parralax, or in any other Jump'n'Run game.


Posted By: Hummel

Re: SNES Mode-7 - 08/14/12 05:29

Quote:
I think 2d-games are very much underestimated. The choice of the appearance for a game it's a matter of taste, even if the majority only likes 3dimensional games .
I think what ratchet wanted say was that you can achieve the same results using modern technology (which is available anyway) at frame rates hundred times higher (srsly) with much less development effort.

EDIT: Nahh, he probably didn't mean that...
Posted By: HeelX

Re: SNES Mode-7 - 08/14/12 12:45

This is great, thanks for sharing. Maybe you could use the power of HLSL shaders to make it faster, on the GPU?
Posted By: rayp

Re: SNES Mode-7 - 08/14/12 14:31

Really cool. Thank you for sharing.
Posted By: Tails01

Re: SNES Mode-7 - 08/14/12 16:07

I already thought there would be nobody how appreciate it.
Posted By: Error014

Re: SNES Mode-7 - 08/14/12 18:04

Hey, thank you for the video! laugh It does remind me a lot of those old, tropical-themed SNES-Mario Kart tracks. Koopa Troopa Beach, or what was it called? laugh


performance-wise, in this line:

Code:
switch(frm)
	{
		case 0:bmap_purge(source);
		case 1:bmap_load(source,"...",0);
		case 2:bmap_load(source,"...",0);
		case 3:bmap_load(source,"...",0);
		case 4:bmap_load(source,"...",0);
		case 5:bmap_load(source,"...",0);
		case 6:bmap_load(source,"...",0);
		case 7:bmap_load(source,"...",0);
	}



Do you actually reload the frame everytime you need it? In this case, I'd rather sacrifice some memory and load all frames in advance, and then just switch to the correct one (for instance, by using a BMAP*-array).

Posted By: Tails01

Re: SNES Mode-7 - 08/14/12 20:40

@Error014 I've implemented your suggestion into action and
sure you're right a BMAP*-array looks neater but it's doesn't speed up the framerate, even if I delete the switch-loop I got no more fps than 44fps.
Posted By: Superku

Re: SNES Mode-7 - 08/14/12 21:15

Although I cannot test it easily myself because of the missing bitmaps, I can see that it works quite well in your youtube video, good job! The reason for the relatively low framerate probably is the use of pixel instructions, they are very slow. As HeelX already has mentioned, a shader would speed up the whole process remarkably.
(And just ignore ratchet's post.)
Posted By: Tails01

Re: SNES Mode-7 - 08/14/12 23:07

Quote:
Although I cannot test it easily myself because of the missing bitmaps


sure I forget about it, so here is the complete folder with script, some bitmaps and a precompiled version of it.

DOWNLOAD "Mode7_Example"
Posted By: HeelX

Re: SNES Mode-7 - 08/15/12 20:06

Thank you very much! Though, I can't compile it with the current A8 version? It says error in 'line 152: 'mode_7_sprite': wrong number/type of parameters
< mode_7_sprite(obj_01,10,-100); >

I love such things, if I find some free time, I'll try to do that with a HLSL shader, just for fun smile
Posted By: Tails01

Re: SNES Mode-7 - 08/15/12 21:05

Code:
function mode_7_sprite(PANEL* sprite, int px, int py, float obj_dir, float obj_org)



I think this line doesn't fit to the new rules of A8
5 parameters but I call the function with 3 arguments
maybe delete the last two parameters and paste the declaration
inside the function. I just own an old A7 extra version so if
someone could check if my conjecture was right that would be
nice so that I can correct this.

Posted By: HeelX

Re: SNES Mode-7 - 08/16/12 11:14

Ah, sorry, didn't knew that.
Posted By: Joozey

Re: SNES Mode-7 - 08/18/12 22:54

Love it! Recently bought a snes and n64. While the n64 has my preference, it is more advanced 3d graphics than the snes. Great to play those awesome games I used to play 10 years ago. Love the lowpolyness.

But I can't download your example. The site doesn't give me a download to a .rar, just to some .exe which I'm not going to use tongue. I think I got the script working by localising the two float arguments, but I am missing the images.

I use A7 pro
Posted By: Tails01

Re: SNES Mode-7 - 08/19/12 17:06

make sure you hit the correct download button it's located on the right side of the page.
Posted By: HeelX

Re: SNES Mode-7 - 09/08/12 22:08

Hi, I made it compatible with A8:

mode7.a8.original.src.20120909.rar

There was a problem with reading and writing the pixels from/to different bitmap formats in A8. I also made all upper case comments to lowercase and translated german comments. For using your technique with shaders, I think a complete rewrite is necessary. I'll see how far I'll come.
© 2024 lite-C Forums