1428 lines
42 KiB
C
1428 lines
42 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***
|
|
// Not all faces are rendering until they rotate out and back in
|
|
// Some fields like objectCount are juint16 but should be jint16
|
|
|
|
|
|
#include <math.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
#include "j3d.h"
|
|
#include "j3dtbls.h"
|
|
|
|
|
|
#ifdef JOEY_IIGS
|
|
|
|
segment "j3d";
|
|
#define M_PI 3.1415926
|
|
#define fabsf fabs
|
|
#define FLT_EPSILON 1.19209290E-07F
|
|
|
|
#else
|
|
|
|
#include <float.h>
|
|
|
|
#pragma GCC diagnostic push
|
|
#pragma GCC diagnostic ignored "-Wpadded"
|
|
|
|
#endif
|
|
|
|
|
|
typedef int (*j3qsortCastT)(const void *, const void *);
|
|
|
|
|
|
// Private Prototypes
|
|
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 _j3ObjectUpdateNormalLength(j3ObjectT *object, juint16 triangle);
|
|
void _j3ObjectUpdate(j3WorldT *w, jint16 index);
|
|
int _j3PolyCompare(j3PolyListT *arg1, j3PolyListT *arg2);
|
|
bool _j3UtilClipLine(jint16 *x1, jint16 *y1, jint16 *x2, jint16 *y2);
|
|
|
|
|
|
// Module global data
|
|
static jint16 _j3VarClipMinX = 0;
|
|
static jint16 _j3VarClipMinY = 0;
|
|
static jint16 _j3VarClipMinZ = 100;
|
|
static jint16 _j3VarClipMaxX = 319;
|
|
static jint16 _j3VarClipMaxY = 199;
|
|
static jint16 _j3VarClipMaxZ = 3000;
|
|
static jint16 _j3VarViewDistance = 200;
|
|
static jreal _j3VarAmbientLight = 6;
|
|
static j3Matrix4x4T _j3VarCameraMatrix;
|
|
static j3VertexT _j3VarCameraLocation;
|
|
static j3FacingT _j3VarCameraAngle;
|
|
static j3DirtynessT _j3VarCameraHowDirty = DIRTYNESS_ALL;
|
|
static j3VertexT _j3VarSunLocation;
|
|
|
|
|
|
#define ASPECT_RATIO (jreal)0.8
|
|
#define INVERSE_ASPECT_RATIO (jreal)1.25
|
|
|
|
#define HALF_SCREEN_WIDTH 160
|
|
#define HALF_SCREEN_HEIGHT 100
|
|
|
|
|
|
void j3CameraMove(jreal x, jreal y, jreal z) {
|
|
_j3VarCameraLocation.x += x;
|
|
_j3VarCameraLocation.y += y;
|
|
_j3VarCameraLocation.z += z;
|
|
_j3VarCameraHowDirty |= DIRTYNESS_TRANSLATE;
|
|
}
|
|
|
|
|
|
void j3CameraMoveTo(jreal x, jreal y, jreal z) {
|
|
_j3VarCameraLocation.x = x;
|
|
_j3VarCameraLocation.y = y;
|
|
_j3VarCameraLocation.z = z;
|
|
_j3VarCameraHowDirty |= DIRTYNESS_TRANSLATE;
|
|
}
|
|
|
|
|
|
void j3CameraRotate(jreal x, jreal y, jreal z) {
|
|
_j3VarCameraAngle.x += x;
|
|
_j3VarCameraAngle.y += y;
|
|
_j3VarCameraAngle.z += z;
|
|
_j3VarCameraHowDirty |= DIRTYNESS_ROTATE;
|
|
}
|
|
|
|
|
|
void j3CameraRotateTo(jreal x, jreal y, jreal z) {
|
|
_j3VarCameraAngle.x = (jint16)x;
|
|
_j3VarCameraAngle.y = (jint16)y;
|
|
_j3VarCameraAngle.z = (jint16)z;
|
|
_j3VarCameraHowDirty |= DIRTYNESS_ROTATE;
|
|
}
|
|
|
|
|
|
void j3CameraSetClippingBounds(jint16 x1, jint16 y1, jint16 x2, jint16 y2) {
|
|
_j3VarClipMinX = x1; j3MathCheckBounds(_j3VarClipMinX, 0, 319);
|
|
_j3VarClipMinY = y1; j3MathCheckBounds(_j3VarClipMinY, 0, 199);
|
|
_j3VarClipMaxX = x2; j3MathCheckBounds(_j3VarClipMaxX, 0, 319);
|
|
_j3VarClipMaxY = y2; j3MathCheckBounds(_j3VarClipMaxY, 0, 199);
|
|
}
|
|
|
|
|
|
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;
|
|
}
|
|
|
|
jlDrawColorSet((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)((jreal)(y2 - y1) * (jreal)(x3 - x1) / (jreal)(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) {
|
|
jreal dxRight;
|
|
jreal dxLeft;
|
|
jreal xs;
|
|
jreal xe;
|
|
jreal 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 = (jreal)x1;
|
|
xe = (jreal)x1 + (jreal)0.5;
|
|
|
|
// Perform y clipping
|
|
if (y1 < _j3VarClipMinY) {
|
|
// Compute new xs and ys
|
|
xs = xs + dxLeft * (jreal)(-y1 + _j3VarClipMinY);
|
|
xe = xe + dxRight * (jreal)(-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) {
|
|
jreal dxRight;
|
|
jreal dxLeft;
|
|
jreal xs;
|
|
jreal xe;
|
|
jreal 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 = (jreal)x1;
|
|
xe = (jreal)x2 + (jreal)0.5;
|
|
|
|
// Perform y clipping
|
|
if (y1 < _j3VarClipMinY) {
|
|
// Compute new xs and ys
|
|
xs = xs + dxLeft * (jreal)(-y1 + _j3VarClipMinY);
|
|
xe = xe + dxRight * (jreal)(-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 j3DrawWorld(j3WorldT *w) {
|
|
jint16 i;
|
|
juint16 vertex1;
|
|
juint16 vertex2;
|
|
juint16 vertex3;
|
|
jreal x1;
|
|
jreal y1;
|
|
jreal z1;
|
|
jreal x2;
|
|
jreal y2;
|
|
jreal z2;
|
|
jreal x3;
|
|
jreal y3;
|
|
jreal z3;
|
|
j3ObjectT *o;
|
|
j3PolyListT *p;
|
|
|
|
w->polygonCount = 0;
|
|
|
|
// Update all our math
|
|
for (i=0; i<w->objectCount; i++) {
|
|
_j3ObjectUpdate(w, i);
|
|
}
|
|
|
|
// Render it
|
|
for (i=0; i<w->polygonCount; i++) {
|
|
|
|
// Dereference
|
|
p = &w->polygons[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->vertices[vertex1].camera.z;
|
|
z2 = o->vertices[vertex2].camera.z;
|
|
z3 = o->vertices[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->vertices[vertex1].camera.x;
|
|
y1 = o->vertices[vertex1].camera.y;
|
|
x2 = o->vertices[vertex2].camera.x;
|
|
y2 = o->vertices[vertex2].camera.y;
|
|
x3 = o->vertices[vertex3].camera.x;
|
|
y3 = o->vertices[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);
|
|
}
|
|
|
|
|
|
jreal 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;
|
|
}
|
|
|
|
|
|
jreal j3MathVectorMagnatude3D(j3Vector3DT *v) {
|
|
// Compute the magnitude of a vector
|
|
return((jreal)sqrt((double)(v->x * v->x + v->y * v->y + v->z * v->z)));
|
|
}
|
|
|
|
|
|
void _j3ObjectMove(j3ObjectT *o, jreal x, jreal y, jreal z) {
|
|
o->position.x += x;
|
|
o->position.y += y;
|
|
o->position.z += z;
|
|
o->howDirty |= DIRTYNESS_TRANSLATE;
|
|
}
|
|
|
|
|
|
void _j3ObjectMoveTo(j3ObjectT *o, jreal x, jreal y, jreal z) {
|
|
o->position.x = x;
|
|
o->position.y = y;
|
|
o->position.z = z;
|
|
o->howDirty |= DIRTYNESS_TRANSLATE;
|
|
}
|
|
|
|
|
|
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->howDirty = DIRTYNESS_ALL;
|
|
}
|
|
|
|
|
|
void _j3ObjectRotate(j3ObjectT *o, jreal x, jreal y, jreal z) {
|
|
o->rotation.x += x; j3MathWrapBounds(o->rotation.x, 0, 360);
|
|
o->rotation.y += y; j3MathWrapBounds(o->rotation.y, 0, 360);
|
|
o->rotation.z += z; j3MathWrapBounds(o->rotation.z, 0, 360);
|
|
o->howDirty |= DIRTYNESS_ROTATE;
|
|
}
|
|
|
|
|
|
void _j3ObjectRotateTo(j3ObjectT *o, jreal x, jreal y, jreal z) {
|
|
o->rotation.x = x; j3MathWrapBounds(o->rotation.x, 0, 360);
|
|
o->rotation.y = y; j3MathWrapBounds(o->rotation.y, 0, 360);
|
|
o->rotation.z = z; j3MathWrapBounds(o->rotation.z, 0, 360);
|
|
o->howDirty |= DIRTYNESS_ROTATE;
|
|
}
|
|
|
|
|
|
void _j3ObjectScale(j3ObjectT *o, jreal x, jreal y, jreal z) {
|
|
o->scale.x += x;
|
|
o->scale.y += y;
|
|
o->scale.z += z;
|
|
o->howDirty |= DIRTYNESS_SCALE;
|
|
}
|
|
|
|
|
|
void _j3ObjectScaleTo(j3ObjectT *o, jreal x, jreal y, jreal z) {
|
|
o->scale.x = x;
|
|
o->scale.y = y;
|
|
o->scale.z = z;
|
|
o->howDirty |= DIRTYNESS_SCALE;
|
|
}
|
|
|
|
|
|
void _j3ObjectUpdate(j3WorldT *w, jint16 index) {
|
|
jint16 x;
|
|
jint16 y;
|
|
jint16 z;
|
|
jint16 i;
|
|
juint16 vertex0;
|
|
juint16 vertex1;
|
|
juint16 vertex2;
|
|
jreal dot;
|
|
jreal 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;
|
|
j3ObjectT *o;
|
|
|
|
o = &w->objects[index];
|
|
|
|
// === SCALE ===
|
|
if (o->howDirty & DIRTYNESS_SCALE) {
|
|
for (i=0; i<o->vertexCount; i++) {
|
|
o->vertices[i].scaled.x = o->vertices[i].original.x * o->scale.x;
|
|
o->vertices[i].scaled.y = o->vertices[i].original.y * o->scale.y;
|
|
o->vertices[i].scaled.z = o->vertices[i].original.z * o->scale.z;
|
|
}
|
|
}
|
|
|
|
// === ROTATION ===
|
|
if (o->howDirty & DIRTYNESS_ROTATE) {
|
|
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] = cosTable[z];
|
|
final[0][1] = sinTable[z];
|
|
final[1][0] = -sinTable[z];
|
|
final[1][1] = cosTable[z];
|
|
for (i=0; i<o->vertexCount; i++) {
|
|
o->vertices[i].rotated.x = o->vertices[i].scaled.x * final[0][0] + o->vertices[i].scaled.y * final[1][0];
|
|
o->vertices[i].rotated.y = o->vertices[i].scaled.x * final[0][1] + o->vertices[i].scaled.y * final[1][1];
|
|
o->vertices[i].rotated.z = o->vertices[i].scaled.z;
|
|
}
|
|
break;
|
|
|
|
case 2: // Final matrix = y
|
|
final[0][0] = cosTable[y];
|
|
final[0][2] = -sinTable[y];
|
|
final[2][0] = sinTable[y];
|
|
final[2][2] = cosTable[y];
|
|
for (i=0; i<o->vertexCount; i++) {
|
|
o->vertices[i].rotated.x = o->vertices[i].scaled.x * final[0][0] + o->vertices[i].scaled.z * final[2][0];
|
|
o->vertices[i].rotated.y = o->vertices[i].scaled.y;
|
|
o->vertices[i].rotated.z = o->vertices[i].scaled.x * final[0][2] + o->vertices[i].scaled.z * final[2][2];
|
|
}
|
|
break;
|
|
|
|
case 3: // Final matrix = y * z
|
|
final[0][0] = cosTable[y] * cosTable[z];
|
|
final[0][1] = cosTable[y] * sinTable[z];
|
|
final[0][2] = -sinTable[y];
|
|
|
|
final[1][0] = -sinTable[z];
|
|
final[1][1] = cosTable[z];
|
|
|
|
final[2][0] = sinTable[y] * cosTable[z];
|
|
final[2][1] = sinTable[y] * sinTable[z];
|
|
final[2][2] = cosTable[y];
|
|
|
|
for (i=0; i<o->vertexCount; i++) {
|
|
o->vertices[i].rotated.x = o->vertices[i].scaled.x * final[0][0] + o->vertices[i].scaled.y * final[1][0] + o->vertices[i].scaled.z * final[2][0];
|
|
o->vertices[i].rotated.y = o->vertices[i].scaled.x * final[0][1] + o->vertices[i].scaled.y * final[1][1] + o->vertices[i].scaled.z * final[2][1];
|
|
o->vertices[i].rotated.z = o->vertices[i].scaled.x * final[0][2] + o->vertices[i].scaled.z * final[2][2];
|
|
}
|
|
break;
|
|
|
|
case 4: // Final matrix = x
|
|
final[1][1] = cosTable[x];
|
|
final[1][2] = sinTable[x];
|
|
final[2][1] = -sinTable[x];
|
|
final[2][2] = cosTable[x];
|
|
for (i=0; i<o->vertexCount; i++) {
|
|
o->vertices[i].rotated.x = o->vertices[i].scaled.x;
|
|
o->vertices[i].rotated.y = o->vertices[i].scaled.y * final[1][1] + o->vertices[i].scaled.z * final[2][1];
|
|
o->vertices[i].rotated.z = o->vertices[i].scaled.y * final[1][2] + o->vertices[i].scaled.z * final[2][2];
|
|
}
|
|
break;
|
|
|
|
case 5: // Final matrix = x * z
|
|
final[0][0] = cosTable[z];
|
|
final[0][1] = sinTable[z];
|
|
|
|
final[1][0] = -cosTable[x] * sinTable[z];
|
|
final[1][1] = cosTable[x] * cosTable[z];
|
|
final[1][2] = sinTable[x];
|
|
|
|
final[2][0] = sinTable[x] * sinTable[z];
|
|
final[2][1] = -sinTable[x] * cosTable[z];
|
|
final[2][2] = cosTable[x];
|
|
|
|
for (i=0; i<o->vertexCount; i++) {
|
|
o->vertices[i].rotated.x = o->vertices[i].scaled.x * final[0][0] + o->vertices[i].scaled.y * final[1][0] + o->vertices[i].scaled.z * final[2][0];
|
|
o->vertices[i].rotated.y = o->vertices[i].scaled.x * final[0][1] + o->vertices[i].scaled.y * final[1][1] + o->vertices[i].scaled.z * final[2][1];
|
|
o->vertices[i].rotated.z = o->vertices[i].scaled.y * final[1][2] + o->vertices[i].scaled.z * final[2][2];
|
|
}
|
|
break;
|
|
|
|
case 6: // Final matrix = x * y
|
|
final[0][0] = cosTable[y];
|
|
final[0][2] = -sinTable[y];
|
|
|
|
final[1][0] = sinTable[x] * sinTable[y];
|
|
final[1][1] = cosTable[x];
|
|
final[1][2] = sinTable[x] * cosTable[y];
|
|
|
|
final[2][0] = cosTable[x] * sinTable[y];
|
|
final[2][1] = -sinTable[x];
|
|
final[2][2] = cosTable[x] * cosTable[y];
|
|
|
|
for (i=0; i<o->vertexCount; i++) {
|
|
o->vertices[i].rotated.x = o->vertices[i].scaled.x * final[0][0] + o->vertices[i].scaled.y * final[1][0] + o->vertices[i].scaled.z * final[2][0];
|
|
o->vertices[i].rotated.y = o->vertices[i].scaled.y * final[1][1] + o->vertices[i].scaled.z * final[2][1];
|
|
o->vertices[i].rotated.z = o->vertices[i].scaled.x * final[0][2] + o->vertices[i].scaled.y * final[1][2] + o->vertices[i].scaled.z * final[2][2];
|
|
}
|
|
break;
|
|
|
|
case 7: // Final matrix = x * y * z
|
|
j3MathMatrix4x4Identity(rotateX);
|
|
rotateX[1][1] = cosTable[x];
|
|
rotateX[1][2] = sinTable[x];
|
|
rotateX[2][1] = -sinTable[x];
|
|
rotateX[2][2] = cosTable[x];
|
|
|
|
j3MathMatrix4x4Identity(rotateY);
|
|
rotateY[0][0] = cosTable[y];
|
|
rotateY[0][2] = -sinTable[y];
|
|
rotateY[2][0] = sinTable[y];
|
|
rotateY[2][2] = cosTable[y];
|
|
|
|
j3MathMatrix4x4Identity(rotateZ);
|
|
rotateZ[0][0] = cosTable[z];
|
|
rotateZ[0][1] = sinTable[z];
|
|
rotateZ[1][0] = -sinTable[z];
|
|
rotateZ[1][1] = cosTable[z];
|
|
|
|
j3MathMatrix4x4Mult(rotateX, rotateY, temp);
|
|
j3MathMatrix4x4Mult(temp, rotateZ, final);
|
|
|
|
for (i=0; i<o->vertexCount; i++) {
|
|
o->vertices[i].rotated.x = o->vertices[i].scaled.x * final[0][0] + o->vertices[i].scaled.y * final[1][0] + o->vertices[i].scaled.z * final[2][0];
|
|
o->vertices[i].rotated.y = o->vertices[i].scaled.x * final[0][1] + o->vertices[i].scaled.y * final[1][1] + o->vertices[i].scaled.z * final[2][1];
|
|
o->vertices[i].rotated.z = o->vertices[i].scaled.x * final[0][2] + o->vertices[i].scaled.y * final[1][2] + o->vertices[i].scaled.z * final[2][2];
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
// === TRANSLATION ===
|
|
if (o->howDirty & DIRTYNESS_TRANSLATE) {
|
|
for (i=0; i<o->vertexCount; i++) {
|
|
o->vertices[i].translated.x = o->vertices[i].rotated.x + o->position.x;
|
|
o->vertices[i].translated.y = o->vertices[i].rotated.y + o->position.y;
|
|
o->vertices[i].translated.z = o->vertices[i].rotated.z + o->position.z;
|
|
}
|
|
}
|
|
|
|
// === CAMERA SPACE ===
|
|
if ((_j3VarCameraHowDirty & DIRTYNESS_ROTATE) || (_j3VarCameraHowDirty & DIRTYNESS_TRANSLATE)) {
|
|
// 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;
|
|
|
|
// X matrix
|
|
rotateX[1][1] = ( cosTable[_j3VarCameraAngle.x]);
|
|
rotateX[1][2] = -( sinTable[_j3VarCameraAngle.x]);
|
|
rotateX[2][1] = -(-sinTable[_j3VarCameraAngle.x]);
|
|
rotateX[2][2] = ( cosTable[_j3VarCameraAngle.x]);
|
|
|
|
// Y matrix
|
|
rotateY[0][0] = ( cosTable[_j3VarCameraAngle.y]);
|
|
rotateY[0][2] = -(-sinTable[_j3VarCameraAngle.y]);
|
|
rotateY[2][0] = -( sinTable[_j3VarCameraAngle.y]);
|
|
rotateY[2][2] = ( cosTable[_j3VarCameraAngle.y]);
|
|
|
|
// Z matrix
|
|
rotateZ[0][0] = ( cosTable[_j3VarCameraAngle.z]);
|
|
rotateZ[0][1] = -( sinTable[_j3VarCameraAngle.z]);
|
|
rotateZ[1][0] = -(-sinTable[_j3VarCameraAngle.z]);
|
|
rotateZ[1][1] = ( cosTable[_j3VarCameraAngle.z]);
|
|
|
|
j3MathMatrix4x4Mult(translate, rotateX, result1);
|
|
j3MathMatrix4x4Mult(result1, rotateY, result2);
|
|
j3MathMatrix4x4Mult(result2, rotateZ, _j3VarCameraMatrix);
|
|
|
|
_j3VarCameraHowDirty = DIRTYNESS_CLEAN;
|
|
}
|
|
for (i=0; i<o->vertexCount; i++) {
|
|
o->vertices[i].camera.x =
|
|
o->vertices[i].translated.x * _j3VarCameraMatrix[0][0] +
|
|
o->vertices[i].translated.y * _j3VarCameraMatrix[1][0] +
|
|
o->vertices[i].translated.z * _j3VarCameraMatrix[2][0] +
|
|
_j3VarCameraMatrix[3][0];
|
|
o->vertices[i].camera.y =
|
|
o->vertices[i].translated.x * _j3VarCameraMatrix[0][1] +
|
|
o->vertices[i].translated.y * _j3VarCameraMatrix[1][1] +
|
|
o->vertices[i].translated.z * _j3VarCameraMatrix[2][1] +
|
|
_j3VarCameraMatrix[3][1];
|
|
o->vertices[i].camera.z =
|
|
o->vertices[i].translated.x * _j3VarCameraMatrix[0][2] +
|
|
o->vertices[i].translated.y * _j3VarCameraMatrix[1][2] +
|
|
o->vertices[i].translated.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->howDirty & DIRTYNESS_SCALE) {
|
|
_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->vertices[vertex0].translated, (j3VertexT *)&o->vertices[vertex1].translated, (j3Vector3DT *)&u);
|
|
// Vector v = v0->v2
|
|
j3MathMakeVector3D((j3VertexT *)&o->vertices[vertex0].translated, (j3VertexT *)&o->vertices[vertex2].translated, (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->vertices[vertex0].translated.x;
|
|
sight.y = _j3VarCameraLocation.y - o->vertices[vertex0].translated.y;
|
|
sight.z = _j3VarCameraLocation.z - o->vertices[vertex0].translated.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->vertices[vertex0].translated, (j3VertexT *)&o->vertices[vertex1].translated, (j3Vector3DT *)&u);
|
|
// Vector v = v0->v2
|
|
j3MathMakeVector3D((j3VertexT *)&o->vertices[vertex0].translated, (j3VertexT *)&o->vertices[vertex2].translated, (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?
|
|
//***TODO*** Our visible flag is backwards for some reason. Winding direction wrong?
|
|
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 = (jreal)0.3333333 * (o->vertices[vertex0].camera.z + o->vertices[vertex1].camera.z + o->vertices[vertex2].camera.z);
|
|
// Add to visible polygon list
|
|
w->polygons[w->polygonCount].object = o;
|
|
w->polygons[w->polygonCount].triangleNumber = (juint16)i;
|
|
w->polygonCount++;
|
|
}
|
|
|
|
} // for i
|
|
|
|
// Z Sort the visible polygon list
|
|
qsort((void *)w->polygons, w->polygonCount, sizeof(j3PolyListT), (j3qsortCastT)_j3PolyCompare);
|
|
|
|
o->howDirty = DIRTYNESS_CLEAN;
|
|
}
|
|
|
|
|
|
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->vertices[vertex0].scaled, (j3VertexT *)&object->vertices[vertex1].scaled, (j3Vector3DT *)&u);
|
|
j3MathMakeVector3D((j3VertexT *)&object->vertices[vertex0].scaled, (j3VertexT *)&object->vertices[vertex2].scaled, (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 = (jreal)15.0 / j3MathVectorMagnatude3D((j3Vector3DT *)&normal);
|
|
}
|
|
|
|
|
|
int _j3PolyCompare(j3PolyListT *arg1, j3PolyListT *arg2) {
|
|
jreal z1;
|
|
jreal 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;
|
|
jreal dx;
|
|
jreal 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)((jreal)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)((jreal)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)((jreal)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)((jreal)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)((jreal)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)((jreal)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)((jreal)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)((jreal)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) {
|
|
// Nothing
|
|
}
|
|
|
|
|
|
void j3UtilStartup(void) {
|
|
_j3VarCameraLocation.x = 0;
|
|
_j3VarCameraLocation.y = 0;
|
|
_j3VarCameraLocation.z = 0;
|
|
|
|
_j3VarCameraAngle.x = 0;
|
|
_j3VarCameraAngle.y = 0;
|
|
_j3VarCameraAngle.z = 0;
|
|
|
|
_j3VarSunLocation.x = (jreal)-0.913913;
|
|
_j3VarSunLocation.y = (jreal) 0.389759;
|
|
_j3VarSunLocation.z = (jreal)-0.113369;
|
|
}
|
|
|
|
|
|
void _j3PropFree(j3PropT **prop) {
|
|
int x;
|
|
|
|
if ((*prop)) {
|
|
for (x=0; x<(*prop)->pieceCount; x++) {
|
|
if ((*prop)->pieces[x].triangles) {
|
|
jlFree((*prop)->pieces[x].triangles);
|
|
}
|
|
if ((*prop)->pieces[x].vertices) {
|
|
jlFree((*prop)->pieces[x].vertices);
|
|
}
|
|
}
|
|
if ((*prop)->pieces) {
|
|
jlFree((*prop)->pieces);
|
|
}
|
|
jlFree(*prop);
|
|
}
|
|
}
|
|
|
|
|
|
jint16 _j3PropLoad(j3PropT **prop, 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 this prop?
|
|
if (*prop != NULL) {
|
|
// Free this one
|
|
j3PropFree(prop);
|
|
}
|
|
|
|
// Allocate prop object
|
|
*prop = (j3PropT *)jlMalloc(sizeof(j3PropT));
|
|
if (*prop) {
|
|
// Initialize prop object & create an initial piece to read data into
|
|
(*prop)->pieceCount = 0;;
|
|
(*prop)->pieces = NULL;
|
|
|
|
// Is this a valid prop file?
|
|
if (fread(buffer, sizeof(byte), 4, in) == 4) {
|
|
if ((buffer[0] == 'J') && (buffer[1] == '3') && (buffer[2] == 'D') && (buffer[3] <= 0)) {
|
|
|
|
// Get piece count
|
|
if (fread(&(*prop)->pieceCount, sizeof(juint16), 1, in) == 1) {
|
|
|
|
// Allocate memory for pieces
|
|
(*prop)->pieces = (j3PieceT *)jlMalloc(sizeof(j3PieceT) * (*prop)->pieceCount);
|
|
if ((*prop)->pieces) {
|
|
|
|
// Iterate across pieces in file
|
|
for (x=0; x<(*prop)->pieceCount; x++) {
|
|
|
|
// Get vertex count
|
|
if (fread(&(*prop)->pieces[x].vertexCount, sizeof(juint16), 1, in) == 1) {
|
|
|
|
// Allocate memory for vertex data
|
|
(*prop)->pieces[x].vertices = (j3VertexT *)jlMalloc(sizeof(j3VertexT) * (*prop)->pieces[x].vertexCount);
|
|
if ((*prop)->pieces[x].vertices) {
|
|
|
|
// Iterate and read vertices
|
|
for (y=0; y<(*prop)->pieces[x].vertexCount; y++) {
|
|
// Read one at a time in case the struct gets padded
|
|
// These really are type 'float' - don't use 'jreal' here
|
|
if (fread(&(*prop)->pieces[x].vertices[y].x, sizeof(float), 1, in) != 1) {
|
|
failed = true;
|
|
break;
|
|
}
|
|
if (fread(&(*prop)->pieces[x].vertices[y].y, sizeof(float), 1, in) != 1) {
|
|
failed = true;
|
|
break;
|
|
}
|
|
if (fread(&(*prop)->pieces[x].vertices[y].z, sizeof(float), 1, in) != 1) {
|
|
failed = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
} else {
|
|
// Failed to get vertex memory
|
|
failed = true;
|
|
}
|
|
|
|
if (!failed) {
|
|
// Get triangle count
|
|
if (fread(&(*prop)->pieces[x].triangleCount, sizeof(juint16), 1, in) == 1) {
|
|
|
|
// Allocate memory for triangle data
|
|
(*prop)->pieces[x].triangles = (j3TriangleThinT *)jlMalloc(sizeof(j3TriangleThinT) * (*prop)->pieces[x].triangleCount);
|
|
if ((*prop)->pieces[x].triangles) {
|
|
|
|
// Iterate and read triangles
|
|
for (y=0; y<(*prop)->pieces[x].triangleCount; y++) {
|
|
// Read one at a time in case the struct gets padded
|
|
for (z=0; z<3; z++) {
|
|
if (fread(&(*prop)->pieces[x].triangles[y].index[z], sizeof(juint16), 1, in) != 1) {
|
|
failed = true;
|
|
break;
|
|
}
|
|
}
|
|
if (failed) break;
|
|
|
|
//***TODO*** Triangles begin life un-lit
|
|
(*prop)->pieces[x].triangles[y].lit = false;
|
|
|
|
//***TODO*** All triangles are one-sided for now
|
|
(*prop)->pieces[x].triangles[y].twoSided = false;
|
|
|
|
//***TODO*** All triangles are white for now
|
|
(*prop)->pieces[x].triangles[y].color = 15;
|
|
}
|
|
|
|
} else {
|
|
// Failed to get triangle memory
|
|
failed = true;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
if (failed) {
|
|
break; // Stop iterating
|
|
}
|
|
|
|
} // piece iterator
|
|
} // pieces alloc
|
|
} // pieces count
|
|
} // Valid file
|
|
} // Read header
|
|
} // prop alloc
|
|
|
|
// Finished! Clean up.
|
|
fclose(in);
|
|
|
|
if (failed) {
|
|
return -1;
|
|
}
|
|
|
|
return (jint16)(*prop)->pieceCount;
|
|
}
|
|
|
|
|
|
jint16 _j3WorldAddProp(j3WorldT **world, j3PropT *prop) {
|
|
|
|
juint16 x;
|
|
juint16 y;
|
|
juint16 z;
|
|
juint16 oldObjectCount = 0;
|
|
bool result = true;
|
|
|
|
// Is this world valid yet?
|
|
if (*world == NULL) {
|
|
*world = (j3WorldT *)jlMalloc(sizeof(j3WorldT));
|
|
(*world)->objectCount = 0;
|
|
(*world)->polygonCount = 0;
|
|
(*world)->triangleCount = 0;
|
|
(*world)->objects = NULL;
|
|
(*world)->polygons = NULL;
|
|
}
|
|
|
|
// Add new prop pieces to world as objects
|
|
oldObjectCount = (*world)->objectCount;
|
|
(*world)->objectCount += prop->pieceCount;
|
|
|
|
// Allocate memory for objects
|
|
(*world)->objects = (j3ObjectT *)jlRealloc((*world)->objects, sizeof(j3ObjectT) * (*world)->objectCount);
|
|
if (!(*world)->objects) {
|
|
return -1;
|
|
}
|
|
|
|
// Copy pieces into objects
|
|
y = 0;
|
|
for (x=oldObjectCount; x<(*world)->objectCount; x++) {
|
|
|
|
// Vertices
|
|
(*world)->objects[x].vertexCount = prop->pieces[y].vertexCount;
|
|
|
|
// Allocate memory for vertex data
|
|
(*world)->objects[x].vertices = (j3CoordinatesT *)jlMalloc(sizeof(j3CoordinatesT) * (*world)->objects[x].vertexCount);
|
|
if (!(*world)->objects[x].vertices) {
|
|
result = false;
|
|
break;
|
|
}
|
|
|
|
// Copy vertex data
|
|
for (z=0; z<prop->pieces[y].vertexCount; z++) {
|
|
(*world)->objects[x].vertices[z].original.x = prop->pieces[y].vertices[z].x;
|
|
(*world)->objects[x].vertices[z].original.y = prop->pieces[y].vertices[z].y;
|
|
(*world)->objects[x].vertices[z].original.z = prop->pieces[y].vertices[z].z;
|
|
}
|
|
|
|
// Triangles
|
|
(*world)->objects[x].triangleCount = prop->pieces[y].triangleCount;
|
|
|
|
// Allocate memory for triangle data
|
|
(*world)->objects[x].triangles = (j3TriangleT *)jlMalloc(sizeof(j3TriangleT) * (*world)->objects[x].triangleCount);
|
|
if (!(*world)->objects[x].triangles) {
|
|
result = false;
|
|
break;
|
|
}
|
|
|
|
// Copy triangle data
|
|
for (z=0; z<prop->pieces[y].triangleCount; z++) {
|
|
(*world)->objects[x].triangles[z].index[0] = prop->pieces[y].triangles[z].index[0];
|
|
(*world)->objects[x].triangles[z].index[1] = prop->pieces[y].triangles[z].index[1];
|
|
(*world)->objects[x].triangles[z].index[2] = prop->pieces[y].triangles[z].index[2];
|
|
(*world)->objects[x].triangles[z].lit = prop->pieces[y].triangles[z].lit;
|
|
(*world)->objects[x].triangles[z].color = prop->pieces[y].triangles[z].color;
|
|
(*world)->objects[x].triangles[z].twoSided = prop->pieces[y].triangles[z].twoSided;
|
|
(*world)->objects[x].triangles[z].normalLength = prop->pieces[y].triangles[z].normalLength;
|
|
|
|
_j3ObjectUpdateNormalLength(&(*world)->objects[x], z);
|
|
|
|
// Triangles begin life visible
|
|
(*world)->objects[x].triangles[z].visible = true;
|
|
}
|
|
|
|
// Add to world count & increase potential polygon list
|
|
(*world)->triangleCount += (*world)->objects[x].triangleCount;
|
|
(*world)->polygons = (j3PolyListT *)jlRealloc((*world)->polygons, sizeof(j3PolyListT) * (*world)->triangleCount);
|
|
if (!(*world)->polygons) {
|
|
result = false;
|
|
break;
|
|
}
|
|
|
|
_j3ObjectReset(&(*world)->objects[x]);
|
|
|
|
// Next piece
|
|
y++;
|
|
|
|
} // Pieces
|
|
|
|
if (!result) {
|
|
return -1;
|
|
}
|
|
|
|
return (jint16)oldObjectCount;
|
|
}
|
|
|
|
|
|
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].vertices) {
|
|
jlFree((*world)->objects[x].vertices);
|
|
}
|
|
}
|
|
if ((*world)->objects) {
|
|
jlFree((*world)->objects);
|
|
}
|
|
if ((*world)->polygons != NULL) {
|
|
jlFree((*world)->polygons);
|
|
}
|
|
jlFree(*world);
|
|
}
|
|
}
|
|
|
|
|
|
#ifndef JOEY_IIGS
|
|
#pragma GCC diagnostic pop
|
|
#endif
|
|
|