Commit 99de9c6b authored by Scott Duensing's avatar Scott Duensing

Backface removal, solid drawing, and basic lighting.

parent d2fd766c
......@@ -49,11 +49,17 @@ segment "j3d";
// Module global data
static jint16 clipMinX = 0;
static jint16 clipMinY = 0;
static jint16 clipMaxX = 319;
static jint16 clipMaxY = 199;
static jint16 viewDistance = 250;
static jint16 clipMinX = 0;
static jint16 clipMinY = 0;
static jint16 clipMinZ = 100;
static jint16 clipMaxX = 319;
static jint16 clipMaxY = 199;
static jint16 clipMaxZ = 3000;
static jint16 viewDistance = 250;
static float ambientLight = 6;
static j3VertexT cameraLocation;
static j3FacingT cameraAngle;
static j3VertexT sunLocation;
#define ASPECT_RATIO (float)0.8
......@@ -63,6 +69,280 @@ static jint16 viewDistance = 250;
#define HALF_SCREEN_HEIGHT 100
void _j3DrawSolid(j3ObjectT *o) {
jint16 t;
juint16 vertex1;
juint16 vertex2;
juint16 vertex3;
float x1;
float y1;
float z1;
float x2;
float y2;
float z2;
float x3;
float y3;
float z3;
for (t=0; t<o->triangleCount; t++) {
// Is this triangle even visible?
if (!o->triangles[t].visible) {
continue;
}
vertex1 = o->triangles[t].index[0];
vertex2 = o->triangles[t].index[1];
vertex3 = o->triangles[t].index[2];
z1 = o->verticies[vertex1].camera.z;
z2 = o->verticies[vertex2].camera.z;
z3 = o->verticies[vertex3].camera.z;
// Perform z clipping
if ((z1 < clipMinZ && z2 < clipMinZ && z3 < clipMinZ) || (z1 > clipMaxZ && z2 > clipMaxZ && z3 > clipMaxZ)) {
continue;
}
// Extract points of triangle
x1 = o->verticies[vertex1].camera.x;
y1 = o->verticies[vertex1].camera.y;
x2 = o->verticies[vertex2].camera.x;
y2 = o->verticies[vertex2].camera.y;
x3 = o->verticies[vertex3].camera.x;
y3 = o->verticies[vertex3].camera.y;
// Screen position of points
x1 = HALF_SCREEN_WIDTH + x1 * viewDistance / z1;
y1 = HALF_SCREEN_HEIGHT - ASPECT_RATIO * y1 * viewDistance / z1;
x2 = HALF_SCREEN_WIDTH + x2 * viewDistance / z2;
y2 = HALF_SCREEN_HEIGHT - ASPECT_RATIO * y2 * viewDistance / z2;
x3 = HALF_SCREEN_WIDTH + x3 * viewDistance / z3;
y3 = HALF_SCREEN_HEIGHT - ASPECT_RATIO * y3 * viewDistance / z3;
j3DrawTriangle2D((jint16)x1, (jint16)y1, (jint16)x2, (jint16)y2, (jint16)x3, (jint16)y3, o->triangles[t].shade);
}
}
void j3DrawTriangle2D(jint16 x1, jint16 y1, jint16 x2,jint16 y2, jint16 x3, jint16 y3, jint16 color) {
jint16 tempX;
jint16 tempY;
jint16 newX;
// Horizontal or vertical lines?
if ((x1 == x2 && x2 == x3) || (y1 == y2 && y2 == y3)) {
return;
}
// Sort points in ascending Y order
if (y2 < y1) {
tempX = x2;
tempY = y2;
x2 = x1;
y2 = y1;
x1 = tempX;
y1 = tempY;
}
// p1 and p2 are now in order
if (y3 < y1) {
tempX = x3;
tempY = y3;
x3 = x1;
y3 = y1;
x1 = tempX;
y1 = tempY;
}
// Finally check y3 against y2
if (y3 < y2) {
tempX = x3;
tempY = y3;
x3 = x2;
y3 = y2;
x2 = tempX;
y2 = tempY;
} // end if
// Trivial rejection tests
if (y3 < clipMinY || y1 > clipMaxY || (x1 < clipMinX && x2 < clipMinX && x3 < clipMinX) || (x1 > clipMaxX && x2 > clipMaxX && x3 > clipMaxX)) {
return;
}
jlDrawColor((byte)color);
// Is top of triangle flat?
if (y1 == y2) {
_j3DrawTriangleTop(x1, y1, x2, y2, x3, y3);
} else if (y2 == y3) {
_j3DrawTriangleBottom(x1, y1, x2, y2, x3, y3);
} else {
// General triangle that's needs to be broken up along long edge
newX = x1 + (jint16)((float)(y2 - y1) * (float)(x3 - x1) / (float)(y3 - y1));
// Draw each sub-triangle
_j3DrawTriangleBottom(x1, y1, newX, y2, x2, y2);
_j3DrawTriangleTop(x2, y2, newX, y2, x3, y3);
}
}
void _j3DrawTriangleBottom(jint16 x1, jint16 y1, jint16 x2,jint16 y2, jint16 x3, jint16 y3) {
float dxRight;
float dxLeft;
float xs;
float xe;
float height;
jint16 tempX;
jint16 tempY;
jint16 right;
jint16 left;
(void)y2;
// Test order of x1 and x2
if (x3 < x2) {
tempX = x2;
x2 = x3;
x3 = tempX;
}
// Compute deltas
height = y3 - y1;
dxLeft = (x2 - x1) / height;
dxRight = (x3 - x1) / height;
// Set starting points
xs = (float)x1;
xe = (float)x1 + (float)0.5;
// Perform y clipping
if (y1 < clipMinY) {
// Compute new xs and ys
xs = xs + dxLeft * (float)(-y1 + clipMinY);
xe = xe + dxRight * (float)(-y1 + clipMinY);
// Reset y1
y1 = clipMinY;
}
if (y3 > clipMaxY) {
y3 = clipMaxY;
}
// Test if x clipping is needed
if (x1 >= clipMinX && x1 <= clipMaxX && x2 >= clipMinX && x2 <= clipMaxX && x3 >= clipMinX && x3 <= clipMaxX) {
// Draw the triangle
for (tempY=y1; tempY<=y3; tempY++) {
jlDrawLine((jint16)xs, tempY, (jint16)xe, tempY);
// Adjust starting point and ending point
xs += dxLeft;
xe += dxRight;
}
} else {
// Clip x axis with slower version
for (tempY=y1; tempY<=y3; tempY++) {
// Do x clip
left = (jint16)xs;
right = (jint16)xe;
// Adjust starting point and ending point
xs += dxLeft;
xe += dxRight;
// Clip line
if (left < clipMinX) {
left = clipMinX;
if (right < clipMinX) {
continue;
}
}
if (right > clipMaxX) {
right = clipMaxX;
if (left > clipMaxX) {
continue;
}
}
// Draw
jlDrawLine(left, tempY, right, tempY);
}
}
}
void _j3DrawTriangleTop(jint16 x1, jint16 y1, jint16 x2,jint16 y2, jint16 x3, jint16 y3) {
float dxRight;
float dxLeft;
float xs;
float xe;
float height;
jint16 tempX;
jint16 tempY;
jint16 right;
jint16 left;
(void)y2;
// Test order of x1 and x2
if (x2 < x1) {
tempX = x2;
x2 = x1;
x1 = tempX;
}
// Compute deltas
height = y3 - y1;
dxLeft = (x3 - x1) / height;
dxRight = (x3 - x2) / height;
// Set starting points
xs = (float)x1;
xe = (float)x2 + (float)0.5;
// Perform y clipping
if (y1 < clipMinY) {
// Compute new xs and ys
xs = xs + dxLeft * (float)(-y1 + clipMinY);
xe = xe + dxRight * (float)(-y1 + clipMinY);
// Reset y1
y1 = clipMinY;
}
if (y3 > clipMaxY) {
y3=clipMaxY;
}
// Test if x clipping is needed
if (x1 >= clipMinX && x1 <= clipMaxX && x2 >= clipMinX && x2 <= clipMaxX && x3 >= clipMinX && x3 <= clipMaxX) {
// Draw the triangle
for (tempY=y1; tempY<=y3; tempY++) {
jlDrawLine((jint16)xs, tempY, (jint16)xe, tempY);
// Adjust starting point and ending point
xs += dxLeft;
xe += dxRight;
}
} else {
// Clip x axis
for (tempY=y1; tempY<=y3; tempY++) {
// Do x clip
left = (jint16)xs;
right = (jint16)xe;
// Adjust starting point and ending point
xs += dxLeft;
xe += dxRight;
// Clip line
if (left < clipMinX) {
left = clipMinX;
if (right < clipMinX) {
continue;
}
}
if (right > clipMaxX) {
right = clipMaxX;
if (left > clipMaxX) {
continue;
}
}
// Draw
jlDrawLine((jint16)left, tempY, (jint16)right, tempY);
}
}
}
void _j3DrawWireframePair(j3ObjectT *o, juint16 v1, juint16 v2) {
float x1;
float y1;
......@@ -114,6 +394,20 @@ void _j3DrawWireframe(j3ObjectT *o) {
}
void j3MathCrossProduct3D(j3Vector3DT *u, j3Vector3DT *v, j3Vector3DT *normal) {
// Compute the cross product between two vectors
normal->x = (u->y*v->z - u->z*v->y);
normal->y = -(u->x*v->z - u->z*v->x);
normal->z = (u->x*v->y - u->y*v->x);
}
float j3MathDotProduct3D(j3Vector3DT *u, j3Vector3DT *v) {
// Compute the dot product of two vectors
return( (u->x * v->x) + (u->y * v->y) + (u->z * v->z));
}
void j3MathMatrix4x4Identity(j3Matrix4x4T result) {
result[0][0] = 1; result[0][1] = 0; result[0][2] = 0; result[0][3] = 0;
result[1][0] = 0; result[1][1] = 1; result[1][2] = 0; result[1][3] = 0;
......@@ -145,6 +439,20 @@ void j3MathMatrix4x4Mult(j3Matrix4x4T a, j3Matrix4x4T b, j3Matrix4x4T result) {
}
void j3MathMakeVector3D(j3VertexT *init, j3VertexT *term, j3Vector3DT *result) {
// Create a vector from two points in 3D space
result->x = term->x - init->x;
result->y = term->y - init->y;
result->z = term->z - init->z;
}
float j3MathVectorMagnatude3D(j3Vector3DT *v) {
// Compute the magnitude of a vector
return((float)sqrt((double)(v->x * v->x + v->y * v->y + v->z * v->z)));
}
void _j3ObjectReset(j3ObjectT *o) {
o->position.x = 0;
o->position.y = 0;
......@@ -165,7 +473,16 @@ void _j3ObjectUpdate(j3ObjectT *o) {
jint16 y;
jint16 z;
jint16 i;
byte axis = 0;
juint16 vertex0;
juint16 vertex1;
juint16 vertex2;
float dot;
float intensity;
j3Vector3DT u;
j3Vector3DT v;
j3Vector3DT normal;
j3Vector3DT sight;
byte axis = 0;
j3Matrix4x4T rotateX;
j3Matrix4x4T rotateY;
j3Matrix4x4T rotateZ;
......@@ -326,6 +643,103 @@ void _j3ObjectUpdate(j3ObjectT *o) {
for (i=0; i<o->vertexCount; i++) {
o->verticies[i].camera = o->verticies[i].world;
}
// === REMOVE BACKFACES & LIGHT ===
for (i=0; i<o->triangleCount; i++) {
// If it's two sided, there are no backfaces
if (!o->triangles[i].twoSided) {
// Compute two vectors on polygon that have the same intial points
vertex0 = o->triangles[i].index[0];
vertex1 = o->triangles[i].index[1];
vertex2 = o->triangles[i].index[2];
// Vector u = v0->v1
j3MathMakeVector3D((j3VertexT *)&o->verticies[vertex0].world, (j3VertexT *)&o->verticies[vertex1].world, (j3Vector3DT *)&u);
// Vector v = v0->v2
j3MathMakeVector3D((j3VertexT *)&o->verticies[vertex0].world, (j3VertexT *)&o->verticies[vertex2].world, (j3Vector3DT *)&v);
// Normal v x u
j3MathCrossProduct3D((j3Vector3DT *)&v, (j3Vector3DT *)&u, (j3Vector3DT *)&normal);
// Compute the line of sight vector, since all coordinates are world all
// object vertices are already relative to (0,0,0), thus
sight.x = cameraLocation.x - o->verticies[vertex0].world.x;
sight.y = cameraLocation.y - o->verticies[vertex0].world.y;
sight.z = cameraLocation.z - o->verticies[vertex0].world.z;
// Compute the dot product between line of sight vector and normal to surface
dot = j3MathDotProduct3D((j3Vector3DT *)&normal, (j3Vector3DT *)&sight);
// Is the surface visible
if (dot > 0) {
// Set visible flag
o->triangles[i].visible = true;
// Compute light intensity if needed
if (o->triangles[i].lit) {
// Compute the dot product between the light source vector and normal vector to surface
dot = j3MathDotProduct3D((j3Vector3DT *)&normal, (j3Vector3DT *)&sunLocation);
// Test if light ray is reflecting off surface
if (dot > 0) {
intensity = ambientLight + (dot * (o->triangles[i].normalLength));
// Test if intensity has overflowed
if (intensity > 15) {
intensity = 15;
}
// Intensity now varies from 0-1, 0 being black or grazing and 1 being
// totally illuminated. use the value to index into color table
o->triangles[i].shade = o->triangles[i].color - (jint16)intensity;
} else {
o->triangles[i].shade = o->triangles[i].color - (jint16)ambientLight;
}
} else {
// Constant shading - simply assign color to shade
o->triangles[i].shade = o->triangles[i].color;
}
} else {
o->triangles[i].visible = false;
}
} else {
// Triangle is always visible i.e. two sided
o->triangles[i].visible = true;
// Perform shading calculation
if (o->triangles[i].lit) {
// Compute two vectors on polygon that have the same intial points
vertex0 = o->triangles[i].index[0];
vertex1 = o->triangles[i].index[1];
vertex2 = o->triangles[i].index[2];
// Vector u = v0->v1
j3MathMakeVector3D((j3VertexT *)&o->verticies[vertex0].world, (j3VertexT *)&o->verticies[vertex1].world, (j3Vector3DT *)&u);
// Vector v = v0->v2
j3MathMakeVector3D((j3VertexT *)&o->verticies[vertex0].world, (j3VertexT *)&o->verticies[vertex2].world, (j3Vector3DT *)&v);
// Normal v x u
j3MathCrossProduct3D((j3Vector3DT *)&v, (j3Vector3DT *)&u, (j3Vector3DT *)&normal);
// Compute the dot product between the light source vector and normal vector to surface
dot = j3MathDotProduct3D((j3Vector3DT *)&normal, (j3Vector3DT *)&sunLocation);
// Is light ray is reflecting off surface
if (dot > 0) {
// cos 0 = (u.v)/|u||v| or
intensity = ambientLight + (dot * (o->triangles[i].normalLength));
// test if intensity has overflowed
if (intensity > 15) {
intensity = 15;
}
// intensity now varies from 0-1, 0 being black or grazing and 1 being
// totally illuminated. use the value to index into color table
o->triangles[i].shade = o->triangles[i].color - (jint16)intensity;
} else {
o->triangles[i].shade = o->triangles[i].color - (jint16)ambientLight;
}
} else {
// Constant shading and simply assign color to shade
o->triangles[i].shade = o->triangles[i].color;
}
}
}
}
......@@ -524,7 +938,17 @@ void j3UtilShutdown(void) {
void j3UtilStartup(void) {
// Nothing yet
cameraLocation.x = 0;
cameraLocation.y = 0;
cameraLocation.z = 0;
cameraAngle.x = 0;
cameraAngle.y = 0;
cameraAngle.z = 0;
sunLocation.x = (float)-0.913913;
sunLocation.y = (float) 0.389759;
sunLocation.z = (float)-0.113369;
}
......@@ -552,6 +976,12 @@ bool _j3WorldLoad(j3WorldT **world, char *file) {
jint16 x;
jint16 y;
jint16 z;
juint16 vertex0;
juint16 vertex1;
juint16 vertex2;
j3Vector3DT u;
j3Vector3DT v;
j3Vector3DT normal;
byte buffer[4];
FILE *in;
bool failed = false;
......@@ -585,21 +1015,19 @@ bool _j3WorldLoad(j3WorldT **world, char *file) {
if (fread(&(*world)->objectCount, sizeof(juint16), 1, in) == 1) {
// Allocate memory for objects
(*world)->objects = (j3ObjectT *)jlMalloc(sizeof(j3ObjectT));
(*world)->objects = (j3ObjectT *)jlMalloc(sizeof(j3ObjectT) * (*world)->objectCount);
if ((*world)->objects) {
// Iterate across objects in file
for (x=0; x<(*world)->objectCount; x++) {
// Allocate memory for object elements
(*world)->objects->verticies = (j3CoordinatesT *)jlMalloc(sizeof(j3CoordinatesT));
(*world)->objects->triangles = (j3TriangleT *)jlMalloc(sizeof(j3TriangleT));
if ((*world)->objects->verticies && (*world)->objects->triangles) {
// Get vertex count
if (fread(&(*world)->objects[x].vertexCount, sizeof(juint16), 1, in) == 1) {
_j3ObjectReset(&(*world)->objects[x]);
// Allocate memory for vertex data
(*world)->objects->verticies = (j3CoordinatesT *)jlMalloc(sizeof(j3CoordinatesT) * (*world)->objects[x].vertexCount);
if ((*world)->objects->verticies) {
// Get vertex count
if (fread(&(*world)->objects[x].vertexCount, sizeof(juint16), 1, in) == 1) {
// Iterate and read verticies
for (y=0; y<(*world)->objects[x].vertexCount; y++) {
// Read one at a time in case the struct gets padded
......@@ -618,9 +1046,19 @@ bool _j3WorldLoad(j3WorldT **world, char *file) {
}
}
if (!failed) {
// Get triangle count
if (fread(&(*world)->objects[x].triangleCount, sizeof(juint16), 1, in) == 1) {
} else {
// Failed to get vertex memory
failed = true;
}
if (!failed) {
// Get triangle count
if (fread(&(*world)->objects[x].triangleCount, sizeof(juint16), 1, in) == 1) {
// Allocate memory for triangle data
(*world)->objects->triangles = (j3TriangleT *)jlMalloc(sizeof(j3TriangleT) * (*world)->objects[x].triangleCount);
if ((*world)->objects->triangles) {
// Iterate and read triangles
for (y=0; y<(*world)->objects[x].triangleCount; y++) {
// Read one at a time in case the struct gets padded
......@@ -631,11 +1069,38 @@ bool _j3WorldLoad(j3WorldT **world, char *file) {
}
}
if (failed) break;
// Compute length of the two co-planer edges of the polygon, since they will be used in the computation of the dot-product later
vertex0 = (*world)->objects[x].triangles[y].index[0];
vertex1 = (*world)->objects[x].triangles[y].index[1];
vertex2 = (*world)->objects[x].triangles[y].index[2];
j3MathMakeVector3D((j3VertexT *)&(*world)->objects[x].verticies[vertex0].local, (j3VertexT *)&(*world)->objects[x].verticies[vertex1].local, (j3Vector3DT *)&u);
j3MathMakeVector3D((j3VertexT *)&(*world)->objects[x].verticies[vertex0].local, (j3VertexT *)&(*world)->objects[x].verticies[vertex2].local, (j3Vector3DT *)&v);
j3MathCrossProduct3D((j3Vector3DT *)&v, (j3Vector3DT *)&u, (j3Vector3DT *)&normal);
// Compute magnitude of normal, take its inverse and multiply it by
// 15, this will change the shading calculation of 15*dp/normal into
// dp*normal_length, removing one division
(*world)->objects[x].triangles[y].normalLength = (float)15.0 / j3MathVectorMagnatude3D((j3Vector3DT *)&normal);
//***TODO*** All triangles are one-sided for now
(*world)->objects[x].triangles[y].twoSided = false;
}
} else {
// Failed to get triangle memory
failed = true;
}
}
}
if (!failed) {
_j3ObjectReset(&(*world)->objects[x]);
} else {
break; // Stop iterating
}
} // vertex & triangle alloc
} // object iterator
} // objects alloc
} // Object count
......
......@@ -27,13 +27,22 @@
#include "joey.h"
//***TODO*** verticies is vertices
typedef float j3Matrix4x4T[4][4];
typedef struct {
jint16 x;
jint16 y;
jint16 z;
} j3FacingT;
typedef struct {
float x;
float y;
float z;
} j3VertexT;
} j3VertexT, j3Vector3DT;
typedef struct {
j3VertexT local; // Original vertex positions
......@@ -42,7 +51,13 @@ typedef struct {
} j3CoordinatesT;
typedef struct {
juint16 index[3]; // We do this instead of just a typedef so it works with stretch_buffer
jint16 color; // Assigned color of this face
jint16 shade; // Color of this face after lighting
bool lit; // Do we care about lighting?
bool visible; // Can we see this triangle?
bool twoSided; // Are both sides visible?
float normalLength; // Magnatude of surface normal
juint16 index[3]; // We do this instead of just a typedef so it works with stretch_buffer
} j3TriangleT;
typedef struct {
......@@ -107,6 +122,7 @@ typedef struct {
// Syntatic sugar
#define j3DrawSolid(o) _j3DrawSolid(&(o))
#define j3DrawWireframe(o) _j3DrawWireframe(&(o))
#define j3ObjectReset(o) _j3ObjectReset(&(o))
#define j3ObjectUpdate(o) _j3ObjectUpdate(&(o))
......@@ -115,13 +131,21 @@ typedef struct {
// Prototypes
void j3MathMatrix4x4Identity(j3Matrix4x4T result);
void j3MathMatrix4x4Mult(j3Matrix4x4T a, j3Matrix4x4T b, j3Matrix4x4T result);
void j3UtilShutdown(void);
void j3UtilStartup(void);
void j3DrawTriangle2D(jint16 x1, jint16 y1, jint16 x2,jint16 y2, jint16 x3, jint16 y3, jint16 color);
void j3MathCrossProduct3D(j3Vector3DT *u, j3Vector3DT *v, j3Vector3DT *normal);
float j3MathDotProduct3D(j3Vector3DT *u, j3Vector3DT *v);
void j3MathMatrix4x4Identity(j3Matrix4x4T result);
void j3MathMatrix4x4Mult(j3Matrix4x4T a, j3Matrix4x4T b, j3Matrix4x4T result);
void j3MathMakeVector3D(j3VertexT *init, j3VertexT *term, j3Vector3DT *result);
float j3MathVectorMagnatude3D(j3Vector3DT *v);
void j3UtilShutdown(void);
void j3UtilStartup(void);
// Private Prototypes
void _j3DrawSolid(j3ObjectT *o);
void _j3DrawTriangleBottom(jint16 x1, jint16 y1, jint16 x2,jint16 y2, jint16 x3, jint16 y3);
void _j3DrawTriangleTop(jint16 x1, jint16 y1, jint16 x2,jint16 y2, jint16 x3, jint16 y3);
void _j3DrawWireframePair(j3ObjectT *o, juint16 v1, juint16 v2);
void _j3DrawWireframe(j3ObjectT *o);
void _j3ObjectReset(j3ObjectT *o);
......
......@@ -59,6 +59,8 @@ void printAt(jlStaT *font, jint16 cx, jint16 cy, const char *what, ...) {
int main(void) {
jint16 x;
jint16 y;
jint16 c;
jlStaT *font = NULL;
j3WorldT *world = NULL;
bool r;
......@@ -81,8 +83,18 @@ int main(void) {
printAt(font, 1, 1, "Object loading: Success!");
jlDisplayPresent();
// Assign fake colors until we can read them from the J3D
c = 1;
for (x=0; x<world->objectCount; x++) {
for (y=0; y<world->objects[x].triangleCount; y++) {
world->objects[x].triangles->color = c++;
if (c > 15) {
c = 1;
}
}
}
#ifdef JOEY_LINUX
jint16 y;
printf("Objects: %d\n", world->objectCount);
for (x=0; x<world->objectCount; x++) {
printf("Object %d:\n", x);
......@@ -121,7 +133,8 @@ int main(void) {
for (x=0; x<world->objectCount; x++) {