Sign in to follow this  
kyc

Need help with 3D transformations

Recommended Posts

Really pathetic that I can't seem to get this working right, but I'd appreciate anyone helping me out. None of the transformations seem quite right. After awhile rotations cause the entire cube to get scaled smaller. Win32 binary and SDL sources are here. Below are the relevant source files. draw.cpp

#include "kee3d.h"
#include "globals.h"

void Draw()
{
	SDL_FillRect(_screen, 0, SDL_MapRGB(_screen->format, 100, 100, 100));

	Lock(_screen);

	_box.Draw();
	
	Unlock(_screen);
	
	SDL_Flip(_screen);
}

void InitData()
{	
	{
		Vector front1(-50, -50, 50), front2(50, -50, 50), front3(50, 50, 50), front4(-50, 50, 50);
		Vector left1(-50, -50, -50), left2(-50, -50, 50), left3(-50, 50, 50), left4(-50, 50, -50);
		Vector right1(50, -50, 50), right2(50, -50, -50), right3(50, 50, -50), right4(50, 50, 50);
		Vector back1(50, -50, -50), back2(-50, -50, -50), back3(-50, 50, -50), back4(50, 50, -50);
		
		_box.AddFace(front1, front2, front3, front4, Color(0, 255, 0, 255));
		_box.AddFace(left1, left2, left3, left4, Color(255, 255, 0, 255));
		_box.AddFace(right1, right2, right3, right4, Color(0, 255, 255, 255));
		_box.AddFace(back1, back2, back3, back4, Color(255, 0, 0, 255));
	}
}



Object.cpp

#include "Object.h"
#include "globals.h"

#include <cmath>
using namespace std;

Face::Face(Vector a_, Vector b_, Vector c_, Vector d_, Color color_)
: color(color_)
{
	vertices.push_back(a_);
	vertices.push_back(b_);
	vertices.push_back(c_);
	vertices.push_back(d_);
}
	
void Face::Draw(Matrix worldSpace_, Matrix localSpace_)
{
	vector<Vector> vs;
	for(unsigned int i = 0; i < vertices.size(); ++i)
	{
		//Vector p = Multiply(vertices[i], localSpace_);
		vertices[i] = Multiply(vertices[i], localSpace_);
		Vector p = vertices[i];
		
		p = Multiply(p, worldSpace_);
		vs.push_back(p);
	}
	{
		line(_screen, vs[0], vs[1], color);
		line(_screen, vs[1], vs[2], color);
		line(_screen, vs[2], vs[3], color);
		line(_screen, vs[3], vs[0], color);
	}
}

void Object::Rotate(float x_, float y_, float z_)
{
	float r = 3.14f / 180.0f;
	
	float rx = r*x_, ry = r*y_, rz = r*z_;
	
	Matrix x, y, z;
	Identity(x);
	Identity(y);
	Identity(z);
	{
		float cx = cosf(rx), sx = sinf(rx);
		x[1][1] = cx;
		x[1][2] = sx;
		x[2][1] = -sx;
		x[2][2] = cx;
		
		float cy = cosf(ry), sy = sinf(ry);
		y[0][0] = cy;
		y[0][2] = -sy;
		y[2][0] = sy;
		y[2][2] = cy;
		
		float cz = cosf(rz), sz = sinf(rz);
		z[0][0] = cz;
		z[0][1] = sz;
		z[1][0] = -sz;
		z[1][1] = cz;
	}
	Multiply(localSpace, x);
	Multiply(localSpace, y);
	Multiply(localSpace, z);
}

void Object::Translate(float x_, float y_, float z_)
{
	Matrix m;
	Identity(m);
	m[3][0] = x_;
	m[3][1] = y_;
	m[3][2] = z_;

	Multiply(worldSpace, m);
}

void Object::AddFace(Vector a_, Vector b_, Vector c_, Vector d_, Color color_)
{
	faces.push_back(Face(a_, b_, c_, d_, color_));
}

void Object::Draw()
{
	for(unsigned int n = 0; n < faces.size(); ++n)
		faces[n].Draw(worldSpace, localSpace);
	
	//Identity(localSpace);
}

Object::Object()
{
	Identity(worldSpace);
	Identity(localSpace);
}



globals.cpp

#include "globals.h"

Object _box;
Vector _camera(0, 0, 256);//



kee3d.cpp

#include "kee3d.h"
#include "globals.h"

//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
bool _running = true;

SDL_Surface *_screen = 0;
int _screenX = 640, _screenY = 480, _bpp = 32;

Uint8 *_keys = 0;

Uint32 _oldTime, _currentTime;
//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@

void triangle(SDL_Surface *surface_, Vector a_, Vector b_, Vector c_, Color color_)
{
	Vector temp;
	
	Vector ap = project(a_), bp = project(b_), cp = project(c_);
	Vector a = ap, b = bp, c = cp;
	if(a.y < b.y)
	{
		temp = b;
		b = a;
		a = temp;
	}
	if(a.y < c.y)
	{
		temp = c;
		c = a;
		a = temp;
	}
	if(b.y < c.y)
	{
		temp = c;
		c = b;
		b = temp;
	}
	
	////////////////////////////

	if(a.y == b.y)
		flatTop(surface_, c, a, b, color_);
	else if(b.y == c.y)
		flatBottom(surface_, a, b, c, color_);
	else
	{
		Vector p((b.y-a.y)/((a.y-c.y)/(a.x-c.x)) + a.x, b.y);
		
		flatTop(surface_, c, p, b, color_);
		flatBottom(surface_, a, b, p, color_);
	}
}

void flatTop(SDL_Surface *surface_, Vector c_, Vector a_, Vector b_, Color color_)
{
	float m1 = (c_-b_).Slope();
	float m2 = (c_-a_).Slope();
	if(m1 == 0 || m2 == 0)
		return;
	
	float y = c_.y;
	while(y <= b_.y)
	{
		Vector c1((y-c_.y)/m1 + c_.x, y);
		Vector c2((y-c_.y)/m2 + c_.x, y);
		
		line(surface_, c1, c2, color_, false);
		y += 0.25f;
	}
	//line(surface_, a_, b_, color_, false);
}

void flatBottom(SDL_Surface *surface_, Vector a_, Vector b_, Vector c_, Color color_)
{
	float m1 = (a_-b_).Slope();
	float m2 = (a_-c_).Slope();
	if(m1 == 0 || m2 == 0)
		return;
	
	float y = a_.y;
	while(y >= b_.y)
	{
		Vector c1((y-a_.y)/m1 + a_.x, y);
		Vector c2((y-a_.y)/m2 + a_.x, y);
		
		line(surface_, c1, c2, color_, false);
		y -= 0.25f;
	}
	//line(surface_, b_, c_, color_, false);
}

void line(SDL_Surface *surface_, Vector p0_, Vector p1_, Color c_, bool project_)
{	
	Vector p0 = p0_, p1 = p1_;
	if(project_)
	{
		p0 = project(p0);
		p1 = project(p1);
	}
	
	Vector line = p1 - p0, unit = line.Unit();
	float length = line.Length();
	
	Vector p = p0;
	while( (p - p0).Length() < length )
	{
		p += unit;
		
		pixel(surface_, p, c_);
	}
}

Vector project(Vector v_)
{
	
	Vector v;
	float div = v_.z + _camera.z;// + _camera.z;
	
	if(div == 0)
		return v;

	v.x = v_.x * 256 / div;
	v.y = v_.y * 256 / div;
	
	//v.x = v_.x;
	//v.y = v_.y;

	return v;
}

// Lock surface before calling this!
void pixel(SDL_Surface *surface, Vector p_, Color c_)
{
	int x = (int)(p_.x), y = (int)(p_.y);
	
	x += _screenX/2;
	y = _screenY/2 - y;
	
	if(!(x>0 && x<_screenX && y>0 && y<_screenY))
		return;

	Uint32 pixel = SDL_MapRGBA(surface->format, c_.r, c_.g, c_.b, c_.a);
	
    int bpp = surface->format->BytesPerPixel;
    /* Here p is the address to the pixel we want to set */
    Uint8 *p = (Uint8 *)surface->pixels + y * surface->pitch + x * bpp;

    switch(bpp) {
    case 1:
        *p = pixel;
        break;

    case 2:
        *(Uint16 *)p = pixel;
        break;

    case 3:
        if(SDL_BYTEORDER == SDL_BIG_ENDIAN) {
            p[0] = (pixel >> 16) & 0xff;
            p[1] = (pixel >> 8) & 0xff;
            p[2] = pixel & 0xff;
        } else {
            p[0] = pixel & 0xff;
            p[1] = (pixel >> 8) & 0xff;
            p[2] = (pixel >> 16) & 0xff;
        }
        break;

    case 4:
        *(Uint32 *)p = pixel;
        break;
    }
}

void Lock(SDL_Surface *surface_)
{
	if(SDL_MUSTLOCK(surface_))
		SDL_LockSurface(surface_);
}

void Unlock(SDL_Surface *surface_)
{
	if(SDL_MUSTLOCK(surface_))
		SDL_UnlockSurface(surface_);
}



Share this post


Link to post
Share on other sites
Ah, yes. I've had this problem before (with a cube, too)!
I didn;t actually figure out what was causing it precisely, but I did fix it. I think it may have been floating point errors? I was using a very similar way of transforming objects.

What usually happens with my code now is that the model or object has its orginal vertices stored, and these are *never* changed by rotations, scaling etc. Then, when you transform the vertices in any way, you stored the transformed vertices in a new array. This takes perhaps more memory, but it seems to work, or at least in the systems I have been using.

Doing things this way requires that you keep one transformation matrix for the object, which transforms the object's vertices from object space into world space. It is only modified when you transform the object, and then the transformed vertices are calculated from these.

It looks like it may also be more efficient than the current system as you only calculate the transformed vertices when you need to.

I have tried to explain this properly, if you have any question my email address is lee@shotmonkey.com

Share this post


Link to post
Share on other sites
Thanks for your help, and sorry for such a late reply.

I just want to close this thread by saying I have since found the solution. It is a floating-point precision error, and I fixed it by multiplying matrices in sequence like so:



void Multiply(Matrix a_, Matrix b_, Matrix destination_);

void Object::Rotate(float alpha_, float beta_, float gamma_)
{
Matrix x, y, z;
...setup x rotation matrix, as well as those of y and z.

Matrix temp1, temp2;
Identity(temp1);
Identity(temp2);

// I doubt order of axes makes a difference,
// but that's how it was in an example.
// localSpace is the local transformation matrix of the object.
Multiply(localSpace, y, temp1);
Multiply(temp1, x, temp2);
Multiply(temp2, z, localSpace);
}




Apparently putting things in a temp buffer helps to alleviate the precision errors, or it could be a matter of not being able to make consecutive multiplications. In any case, this solution works and now I'm further on my road to understanding software rendering.

I hope someone can learn from this because the articles I've seen on the web do not address this issue clearly, if at all.


Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this