The 3D Story
Your guide to the third dimension.

Introduction

This is the third version of the box demo. It is now rather complex and I highly recomend you to read the comments (after the code) before altering the code too much.
This code has been compiled using Turbo C++ 3.0 from Borland.

BOX3.CPP

#include <conio.h>
#include <math.h>
#include "gfxman2.h"

#define MAXPOLYS 12
#define DIST 256
#define MIDDLEX 160
#define MIDDLEY 100

struct point
{
	double x,y,z;
};

struct poly
{
	point a,b,c,n;
	unsigned char colour;
	double v;
};

point RotPoint(point i,point a);
struct poly RotPoly(struct poly i,point a);

point ProjPoint(point i);
struct poly ProjPoly(struct poly i);

struct poly CalcNormal(struct poly i);

double GetAngle(point a,point b);
double GetVAngle(point a,int &vis);

void DrawPoly(int minx,int miny,int medx,int medy,int maxx,int maxy,unsigned char colour);
void Swap(int &x,int &y);

void main(void);
void precalc(void);

double c[3600],s[3600];

void main(void)
{
	precalc();

	int vis;
	
	struct poly p[]={{{25,-25,25},{-25,25,25},{-25,-25,25},{0,0,0},1}    // Back
			,{{-25,25,25},{25,-25,25},{25,25,25},{0,0,0},1}
			,{{25,-25,-25},{-25,25,-25},{25,25,-25},{0,0,0},1}   // Front
			,{{-25,25,-25},{25,-25,-25},{-25,-25,-25},{0,0,0},1}
			,{{25,25,-25},{25,-25,25},{25,-25,-25},{0,0,0},1}    // Left
			,{{25,-25,25},{25,25,-25},{25,25,25},{0,0,0},1}
			,{{-25,-25,25},{-25,25,-25},{-25,-25,-25},{0,0,0},1} // Right
			,{{-25,25,-25},{-25,-25,25},{-25,25,25},{0,0,0},1}
			,{{-25,25,25},{25,25,-25},{-25,25,-25},{0,0,0},1}    // Top
			,{{25,25,-25},{-25,25,25},{25,25,25},{0,0,0},1}
			,{{25,-25,-25},{-25,-25,25},{-25,-25,-25},{0,0,0},1} // Bottom
			,{{-25,-25,25},{25,-25,-25},{25,-25,25},{0,0,0},1}
			/*,{{,,},{,,},{,,},{,,},}*/
			};
	struct poly rp[MAXPOLYS];
	int depth[MAXPOLYS],vp;
	point angle={10,0,20};
	int a,b;

	for(a=0;a<MAXPOLYS;a++)
		p[a]=CalcNormal(p[a]);

	SetVGA();

	do
	{
		for(a=0;a<MAXPOLYS;a++)
			rp[a]=ProjPoly(p[a]=RotPoly(p[a],angle));

		vp=0;

		for(a=0;a<MAXPOLYS;a++)
		{
			rp[a].v=GetVAngle(rp[a].n,vis);
			if(vis)
				depth[vp++]=a;
		}

		for(a=vp-1;a>=0;a--)
			for(b=0;b<a;b++)
				if(rp[depth[a]].a.z>rp[depth[b]].a.z)
					Swap(depth[a],depth[b]);

		Cls();

		for(a=0;a<vp;a++)
		{
			DrawPoly(rp[depth[a]].a.x,rp[depth[a]].a.y,
				 rp[depth[a]].b.x,rp[depth[a]].b.y,
				 rp[depth[a]].c.x,rp[depth[a]].c.y,
				 (p[depth[a]].colour<<4)-rp[depth[a]].v*15);
		}

		WaitRetrace();
		ScreenCopy();
	}
	while(!kbhit());

	getch();

	SetText();
}

struct poly RotPoly(struct poly i,point a)
{
	struct poly r;

	r.colour=i.colour;
	r.a=RotPoint(i.a,a);
	r.b=RotPoint(i.b,a);
	r.c=RotPoint(i.c,a);
	r.n=RotPoint(i.n,a);

	return r;
}

point RotPoint(point i,point a)
{
	point r;

	r.x=c[(int)a.z]*i.x-s[(int)a.z]*i.y;
	r.y=s[(int)a.z]*i.x+c[(int)a.z]*i.y;
	r.z=i.z;

	i.x=c[(int)a.y]*r.x-s[(int)a.y]*r.z;
	i.y=r.y;
	i.z=s[(int)a.y]*r.x+c[(int)a.y]*r.z;

	r.x=i.x;
	r.y=s[(int)a.x]*i.z+c[(int)a.x]*i.y;
	r.z=c[(int)a.x]*i.z-s[(int)a.x]*i.y;

	return r;
}

struct poly ProjPoly(struct poly i)
{
	struct poly r;

	r.n=i.n;
	r.colour=i.colour;
	r.a=ProjPoint(i.a);
	r.b=ProjPoint(i.b);
	r.c=ProjPoint(i.c);

	if(r.b.z>r.a.z)
		r.a.z=r.b.z;

	if(r.c.z>r.a.z)
		r.a.z=r.c.z;

	return r;
}

point ProjPoint(point i)
{
	double t;
	point r;

	t=DIST+i.z;

	r.z=i.z;

	if(t)
	{
		r.x=i.x*DIST/t+MIDDLEX;
		r.y=i.y*DIST/t+MIDDLEY;
	}
	else
	{
		r.x=i.x+MIDDLEX;
		r.y=i.y+MIDDLEY;
	}

	return r;
}

void DrawPoly(int minx,int miny,int medx,int medy,int maxx,int maxy,unsigned char colour)
{
	if(medy>maxy)
	{
		Swap(maxy,medy);
		Swap(maxx,medx);
	}

	if(miny>medy)
	{
		Swap(miny,medy);
		Swap(minx,medx);
	}

	if(medy>maxy)
	{
		Swap(medy,maxy);
		Swap(medx,maxx);
	}

	int lx,sx;
	float temp;

	for(int y=miny;y<=maxy;y++)
	{
		temp=(maxy-miny);
		temp=(temp==0)?1:temp;
		lx=minx+((maxx-minx)/temp)*(y-miny);

		if(y<medy)
		{
			temp=(medy-miny);
			temp=(temp==0)?1:temp;
			sx=minx+((medx-minx)/temp)*(y-miny);
		}
		else
		{
			temp=(maxy-medy);
			temp=(temp==0)?1:temp;
			sx=medx+((maxx-medx)/temp)*(y-medy);
		}

		Line(lx,y,sx,y,colour);
	}
}

void Swap(int &x,int &y)
{
	int temp=x;
	x=y;
	y=temp;
}

struct poly CalcNormal(struct poly i)
{
	double ax,ay,az,bx,by,bz;
	struct poly r=i;

	ax=i.a.x-i.c.x;
	ay=i.a.y-i.c.y;
	az=i.a.z-i.c.z;
	bx=i.b.x-i.c.x;
	by=i.b.y-i.c.y;
	bz=i.b.z-i.c.z;

	r.n.x=ay*bz-az*by;
	r.n.y=bx*az-ax*bz;
	r.n.z=ax*by-bx*ay;

	return r;
}

double GetAngle(point a,point b)
{
	double v;

	v=(a.x*b.x+a.y*b.y+a.z*b.z)/(sqrt(a.x*a.x+a.y*a.y+a.z*a.z)*sqrt(b.x*b.x+b.y*b.y+b.z*b.z));

	return v;
}

double GetVAngle(point a,int &vis)
{
	double v;

	v=a.z/sqrt(a.x*a.x+a.y*a.y+a.z*a.z);

	if(v<0)
		vis=-1;
	else
		vis=0;

	return v;
}

void precalc(void)
{
	for(int a=0;a<3600;a++)
	{
		s[a]=sin((a/10.0)*(M_PI/180));
		c[a]=cos((a/10.0)*(M_PI/180));
	}
}

Comments

The commenting will be made in a slightly different way than before. First I'll go through how the engine works, then all the functions used by the program will be dealed with individually, after that, some rules about creating objects will be discussed.

How does the engine work?

This is mainly how the engine works : (The right column is the used function)
	Precalculate sin and cos tables.				    PreCalc()
	Setup the memory with all the object data.
	Calculate the normal vectors for all surfaces.			    CalcNormal()
	DO
		Rotate and project all surfaces.			    RotPoly()/ProjPoly()
		Add all visible surfaces to a stack.			    GetVAngle()
		Sort the stack according to each surface's maximum Z-value.
		Clear the virtual screen.				    Cls()
		Draw the polygons to the virtual screen.		    DrawPoly()
		Copy the virtual screen to the video memory.		    ScreenCopy()
	LOOP until any key is pressed.
	Clear keyboard buffer.
This is how the engine works. The commands within the DO - LOOP commands are made for each frame.

The Functions

point RotPoint(point i,point a)
Rotates the point i according to the values recieved through a.
struct poly RotPoly(struct poly i,point a)
Rotates an entire polygon (i) using several calls to Rotpoint().
point ProjPoint(point i)
Projects a point (i) and returns the projected X- and Y-values and the untouched Z-value.
struct poly ProjPoly(struct poly i)
Projects an enitire polygon (i) and returns a polygon. This polygon contains the projected X- and Y-values, and the .a.z variable contains the maximum Z-value.
struct poly CalcNormal(struct poly i)
Calculates the normal vector for a polygon (i).
double GetAngle(point a,point b)
Returns the angle between two 3D vectors (a and b);
double GetVAngle(point a,int &vis)
Returns the angle to the viewpoint, and a variable (vis) which is non-zero if the input vector (a) is facing the viewpoint, i.e. the object is visible if this function is feed with the normal vector.
void DrawPoly(int minx,int miny,int medx,int medy,int maxx,int maxy,unsigned char colour)
Draws a polygon onto the virtual screen.
void Swap(int &x,int &y)
Swaps values between two integers.
void precalc(void)
Precalculates the sin and cos tables.

Creating Objects

When creating an object, you'll need to know one or two things.
What is an object made of?
Each object is made of one or more triangle. The number of triangles are stored in the constant MAXPOLYS. Each triangle is the defined in the p[] variable. It is stored in the following way :

The point A, The point B, The point C, The normal vector (usually (0,0,0)), The colour (0 to 15)

To use the colour you'll need to setup the palette, which we haven't done yet. Now the colour 1 is providing a grey-scale, and that is all.
Determing the direction the surface is facing
Just use your hand! Take you left hand and put your palm where C is. Then point your thumb at A and the other fingers at B. The normal vector now comes out of your palm.



[ Previous | Main Page | Next ]
Copyright©1997 Johan E. Thelin