joeylib3d/j3d/j3d.c

1329 lines
39 KiB
C

/*
* JoeyLib 3D
* Copyright (C) 2019 Scott Duensing <scott@kangaroopunch.com>
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
* arising from the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
*/
// The code in this file is heavily influenced by
// "Black Art of 3D Game Programming" by Andre LaMothe
// Waite Group Press - ISBN 1-57169-004-2
// https://archive.org/details/BlackArt3DEBook
//***TODO***
// Allow multiple objects to be loaded into a world
// Store original vertex data so we can do local rotations and save them
// Rename verticies to vertices
// Remove _ from sin_table and cos_table
#include <math.h>
#include <stdio.h>
#include <float.h>
#include <string.h>
#include "j3d.h"
#include "j3dtbls.h"
#ifdef JOEY_IIGS
segment "j3d";
#define M_PI 3.1415926
#define fabsf fabs
#else
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wpadded"
#endif
typedef int (*j3qsortCastT)(const void *, const void *);
int _j3PolyCompare(j3PolyListT *arg1, j3PolyListT *arg2);
// Module global data
jint16 _j3VarClipMinX = 0;
jint16 _j3VarClipMinY = 0;
jint16 _j3VarClipMinZ = 100;
jint16 _j3VarClipMaxX = 319;
jint16 _j3VarClipMaxY = 199;
jint16 _j3VarClipMaxZ = 3000;
jint16 _j3VarViewDistance = 200;
float _j3VarAmbientLight = 6;
j3Matrix4x4T _j3VarCameraMatrix;
j3VertexT _j3VarCameraLocation;
j3FacingT _j3VarCameraAngle;
bool _j3VarCameraLocationDirty = true;
bool _j3VarCameraAngleDirty = true;
j3VertexT _j3VarSunLocation;
//***TODO*** None of this should be global - put inside world
j3PolyListT *_j3Polygons = NULL;
juint16 _j3PolygonCount = 0; // Current visible polygons
juint16 _j3TriangleCount = 0; // Current triangles in world
#define ASPECT_RATIO (float)0.8
#define INVERSE_ASPECT_RATIO (float)1.25
#define HALF_SCREEN_WIDTH 160
#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 < _j3VarClipMinZ && z2 < _j3VarClipMinZ && z3 < _j3VarClipMinZ) || (z1 > _j3VarClipMaxZ && z2 > _j3VarClipMaxZ && z3 > _j3VarClipMaxZ)) {
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 * _j3VarViewDistance / z1;
y1 = HALF_SCREEN_HEIGHT - ASPECT_RATIO * y1 * _j3VarViewDistance / z1;
x2 = HALF_SCREEN_WIDTH + x2 * _j3VarViewDistance / z2;
y2 = HALF_SCREEN_HEIGHT - ASPECT_RATIO * y2 * _j3VarViewDistance / z2;
x3 = HALF_SCREEN_WIDTH + x3 * _j3VarViewDistance / z3;
y3 = HALF_SCREEN_HEIGHT - ASPECT_RATIO * y3 * _j3VarViewDistance / 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 < _j3VarClipMinY || y1 > _j3VarClipMaxY || (x1 < _j3VarClipMinX && x2 < _j3VarClipMinX && x3 < _j3VarClipMinX) || (x1 > _j3VarClipMaxX && x2 > _j3VarClipMaxX && x3 > _j3VarClipMaxX)) {
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 < _j3VarClipMinY) {
// Compute new xs and ys
xs = xs + dxLeft * (float)(-y1 + _j3VarClipMinY);
xe = xe + dxRight * (float)(-y1 + _j3VarClipMinY);
// Reset y1
y1 = _j3VarClipMinY;
}
if (y3 > _j3VarClipMaxY) {
y3 = _j3VarClipMaxY;
}
// Test if x clipping is needed
if (x1 >= _j3VarClipMinX && x1 <= _j3VarClipMaxX && x2 >= _j3VarClipMinX && x2 <= _j3VarClipMaxX && x3 >= _j3VarClipMinX && x3 <= _j3VarClipMaxX) {
// 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 < _j3VarClipMinX) {
left = _j3VarClipMinX;
if (right < _j3VarClipMinX) {
continue;
}
}
if (right > _j3VarClipMaxX) {
right = _j3VarClipMaxX;
if (left > _j3VarClipMaxX) {
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 < _j3VarClipMinY) {
// Compute new xs and ys
xs = xs + dxLeft * (float)(-y1 + _j3VarClipMinY);
xe = xe + dxRight * (float)(-y1 + _j3VarClipMinY);
// Reset y1
y1 = _j3VarClipMinY;
}
if (y3 > _j3VarClipMaxY) {
y3=_j3VarClipMaxY;
}
// Test if x clipping is needed
if (x1 >= _j3VarClipMinX && x1 <= _j3VarClipMaxX && x2 >= _j3VarClipMinX && x2 <= _j3VarClipMaxX && x3 >= _j3VarClipMinX && x3 <= _j3VarClipMaxX) {
// 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 < _j3VarClipMinX) {
left = _j3VarClipMinX;
if (right < _j3VarClipMinX) {
continue;
}
}
if (right > _j3VarClipMaxX) {
right = _j3VarClipMaxX;
if (left > _j3VarClipMaxX) {
continue;
}
}
// Draw
jlDrawLine((jint16)left, tempY, (jint16)right, tempY);
}
}
}
void _j3DrawWireframePair(j3ObjectT *o, juint16 v1, juint16 v2) {
float x1;
float y1;
float z1;
float x2;
float y2;
float z2;
jint16 ix1;
jint16 iy1;
jint16 ix2;
jint16 iy2;
j3VertexT v;
v = o->verticies[v1].camera;
x1 = v.x;
y1 = v.y;
z1 = v.z;
v = o->verticies[v2].camera;
x2 = v.x;
y2 = v.y;
z2 = v.z;
x1 = HALF_SCREEN_WIDTH + x1 * _j3VarViewDistance / z1;
y1 = HALF_SCREEN_HEIGHT - ASPECT_RATIO * y1 * _j3VarViewDistance / z1;
x2 = HALF_SCREEN_WIDTH + x2 * _j3VarViewDistance / z2;
y2 = HALF_SCREEN_HEIGHT - ASPECT_RATIO * y2 * _j3VarViewDistance / z2;
ix1 = (jint16)x1;
iy1 = (jint16)y1;
ix2 = (jint16)x2;
iy2 = (jint16)y2;
if (_j3UtilClipLine(&ix1, &iy1, &ix2, &iy2)) {
jlDrawLine(ix1, iy1, ix2, iy2);
}
}
void _j3DrawWireframe(j3ObjectT *o) {
jint16 t; // Current triangle
for (t=0; t<o->triangleCount; t++) {
_j3DrawWireframePair(o, o->triangles[t].index[0], o->triangles[t].index[1]);
_j3DrawWireframePair(o, o->triangles[t].index[1], o->triangles[t].index[2]);
_j3DrawWireframePair(o, o->triangles[t].index[2], o->triangles[t].index[0]);
}
}
void j3DrawWorld(j3WorldT *w) {
jint16 i;
juint16 vertex1;
juint16 vertex2;
juint16 vertex3;
float x1;
float y1;
float z1;
float x2;
float y2;
float z2;
float x3;
float y3;
float z3;
j3ObjectT *o;
j3PolyListT *p;
//***TODO*** Fix
(void)w;
for (i=0; i<_j3PolygonCount; i++) {
// Dereference
p = &_j3Polygons[i];
o = p->object;
vertex1 = o->triangles[p->triangleNumber].index[0];
vertex2 = o->triangles[p->triangleNumber].index[1];
vertex3 = o->triangles[p->triangleNumber].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 < _j3VarClipMinZ && z2 < _j3VarClipMinZ && z3 < _j3VarClipMinZ) || (z1 > _j3VarClipMaxZ && z2 > _j3VarClipMaxZ && z3 > _j3VarClipMaxZ)) {
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 * _j3VarViewDistance / z1;
y1 = HALF_SCREEN_HEIGHT - ASPECT_RATIO * y1 * _j3VarViewDistance / z1;
x2 = HALF_SCREEN_WIDTH + x2 * _j3VarViewDistance / z2;
y2 = HALF_SCREEN_HEIGHT - ASPECT_RATIO * y2 * _j3VarViewDistance / z2;
x3 = HALF_SCREEN_WIDTH + x3 * _j3VarViewDistance / z3;
y3 = HALF_SCREEN_HEIGHT - ASPECT_RATIO * y3 * _j3VarViewDistance / z3;
j3DrawTriangle2D((jint16)x1, (jint16)y1, (jint16)x2, (jint16)y2, (jint16)x3, (jint16)y3, o->triangles[p->triangleNumber].shade);
}
}
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;
result[2][0] = 0; result[2][1] = 0; result[2][2] = 1; result[2][3] = 0;
result[3][0] = 0; result[3][1] = 0; result[3][2] = 0; result[3][3] = 1;
}
void j3MathMatrix4x4Mult(j3Matrix4x4T a, j3Matrix4x4T b, j3Matrix4x4T result) {
result[0][0] = a[0][0] * b[0][0] + a[0][1] * b[1][0] + a[0][2] * b[2][0];
result[0][1] = a[0][0] * b[0][1] + a[0][1] * b[1][1] + a[0][2] * b[2][1];
result[0][2] = a[0][0] * b[0][2] + a[0][1] * b[1][2] + a[0][2] * b[2][2];
result[0][3] = 0;
result[1][0] = a[1][0] * b[0][0] + a[1][1] * b[1][0] + a[1][2] * b[2][0];
result[1][1] = a[1][0] * b[0][1] + a[1][1] * b[1][1] + a[1][2] * b[2][1];
result[1][2] = a[1][0] * b[0][2] + a[1][1] * b[1][2] + a[1][2] * b[2][2];
result[1][3] = 0;
result[2][0] = a[2][0] * b[0][0] + a[2][1] * b[1][0] + a[2][2] * b[2][0];
result[2][1] = a[2][0] * b[0][1] + a[2][1] * b[1][1] + a[2][2] * b[2][1];
result[2][2] = a[2][0] * b[0][2] + a[2][1] * b[1][2] + a[2][2] * b[2][2];
result[2][3] = 0;
result[3][0] = a[3][0] * b[0][0] + a[3][1] * b[1][0] + a[3][2] * b[2][0] + b[3][0];
result[3][1] = a[3][0] * b[0][1] + a[3][1] * b[1][1] + a[3][2] * b[2][1] + b[3][1];
result[3][2] = a[3][0] * b[0][2] + a[3][1] * b[1][2] + a[3][2] * b[2][2] + b[3][2];
result[3][3] = 1;
}
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;
o->position.z = 0;
o->rotation.x = 0;
o->rotation.y = 0;
o->rotation.z = 0;
o->scale.x = 1;
o->scale.y = 1;
o->scale.z = 1;
o->positionDirty = true;
o->rotationDirty = true;
o->scaleDirty = true;
}
void _j3ObjectUpdate(j3ObjectT *o) {
jint16 x;
jint16 y;
jint16 z;
jint16 i;
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;
j3Matrix4x4T final;
j3Matrix4x4T temp;
j3Matrix4x4T result1;
j3Matrix4x4T result2;
j3Matrix4x4T translate;
// === ROTATION ===
if (o->rotationDirty) {
// Rotation being dirty means we also need to update position later
o->positionDirty = true;
x = (jint16)o->rotation.x;
y = (jint16)o->rotation.y;
z = (jint16)o->rotation.z;
j3MathMatrix4x4Identity(final);
// What angles are we rotating on? By knowing this we can optimize some.
if (x) axis += 4;
if (y) axis += 2;
if (z) axis += 1;
switch (axis) {
case 1: // Final matrix = z
final[0][0] = cos_table[z];
final[0][1] = sin_table[z];
final[1][0] = -sin_table[z];
final[1][1] = cos_table[z];
for (i=0; i<o->vertexCount; i++) {
o->verticies[i].world.x = o->verticies[i].local.x * final[0][0] + o->verticies[i].local.y * final[1][0];
o->verticies[i].world.y = o->verticies[i].local.x * final[0][1] + o->verticies[i].local.y * final[1][1];
o->verticies[i].world.z = o->verticies[i].local.z;
}
break;
case 2: // Final matrix = y
final[0][0] = cos_table[y];
final[0][2] = -sin_table[y];
final[2][0] = sin_table[y];
final[2][2] = cos_table[y];
for (i=0; i<o->vertexCount; i++) {
o->verticies[i].world.x = o->verticies[i].local.x * final[0][0] + o->verticies[i].local.z * final[2][0];
o->verticies[i].world.y = o->verticies[i].local.y;
o->verticies[i].world.z = o->verticies[i].local.x * final[0][2] + o->verticies[i].local.z * final[2][2];
}
break;
case 3: // Final matrix = y * z
final[0][0] = cos_table[y] * cos_table[z];
final[0][1] = cos_table[y] * sin_table[z];
final[0][2] = -sin_table[y];
final[1][0] = -sin_table[z];
final[1][1] = cos_table[z];
final[2][0] = sin_table[y] * cos_table[z];
final[2][1] = sin_table[y] * sin_table[z];
final[2][2] = cos_table[y];
for (i=0; i<o->vertexCount; i++) {
o->verticies[i].world.x = o->verticies[i].local.x * final[0][0] + o->verticies[i].local.y * final[1][0] + o->verticies[i].local.z * final[2][0];
o->verticies[i].world.y = o->verticies[i].local.x * final[0][1] + o->verticies[i].local.y * final[1][1] + o->verticies[i].local.z * final[2][1];
o->verticies[i].world.z = o->verticies[i].local.x * final[0][2] + o->verticies[i].local.z * final[2][2];
}
break;
case 4: // Final matrix = x
final[1][1] = cos_table[x];
final[1][2] = sin_table[x];
final[2][1] = -sin_table[x];
final[2][2] = cos_table[x];
for (i=0; i<o->vertexCount; i++) {
o->verticies[i].world.x = o->verticies[i].local.x;
o->verticies[i].world.y = o->verticies[i].local.y * final[1][1] + o->verticies[i].local.z * final[2][1];
o->verticies[i].world.z = o->verticies[i].local.y * final[1][2] + o->verticies[i].local.z * final[2][2];
}
break;
case 5: // Final matrix = x * z
final[0][0] = cos_table[z];
final[0][1] = sin_table[z];
final[1][0] = -cos_table[x]*sin_table[z];
final[1][1] = cos_table[x]*cos_table[z];
final[1][2] = sin_table[x];
final[2][0] = sin_table[x]*sin_table[z];
final[2][1] = -sin_table[x]*cos_table[z];
final[2][2] = cos_table[x];
for (i=0; i<o->vertexCount; i++) {
o->verticies[i].world.x = o->verticies[i].local.x * final[0][0] + o->verticies[i].local.y * final[1][0] + o->verticies[i].local.z * final[2][0];
o->verticies[i].world.y = o->verticies[i].local.x * final[0][1] + o->verticies[i].local.y * final[1][1] + o->verticies[i].local.z * final[2][1];
o->verticies[i].world.z = o->verticies[i].local.y * final[1][2] + o->verticies[i].local.z * final[2][2];
}
break;
case 6: // Final matrix = x * y
final[0][0] = cos_table[y];
final[0][2] = -sin_table[y];
final[1][0] = sin_table[x] * sin_table[y];
final[1][1] = cos_table[x];
final[1][2] = sin_table[x] * cos_table[y];
final[2][0] = cos_table[x] * sin_table[y];
final[2][1] = -sin_table[x];
final[2][2] = cos_table[x] * cos_table[y];
for (i=0; i<o->vertexCount; i++) {
o->verticies[i].world.x = o->verticies[i].local.x * final[0][0] + o->verticies[i].local.y * final[1][0] + o->verticies[i].local.z * final[2][0];
o->verticies[i].world.y = o->verticies[i].local.y * final[1][1] + o->verticies[i].local.z * final[2][1];
o->verticies[i].world.z = o->verticies[i].local.x * final[0][2] + o->verticies[i].local.y * final[1][2] + o->verticies[i].local.z * final[2][2];
}
break;
case 7: // Final matrix = x * y * z
j3MathMatrix4x4Identity(rotateX);
rotateX[1][1] = cos_table[x];
rotateX[1][2] = sin_table[x];
rotateX[2][1] = -sin_table[x];
rotateX[2][2] = cos_table[x];
j3MathMatrix4x4Identity(rotateY);
rotateY[0][0] = cos_table[y];
rotateY[0][2] = -sin_table[y];
rotateY[2][0] = sin_table[y];
rotateY[2][2] = cos_table[y];
j3MathMatrix4x4Identity(rotateZ);
rotateZ[0][0] = cos_table[z];
rotateZ[0][1] = sin_table[z];
rotateZ[1][0] = -sin_table[z];
rotateZ[1][1] = cos_table[z];
j3MathMatrix4x4Mult(rotateX, rotateZ, temp);
j3MathMatrix4x4Mult(temp, rotateZ, final);
for (i=0; i<o->vertexCount; i++) {
o->verticies[i].world.x = o->verticies[i].local.x * final[0][0] + o->verticies[i].local.y * final[1][0] + o->verticies[i].local.z * final[2][0];
o->verticies[i].world.y = o->verticies[i].local.x * final[0][1] + o->verticies[i].local.y * final[1][1] + o->verticies[i].local.z * final[2][1];
o->verticies[i].world.z = o->verticies[i].local.x * final[0][2] + o->verticies[i].local.y * final[1][2] + o->verticies[i].local.z * final[2][2];
}
break;
default:
break;
}
}
// === SCALE & TRANSLATION ===
if (o->positionDirty || o->scaleDirty) {
for (i=0; i<o->vertexCount; i++) {
o->verticies[i].world.x = o->verticies[i].world.x * o->scale.x + o->position.x;
o->verticies[i].world.y = o->verticies[i].world.y * o->scale.y + o->position.y;
o->verticies[i].world.z = o->verticies[i].world.z * o->scale.z + o->position.z;
}
}
// === CAMERA SPACE ===
//***TODO*** Move this when we add multiple object stuff and re-do this function
if (_j3VarCameraLocationDirty || _j3VarCameraAngleDirty) {
// Create the global inverse transformation matrix used to transform world coordinate to camera coordinates
j3MathMatrix4x4Identity(translate);
j3MathMatrix4x4Identity(rotateX);
j3MathMatrix4x4Identity(rotateY);
j3MathMatrix4x4Identity(rotateZ);
translate[3][0] = -_j3VarCameraLocation.x;
translate[3][1] = -_j3VarCameraLocation.y;
translate[3][2] = -_j3VarCameraLocation.z;
// Z matrix
rotateX[1][1] = ( cos_table[_j3VarCameraAngle.x]);
rotateX[1][2] = -( sin_table[_j3VarCameraAngle.x]);
rotateX[2][1] = -(-sin_table[_j3VarCameraAngle.x]);
rotateX[2][2] = ( cos_table[_j3VarCameraAngle.x]);
// Y matrix
rotateY[0][0] = ( cos_table[_j3VarCameraAngle.y]);
rotateY[0][2] = -(-sin_table[_j3VarCameraAngle.y]);
rotateY[2][0] = -( sin_table[_j3VarCameraAngle.y]);
rotateY[2][2] = ( cos_table[_j3VarCameraAngle.y]);
// Z matrix
rotateZ[0][0] = ( cos_table[_j3VarCameraAngle.z]);
rotateZ[0][1] = -( sin_table[_j3VarCameraAngle.z]);
rotateZ[1][0] = -(-sin_table[_j3VarCameraAngle.z]);
rotateZ[1][1] = ( cos_table[_j3VarCameraAngle.z]);
j3MathMatrix4x4Mult(translate, rotateX, result1);
j3MathMatrix4x4Mult(result1, rotateY, result2);
j3MathMatrix4x4Mult(result2, rotateZ, _j3VarCameraMatrix);
_j3VarCameraLocationDirty = false;
_j3VarCameraAngleDirty = false;
}
if (o->positionDirty || o->rotationDirty) {
for (i=0; i<o->vertexCount; i++) {
o->verticies[i].camera.x =
o->verticies[i].world.x * _j3VarCameraMatrix[0][0] +
o->verticies[i].world.y * _j3VarCameraMatrix[1][0] +
o->verticies[i].world.z * _j3VarCameraMatrix[2][0] + _j3VarCameraMatrix[3][0];
o->verticies[i].camera.y =
o->verticies[i].world.x * _j3VarCameraMatrix[0][1] +
o->verticies[i].world.y * _j3VarCameraMatrix[1][1] +
o->verticies[i].world.z * _j3VarCameraMatrix[2][1] + _j3VarCameraMatrix[3][1];
o->verticies[i].camera.z =
o->verticies[i].world.x * _j3VarCameraMatrix[0][2] +
o->verticies[i].world.y * _j3VarCameraMatrix[1][2] +
o->verticies[i].world.z * _j3VarCameraMatrix[2][2] + _j3VarCameraMatrix[3][2];
}
}
// === REMOVE BACKFACES & LIGHT ===
for (i=0; i<o->triangleCount; i++) {
// If the scale changed, we need a new normal length
if (o->scaleDirty) {
_j3ObjectUpdateNormalLength(o, (juint16)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 = _j3VarCameraLocation.x - o->verticies[vertex0].world.x;
sight.y = _j3VarCameraLocation.y - o->verticies[vertex0].world.y;
sight.z = _j3VarCameraLocation.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 *)&_j3VarSunLocation);
// Test if light ray is reflecting off surface
if (dot > 0) {
intensity = _j3VarAmbientLight + (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)_j3VarAmbientLight;
}
} 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 *)&_j3VarSunLocation);
// Is light ray is reflecting off surface
if (dot > 0) {
// cos 0 = (u.v)/|u||v| or
intensity = _j3VarAmbientLight + (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)_j3VarAmbientLight;
}
} else {
// Constant shading and simply assign color to shade
o->triangles[i].shade = o->triangles[i].color;
}
}
// Did this triangle survive culling?
if (o->triangles[i].visible) {
// Find average z depth in camera space
vertex0 = o->triangles[i].index[0];
vertex1 = o->triangles[i].index[1];
vertex2 = o->triangles[i].index[2];
o->triangles[i].averageDepth = (float)0.3333333 * (o->verticies[vertex0].camera.z + o->verticies[vertex1].camera.z + o->verticies[vertex2].camera.z);
// Add to visible polygon list
_j3Polygons[_j3PolygonCount].object = o;
_j3Polygons[_j3PolygonCount].triangleNumber = (juint16)i;
_j3PolygonCount++;
}
} // for i
// Z Sort the visible polygon list
qsort((void *)_j3Polygons, _j3PolygonCount, sizeof(j3PolyListT), (j3qsortCastT)_j3PolyCompare);
o->positionDirty = false;
o->rotationDirty = false;
o->scaleDirty = false;
}
void _j3ObjectUpdateNormalLength(j3ObjectT *object, juint16 triangle) {
juint16 vertex0;
juint16 vertex1;
juint16 vertex2;
j3Vector3DT u;
j3Vector3DT v;
j3Vector3DT normal;
// 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 = object->triangles[triangle].index[0];
vertex1 = object->triangles[triangle].index[1];
vertex2 = object->triangles[triangle].index[2];
j3MathMakeVector3D((j3VertexT *)&object->verticies[vertex0].local, (j3VertexT *)&object->verticies[vertex1].local, (j3Vector3DT *)&u);
j3MathMakeVector3D((j3VertexT *)&object->verticies[vertex0].local, (j3VertexT *)&object->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
object->triangles[triangle].normalLength = (float)15.0 / j3MathVectorMagnatude3D((j3Vector3DT *)&normal);
}
int _j3PolyCompare(j3PolyListT *arg1, j3PolyListT *arg2) {
float z1;
float z2;
z1 = arg1->object->triangles[arg1->triangleNumber].averageDepth;
z2 = arg2->object->triangles[arg2->triangleNumber].averageDepth;
if (z1 > z2) {
return(-1);
} else if (z1 < z2) {
return(1);
} else {
return(0);
}
}
bool _j3UtilClipLine(jint16 *x1, jint16 *y1, jint16 *x2, jint16 *y2) {
jint16 xi;
jint16 yi;
float dx;
float dy;
bool point1 = false; // End points visible?
bool point2 = false;
bool rightEdge = false; // Which edges are the endpoints beyond?
bool leftEdge = false;
bool topEdge = false;
bool bottomEdge = false;
bool success = false; // Did we successfully clip this line?
// Is the line completely visible?
point1 = ((*x1 >= _j3VarClipMinX) && (*x1 <= _j3VarClipMaxX) && (*y1 >= _j3VarClipMinY) && (*y1 <= _j3VarClipMaxY));
point2 = ((*x2 >= _j3VarClipMinX) && (*x2 <= _j3VarClipMaxX) && (*y2 >= _j3VarClipMinY) && (*y2 <= _j3VarClipMaxY));
if (point1 && point2) {
return(true);
}
// Is the line is completely invisible?
if (!point1 && !point2) {
// Test to see if each endpoint is on the same side of one of
// the bounding planes created by each clipping region boundary
if (((*x1<_j3VarClipMinX) && (*x2<_j3VarClipMinX)) || // left
((*x1>_j3VarClipMaxX) && (*x2>_j3VarClipMaxX)) || // right
((*y1<_j3VarClipMinY) && (*y2<_j3VarClipMinY)) || // above
((*y1>_j3VarClipMaxY) && (*y2>_j3VarClipMaxY))) { // below
// No need to draw line
return(false);
}
// if we got here we have the special case where the line cuts into and
// out of the clipping region
//return(false);
}
// Either endpoint is in clipping region
if (point1 || (!point1 && !point2)) {
// Find deltas
dx = *x2 - *x1;
dy = *y2 - *y1;
// Find which boundary line needs to be clipped against
if (*x2 > _j3VarClipMaxX) {
// Flag right edge
rightEdge = true;
// Find intersection with right edge
if (fabsf(dx) > FLT_EPSILON) { // dx != 0
yi = (jint16)((float)0.5 + (dy / dx) * (_j3VarClipMaxX - *x1) + *y1);
} else {
yi = -1; // Invalidate intersection
}
} else if (*x2 < _j3VarClipMinX) {
// Flag left edge
leftEdge = true;
// Find intersection with left edge
if (fabsf(dx) > FLT_EPSILON) { // dx != 0
yi = (jint16)((float)0.5 + (dy / dx) * (_j3VarClipMinX - *x1) + *y1);
} else {
yi = -1; // Invalidate intersection
}
}
if (*y2 > _j3VarClipMaxY) {
// Flag bottom edge
bottomEdge = true;
// Find intersection with right edge
if (fabsf(dy) > FLT_EPSILON) { // dy != 0
xi = (jint16)((float)0.5 + (dx / dy) * (_j3VarClipMaxY - *y1) + *x1);
} else {
xi = -1; // Invalidate inntersection
}
} else if (*y2 < _j3VarClipMinY) {
// Flag top edge
topEdge = true;
// Find intersection with top edge
if (fabsf(dy) > FLT_EPSILON) { // dy != 0
xi = (jint16)((float)0.5 + (dx / dy) * (_j3VarClipMinY - *y1) + *x1);
} else {
xi = -1; // Invalidate intersection
}
}
// We know where the line passed through
// FInd which edge is the proper intersection
if (rightEdge && (yi >= _j3VarClipMinY && yi <= _j3VarClipMaxY)) {
*x2 = _j3VarClipMaxX;
*y2 = yi;
success = true;
} else if (leftEdge && (yi >= _j3VarClipMinY && yi <= _j3VarClipMaxY)) {
*x2 = _j3VarClipMinX;
*y2 = yi;
success = true;
}
if (bottomEdge && (xi >= _j3VarClipMinX && xi <= _j3VarClipMaxX)) {
*x2 = xi;
*y2 = _j3VarClipMaxY;
success = true;
} else if (topEdge && (xi >= _j3VarClipMinX && xi <= _j3VarClipMaxX)) {
*x2 = xi;
*y2 = _j3VarClipMinY;
success = true;
}
}
// Reset edge flags
rightEdge = false;
leftEdge = false;
topEdge = false;
bottomEdge = false;
// Test second endpoint
if (point2 || (!point1 && !point2)) {
// Find deltas
dx = *x1 - *x2;
dy = *y1 - *y2;
// Find which boundary line needs to be clipped against
if (*x1 > _j3VarClipMaxX) {
// Flag right edge
rightEdge = true;
// Find intersection with right edge
if (fabsf(dx) > FLT_EPSILON) { // dx != 0
yi = (jint16)((float)0.5 + (dy / dx) * (_j3VarClipMaxX - *x2) + *y2);
} else {
yi = -1; // Invalidate inntersection
}
} else if (*x1 < _j3VarClipMinX) {
// Flag left edge
leftEdge = true;
// Find intersection with left edge
if (fabsf(dx) > FLT_EPSILON) { // dx != 0
yi = (jint16)((float)0.5 + (dy / dx) * (_j3VarClipMinX - *x2) + *y2);
} else {
yi = -1; // Invalidate intersection
}
}
if (*y1 > _j3VarClipMaxY) {
// Flag bottom edge
bottomEdge = true;
// Find intersection with right edge
if (fabsf(dy) > FLT_EPSILON) { // dy != 0
xi = (jint16)((float)0.5 + (dx / dy) * (_j3VarClipMaxY - *y2) + *x2);
} else {
xi = -1; // invalidate inntersection
}
} else if (*y1 < _j3VarClipMinY) {
// Flag top edge
topEdge = true;
// Find intersection with top edge
if (fabsf(dy) > FLT_EPSILON) { // dy != 0
xi = (jint16)((float)0.5 + (dx / dy) * (_j3VarClipMinY - *y2) + *x2);
} else {
xi = -1; // invalidate inntersection
}
}
// We know where the line passed through
// Find which edge is the proper intersection
if (rightEdge && (yi >= _j3VarClipMinY && yi <= _j3VarClipMaxY)) {
*x1 = _j3VarClipMaxX;
*y1 = yi;
success = true;
} else if (leftEdge && (yi >= _j3VarClipMinY && yi <= _j3VarClipMaxY)) {
*x1 = _j3VarClipMinX;
*y1 = yi;
success = true;
}
if (bottomEdge && (xi >= _j3VarClipMinX && xi <= _j3VarClipMaxX)) {
*x1 = xi;
*y1 = _j3VarClipMaxY;
success = true;
} else if (topEdge && (xi >= _j3VarClipMinX && xi <= _j3VarClipMaxX)) {
*x1 = xi;
*y1 = _j3VarClipMinY;
success = true;
}
}
return(success);
}
void j3UtilShutdown(void) {
// Free visible polygon list
if (_j3Polygons != NULL) {
jlFree(_j3Polygons);
}
}
void j3UtilStartup(void) {
_j3VarCameraLocation.x = 0;
_j3VarCameraLocation.y = 0;
_j3VarCameraLocation.z = 0;
_j3VarCameraAngle.x = 0;
_j3VarCameraAngle.y = 0;
_j3VarCameraAngle.z = 0;
_j3VarSunLocation.x = (float)-0.913913;
_j3VarSunLocation.y = (float) 0.389759;
_j3VarSunLocation.z = (float)-0.113369;
}
void _j3WorldFree(j3WorldT **world) {
int x;
if ((*world)) {
for (x=0; x<(*world)->objectCount; x++) {
if ((*world)->objects[x].triangles) {
jlFree((*world)->objects[x].triangles);
}
if ((*world)->objects[x].verticies) {
jlFree((*world)->objects[x].verticies);
}
}
if ((*world)->objects) {
jlFree((*world)->objects);
}
jlFree(*world);
}
}
bool _j3WorldLoad(j3WorldT **world, char *file) {
juint16 x;
juint16 y;
juint16 z;
byte buffer[4];
FILE *in;
bool failed = false;
in = fopen(jlUtilMakePathname(file, "j3d"), "rb");
// Did we find the file?
if (in == NULL) {
// Nope.
return false;
}
// Do we have a world?
if (*world != NULL) {
// Free this one
j3WorldFree(world);
}
// Allocate world object
*world = (j3WorldT *)jlMalloc(sizeof(j3WorldT));
if (*world) {
// Initialize world object & create an initial object to read data into
(*world)->objectCount = 0;;
(*world)->objects = NULL;
// Is this a valid world file?
if (fread(buffer, sizeof(byte), 4, in) == 4) {
if ((buffer[0] == 'J') && (buffer[1] == '3') && (buffer[2] == 'D') && (buffer[3] <= 0)) {
// Get object count
if (fread(&(*world)->objectCount, sizeof(juint16), 1, in) == 1) {
// Allocate memory for objects
(*world)->objects = (j3ObjectT *)jlMalloc(sizeof(j3ObjectT) * (*world)->objectCount);
if ((*world)->objects) {
// Iterate across objects in file
for (x=0; x<(*world)->objectCount; x++) {
// Get vertex count
if (fread(&(*world)->objects[x].vertexCount, sizeof(juint16), 1, in) == 1) {
// Allocate memory for vertex data
(*world)->objects->verticies = (j3CoordinatesT *)jlMalloc(sizeof(j3CoordinatesT) * (*world)->objects[x].vertexCount);
if ((*world)->objects->verticies) {
// Iterate and read verticies
for (y=0; y<(*world)->objects[x].vertexCount; y++) {
// Read one at a time in case the struct gets padded
if (fread(&(*world)->objects[x].verticies[y].local.x, sizeof(float), 1, in) != 1) {
failed = true;
break;
}
if (fread(&(*world)->objects[x].verticies[y].local.y, sizeof(float), 1, in) != 1) {
failed = true;
break;
}
if (fread(&(*world)->objects[x].verticies[y].local.z, sizeof(float), 1, in) != 1) {
failed = true;
break;
}
}
}
} 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) {
//***TODO*** This will have to be moved/improved
// Add to world count & increase potential polygon list
_j3TriangleCount += (*world)->objects[x].triangleCount;
_j3Polygons = (j3PolyListT *)jlRealloc(_j3Polygons, sizeof(j3PolyListT) * _j3TriangleCount);
// Iterate and read triangles
for (y=0; y<(*world)->objects[x].triangleCount; y++) {
// Read one at a time in case the struct gets padded
for (z=0; z<3; z++) {
if (fread(&(*world)->objects[x].triangles[y].index[z], sizeof(juint16), 1, in) != 1) {
failed = true;
break;
}
}
if (failed) break;
_j3ObjectUpdateNormalLength(&(*world)->objects[x], y);
// Triangles begin life un-lit
(*world)->objects[x].triangles[y].lit = false;
//***TODO*** All triangles are one-sided for now
(*world)->objects[x].triangles[y].twoSided = false;
//***TODO*** All triangles are white for now
(*world)->objects[x].triangles[y].color = 15;
}
} else {
// Failed to get triangle memory
failed = true;
}
}
}
if (!failed) {
_j3ObjectReset(&(*world)->objects[x]);
} else {
break; // Stop iterating
}
} // object iterator
} // objects alloc
} // Object count
} // Valid file
} // Read header
} // world alloc
// Finished! Clean up.
fclose(in);
return !failed;
}
#ifndef JOEY_IIGS
#pragma GCC diagnostic pop
#endif