diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..1ab4b10 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +font.xcf filter=lfs diff=lfs merge=lfs -text diff --git a/.gitignore b/.gitignore index 9a585f6..8766f23 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,7 @@ -*.user *.~ +*.user +*.sta +*.dis +*.lnk +*.map +*.sym diff --git a/build-All.sh b/build-All.sh new file mode 100755 index 0000000..290ba36 --- /dev/null +++ b/build-All.sh @@ -0,0 +1,23 @@ +#!/bin/bash -e + +PROJECT=j3d +GAME=${JOEY}/j3d/j3d +DATA=(${GAME}/cube.obj ${GAME}/font.sta) +#SOURCE=(*.c *.h) +SOURCE=() + +pushd "${GAME}" +find . -type f -name "*.xcf" -exec ${JOEY}/utils/xcf2sta.sh {} \; +popd + +#. ${JOEY}/dist/IIgs/build-IIgs.helper.sh +. ${JOEY}/joeylib/scripts/build-IIgs.helper.sh +. ${JOEY}/joeylib/scripts/build-PC.helper.sh + +if [[ "$1x" == "x" ]]; then + #buildLinux32 + buildLinux64 + #buildWindows32 + buildWindows64 +fi +buildIIgs $1 diff --git a/font.xcf b/font.xcf new file mode 100644 index 0000000..bd06b71 --- /dev/null +++ b/font.xcf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ba47ba0dc92cba333e21e7c7b91e82d0ebcc15fb4c2779e676d2c1924418d5f7 +size 19261 diff --git a/j3d.c b/j3d.c new file mode 100644 index 0000000..9395677 --- /dev/null +++ b/j3d.c @@ -0,0 +1,635 @@ +/* + * JoeyLib 3D + * Copyright (C) 2019 Scott Duensing + * + * 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 + + +#include +#include +#include +#include + +#include "stretchy_buffer.h" + +#include "j3d.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 + + +// Module global data +static float sin_table[361]; +static float cos_table[361]; +static int clipMinX = 0; +static int clipMinY = 0; +static int clipMaxX = 319; +static int clipMaxY = 199; +static int viewDistance = 250; + + +#define ASPECT_RATIO (float)0.8 +#define INVERSE_ASPECT_RATIO (float)1.25 + +#define HALF_SCREEN_WIDTH 160 +#define HALF_SCREEN_HEIGHT 100 + + +void _j3DrawWireframePair(j3ObjectT *o, int v1, int v2) { + float x1; + float y1; + float z1; + float x2; + float y2; + float z2; + int ix1; + int iy1; + int ix2; + int 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 * 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; + + ix1 = (int)x1; + iy1 = (int)y1; + ix2 = (int)x2; + iy2 = (int)y2; + + if (_j3UtilClipLine(&ix1, &iy1, &ix2, &iy2)) { + jlDrawLine((jint16)ix1, (jint16)iy1, (jint16)ix2, (jint16)iy2); + } +} + + +void _j3DrawWireframe(j3ObjectT *o) { + int t; // Current triangle + + for (t=0; ttriangleCount; 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 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 _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; +} + + +void _j3ObjectUpdate(j3ObjectT *o) { + int x; + int y; + int z; + int i; + int axis = 0; + j3Matrix4x4T rotateX; + j3Matrix4x4T rotateY; + j3Matrix4x4T rotateZ; + j3Matrix4x4T final; + j3Matrix4x4T temp; + + // === ROTATION === + + x = (int)o->rotation.x; + y = (int)o->rotation.y; + z = (int)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; ivertexCount; 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; ivertexCount; 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; ivertexCount; 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; ivertexCount; 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; ivertexCount; 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; ivertexCount; 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; ivertexCount; 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 === + + for (i=0; ivertexCount; 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? + + for (i=0; ivertexCount; i++) { + o->verticies[i].camera = o->verticies[i].world; + } +} + + +bool _j3UtilClipLine(int *x1, int *y1, int *x2, int *y2) { + int xi; + int 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 >= clipMinX) && (*x1 <= clipMaxX) && (*y1 >= clipMinY) && (*y1 <= clipMaxY)); + point2 = ((*x2 >= clipMinX) && (*x2 <= clipMaxX) && (*y2 >= clipMinY) && (*y2 <= clipMaxY)); + 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 (((*x1clipMaxX) && (*x2>clipMaxX)) || // right + ((*y1clipMaxY) && (*y2>clipMaxY))) { // 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 > clipMaxX) { + // Flag right edge + rightEdge = true; + // Find intersection with right edge + if (fabsf(dx) > FLT_EPSILON) { // dx != 0 + yi = (int)((float)0.5 + (dy / dx) * (clipMaxX - *x1) + *y1); + } else { + yi = -1; // Invalidate intersection + } + } else if (*x2 < clipMinX) { + // Flag left edge + leftEdge = true; + // Find intersection with left edge + if (fabsf(dx) > FLT_EPSILON) { // dx != 0 + yi = (int)((float)0.5 + (dy / dx) * (clipMinX - *x1) + *y1); + } else { + yi = -1; // Invalidate intersection + } + } + + if (*y2 > clipMaxY) { + // Flag bottom edge + bottomEdge = true; + // Find intersection with right edge + if (fabsf(dy) > FLT_EPSILON) { // dy != 0 + xi = (int)((float)0.5 + (dx / dy) * (clipMaxY - *y1) + *x1); + } else { + xi = -1; // Invalidate inntersection + } + } else if (*y2 < clipMinY) { + // Flag top edge + topEdge = true; + // Find intersection with top edge + if (fabsf(dy) > FLT_EPSILON) { // dy != 0 + xi = (int)((float)0.5 + (dx / dy) * (clipMinY - *y1) + *x1); + } else { + xi = -1; // Invalidate intersection + } + } + + // We know where the line passed through + // FInd which edge is the proper intersection + + if (rightEdge && (yi >= clipMinY && yi <= clipMaxY)) { + *x2 = clipMaxX; + *y2 = yi; + success = true; + } else if (leftEdge && (yi >= clipMinY && yi <= clipMaxY)) { + *x2 = clipMinX; + *y2 = yi; + success = true; + } + + if (bottomEdge && (xi >= clipMinX && xi <= clipMaxX)) { + *x2 = xi; + *y2 = clipMaxY; + success = true; + } else if (topEdge && (xi >= clipMinX && xi <= clipMaxX)) { + *x2 = xi; + *y2 = clipMinY; + 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 > clipMaxX) { + // Flag right edge + rightEdge = true; + // Find intersection with right edge + if (fabsf(dx) > FLT_EPSILON) { // dx != 0 + yi = (int)((float)0.5 + (dy / dx) * (clipMaxX - *x2) + *y2); + } else { + yi = -1; // Invalidate inntersection + } + } else if (*x1 < clipMinX) { + // Flag left edge + leftEdge = true; + // Find intersection with left edge + if (fabsf(dx) > FLT_EPSILON) { // dx != 0 + yi = (int)((float)0.5 + (dy / dx) * (clipMinX - *x2) + *y2); + } else { + yi = -1; // Invalidate intersection + } + } + + if (*y1 > clipMaxY) { + // Flag bottom edge + bottomEdge = true; + // Find intersection with right edge + if (fabsf(dy) > FLT_EPSILON) { // dy != 0 + xi = (int)((float)0.5 + (dx / dy) * (clipMaxY - *y2) + *x2); + } else { + xi = -1; // invalidate inntersection + } + } else if (*y1 < clipMinY) { + // Flag top edge + topEdge = true; + // Find intersection with top edge + if (fabsf(dy) > FLT_EPSILON) { // dy != 0 + xi = (int)((float)0.5 + (dx / dy) * (clipMinY - *y2) + *x2); + } else { + xi = -1; // invalidate inntersection + } + } + + // We know where the line passed through + // Find which edge is the proper intersection + + if (rightEdge && (yi >= clipMinY && yi <= clipMaxY)) { + *x1 = clipMaxX; + *y1 = yi; + success = true; + } else if (leftEdge && (yi >= clipMinY && yi <= clipMaxY)) { + *x1 = clipMinX; + *y1 = yi; + success = true; + } + + if (bottomEdge && (xi >= clipMinX && xi <= clipMaxX)) { + *x1 = xi; + *y1 = clipMaxY; + success = true; + } else if (topEdge && (xi >= clipMinX && xi <= clipMaxX)) { + *x1 = xi; + *y1 = clipMinY; + success = true; + } + } + + return(success); +} + + +void j3UtilShutdown(void) { + // Nothing yet +} + + +void j3UtilStartup(void) { + int euler; + float radian; + + // Build look-up tables for speed later + for (euler=0; euler<361; euler++) { + radian = ((float)M_PI * (float)euler / (float)180); + sin_table[euler] = (float)sin((double)radian); + cos_table[euler] = (float)cos((double)radian); + } +} + + +void _j3WorldFree(j3WorldT **world) { + int x; + + for (x=0; xobjects); x++) { + sb_free((*world)->objects[x].triangles); + sb_free((*world)->objects[x].verticies); + } + sb_free((*world)->objects); + jlFree(*world); +} + + +bool _j3WorldLoad(j3WorldT **world, char *file) { + int r; + int x; + char token[1024]; + char *c; + FILE *in; + j3CoordinatesT v; + j3ObjectT o; + j3TriangleT t; + + in = fopen(jlUtilMakePathname(file, "obj"), "rt"); + + // 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)); + + // Initialize world object & create an initial object to read data into + (*world)->objects = NULL; + o.vertexCount = 0; + o.triangleCount = 0; + o.verticies = NULL; + o.triangles = NULL; + + _j3ObjectReset(&o); + + while (true) { + + // Read next token + r = fscanf(in, "%s", token); + + // End of file? + if (r == EOF) { + break; + } + + // Vertex? + if (strcmp(token, "v" ) == 0) { + r = fscanf(in, "%f %f %f\n", &v.local.x, &v.local.y, &v.local.z); + sb_push(o.verticies, v); + o.vertexCount++; + } + + // Face? + if (strcmp(token, "f" ) == 0) { + for (x=0; x<3; x++) { + // Fetch 'x'th vertex index + r = fscanf(in, "%s", token); + c = strstr(token, "/"); + if (c) c[0] = 0; + r = atoi(token); + t.index[x] = r - 1; + } + fscanf(in, "\n"); + sb_push(o.triangles, t); + o.triangleCount++; + } + } + + //***TODO*** support multiple objects - not sure how I want to do this yet + sb_push((*world)->objects, o); + + // Finished! Clean up. + fclose(in); + + return true; +} + +#ifndef JOEY_IIGS +#pragma GCC diagnostic pop +#endif + diff --git a/j3d.h b/j3d.h new file mode 100644 index 0000000..57d11ea --- /dev/null +++ b/j3d.h @@ -0,0 +1,133 @@ +/* + * JoeyLib 3D + * Copyright (C) 2019 Scott Duensing + * + * 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. +*/ + + +#ifndef H_J3D_ +#define H_J3D_ + + +#include "joey.h" + + +typedef float j3Matrix4x4T[4][4]; + +typedef struct { + float x; + float y; + float z; +} j3VertexT; + +typedef struct { + j3VertexT local; // Original vertex positions + j3VertexT world; // After rotation, scale, translation (in that order) + j3VertexT camera; // Camera space +} j3CoordinatesT; + +typedef struct { + int index[3]; // We do this instead of just a typedef so it works with stretch_buffer +} j3TriangleT; + +typedef struct { + int vertexCount; // How many verticies are in the list + int triangleCount; // How many triangles are in the list + j3CoordinatesT *verticies; // List of verticies as loaded from disk (more v1, v2, v3 than x, y, z) + j3TriangleT *triangles; // Triangles built from vertex list + j3VertexT position; // Position of object in world + j3VertexT rotation; // Rotation of object in world + j3VertexT scale; // Scale of object in world +} j3ObjectT; + +typedef struct { + j3ObjectT *objects; +} j3WorldT; + + +#define jtCameraSetClippingBounds(x1, y1, x2, y2) \ + clipMinX = (x1); j3MathCheckBounds(clipMinX, 0, 319); \ + clipMinY = (y1); j3MathCheckBounds(clipMiny, 0, 199); \ + clipMaxX = (x2); j3MathCheckBounds(clipMaxX, 0, 319); \ + clipMaxY = (y2); j3MathCheckBounds(clipMaxy, 0, 199) + +#define j3MathCheckBounds(value, min, max) \ + if (value < (min)) value = (min); \ + if (value > (max)) value = (max) + +#define j3MathWrapBounds(value, min, max) \ + if (value < (min)) value += (max); \ + if (value > (max)) value -= (max) + +#define j3ObjectMove(ob, px, py, pz) \ + ob.position.x += (px); \ + ob.position.y += (py); \ + ob.position.z += (pz) + +#define j3ObjectMoveTo(ob, px, py, pz) \ + ob.position.x = (px); \ + ob.position.y = (py); \ + ob.position.z = (pz) + +#define j3ObjectRotate(ob, px, py, pz) \ + ob.rotation.x += (px); j3MathWrapBounds(ob.rotation.x, 0, 360); \ + ob.rotation.y += (py); j3MathWrapBounds(ob.rotation.y, 0, 360); \ + ob.rotation.z += (pz); j3MathWrapBounds(ob.rotation.z, 0, 360) + +#define j3ObjectRotateTo(ob, px, py, pz) \ + ob.rotation.x = (px); j3MathWrapBounds(ob.rotation.x, 0, 360); \ + ob.rotation.y = (py); j3MathWrapBounds(ob.rotation.y, 0, 360); \ + ob.rotation.z = (pz); j3MathWrapBounds(ob.rotation.z, 0, 360) + +#define j3ObjectScale(ob, px, py, pz) \ + ob.scale.x += (px); \ + ob.scale.y += (py); \ + ob.scale.z += (pz) + +#define j3ObjectScaleTo(ob, px, py, pz) \ + ob.scale.x = (px); \ + ob.scale.y = (py); \ + ob.scale.z = (pz) + + +// Syntatic sugar +#define j3DrawWireframe(o) _j3DrawWireframe(&(o)) +#define j3ObjectReset(o) _j3ObjectReset(&(o)) +#define j3ObjectUpdate(o) _j3ObjectUpdate(&(o)) +#define j3WorldFree(w) _j3WorldFree((j3WorldT **)&(w)) +#define j3WorldLoad(w, f) _j3WorldLoad((j3WorldT **)&(w), (f)) + + +// Prototypes +void j3MathMatrix4x4Identity(j3Matrix4x4T result); +void j3MathMatrix4x4Mult(j3Matrix4x4T a, j3Matrix4x4T b, j3Matrix4x4T result); +void j3UtilShutdown(void); +void j3UtilStartup(void); + + +// Private Prototypes +void _j3DrawWireframePair(j3ObjectT *o, int v1, int v2); +void _j3DrawWireframe(j3ObjectT *o); +void _j3ObjectReset(j3ObjectT *o); +void _j3ObjectUpdate(j3ObjectT *o); +bool _j3UtilClipLine(int *x1, int *y1, int *x2, int *y2); +void _j3WorldFree(j3WorldT **world); +bool _j3WorldLoad(j3WorldT **world, char *file); + + +#endif // H_J3D_ diff --git a/j3d.pro b/j3d.pro index 91c74bc..4d7dfb0 100644 --- a/j3d.pro +++ b/j3d.pro @@ -2,12 +2,15 @@ JOEY = /home/scott/joey include($$JOEY/dist/joey.pri) HEADERS += \ - utarray.h + stretchy_buffer.h \ + j3d.h SOURCES += \ - main.c + main.c \ + j3d.c OTHER_FILES += \ + build-All.sh \ postlink.sh \ notes.txt \ cube.obj diff --git a/main.c b/main.c index 070e4f8..9b300cd 100644 --- a/main.c +++ b/main.c @@ -1,136 +1,103 @@ -#include -#include +/* + * JoeyLib 3D + * Copyright (C) 2019 Scott Duensing + * + * 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. +*/ -#include "utarray.h" + +#include +#include #define JOEY_MAIN #include "joey.h" + #ifdef JOEY_IIGS -segment "j3d"; +segment "j3dTest"; #endif -#pragma GCC diagnostic push -//#pragma GCC diagnostic ignored "-Wcast-align" - -typedef struct { - float x; - float y; - float z; -} j3VertexT; - -typedef struct { - j3VertexT local[3]; - j3VertexT world[3]; - j3VertexT camera[3]; -} j3TriangleT; - -typedef struct { - UT_array *verticies; - UT_array *triangles; -} j3ObjectT; - -typedef struct { - UT_array *objects; -} j3WorldT; +#include "j3d.h" -static UT_icd j3VertexT_icd = { sizeof(j3VertexT), NULL, NULL, NULL }; -static UT_icd j3TriangleT_icd = { sizeof(j3TriangleT), NULL, NULL, NULL }; -static UT_icd j3WorldT_icd = { sizeof(j3WorldT), NULL, NULL, NULL }; +void printAt(jlStaT *font, jint16 x, jint16 y, char *string) { + juint16 i; + byte c; + jint16 sx = x; + jint16 tx; + jint16 ty; - -bool j3WorldLoad(char *file, j3WorldT *world); - - -bool j3WorldLoad(char *file, j3WorldT *world) { - int r; - int x; - char token[1024]; - char *c; - FILE *in; - j3VertexT *vp; - j3VertexT v; - j3ObjectT o; - j3TriangleT t; - - in = fopen(jlUtilMakePathname(file, "obj"), "rt"); - - // Did we find the file? - if (in == NULL) { - // Nope. - return false; + for (i=0; iobjects, &j3WorldT_icd); - - // Create an initial object to read data into (***TODO*** support multiple objects) - utarray_new(o.verticies, &j3VertexT_icd); - utarray_new(o.triangles, &j3TriangleT_icd); - - while (true) { - - // Read next token - r = fscanf(in, "%s", token); - //printf("fscanf s = %d [%s]\n", r, token); - - // End of file? - if (r == EOF) { - break; - } - - // Vertex? - if (strcmp(token, "v" ) == 0) { - r = fscanf(in, "%f %f %f\n", &v.x, &v.y, &v.z); - //printf("fscanf f f f = %d [%s]\n", r, token); - //printf("Vertex: %f %f %f\n", v.x, v.y, v.z); - utarray_push_back(o.verticies, &v); - } - - // Face? - if (strcmp(token, "f" ) == 0) { - //printf("Triangle:\n"); - for (x=0; x<3; x++) { - // Fetch 'x'th vertex index - r = fscanf(in, "%s", token); - //printf("fscanf s = %d [%s]\n", r, token); - c = strstr(token, "/"); - if (c) c[0] = 0; - r = atoi(token); - //printf("atoi = %d\n", r); - vp = (j3VertexT *)utarray_eltptr(o.verticies, (unsigned int)r - 1); - t.local[x] = *vp; - //printf(" Face Index: %s Vertex: %f %f %f\n", token, vp->x, vp->y, vp->z); - } - fscanf(in, "\n"); - utarray_push_back(o.triangles, &t); - } - } - - // Finished! Clean up. - fclose(in); - - return true; } int main(void) { - - j3WorldT world; - j3ObjectT *object; + jlStaT *font = NULL; + j3WorldT *world = NULL; + bool r; jlUtilStartup("JoeyLib 3D"); + jlStaLoad(font, "font"); - j3WorldLoad("cube", &world); + printAt(font, 1, 1, "Starting JoeyLib3D..."); + jlDisplayPresent(); + j3UtilStartup(); - printf("Made it here\n"); + printAt(font, 1, 1, "Loading object... "); + jlDisplayPresent(); + r = j3WorldLoad(world, "cube"); + if (r) { + printAt(font, 1, 1, "Object loading: Success!"); + jlDisplayPresent(); + } else { + printAt(font, 1, 1, "Object loading: Failed."); + jlDisplayPresent(); + } - object = (j3ObjectT *)utarray_front(world.objects); - printf("Verticies: %d\n", utarray_len(object->verticies)); - printf("Triangles: %d\n", utarray_len(object->triangles)); + //***TODO*** Add methods to inspect world/object information + //printf("Verticies: %d\n", sb_count(world->objects[0].verticies)); + //printf("Triangles: %d\n", sb_count(world->objects[0].triangles)); - jlKeyWaitForAny(); + j3ObjectMoveTo(world->objects[0], 0, 0, 300); // Matching values in code I'm studying + j3ObjectScaleTo(world->objects[0], 30, 30, 30); // Matching values in code I'm studying + + while (!jlKeyPressed() && !jlUtilMustExit()) { + j3ObjectRotate(world->objects[0], 2, 4, 6); // Matching values in code I'm studying + j3ObjectUpdate(world->objects[0]); + + jlDrawColor(0); + jlDrawClear(); + + jlDrawColor(15); + jlDrawBox(0, 0, 319, 199); + j3DrawWireframe(world->objects[0]); + + jlDisplayPresent(); + } + jlKeyRead(); + + j3WorldFree(world); + jlStaFree(font); + + j3UtilShutdown(); jlUtilShutdown(); } -#pragma GCC diagnostic pop diff --git a/postlink.sh b/postlink.sh index 4f92893..755d756 100755 --- a/postlink.sh +++ b/postlink.sh @@ -3,5 +3,10 @@ GAME=$1 export JOEY=$2 +pushd "${GAME}" +find . -type f -name "*.xcf" -exec ${JOEY}/utils/xcf2sta.sh {} \; +popd + mkdir -p data +cp -f "${GAME}"/*.sta data/. cp -f "${GAME}"/*.obj data/. diff --git a/stretchy_buffer.h b/stretchy_buffer.h new file mode 100644 index 0000000..cbd48a3 --- /dev/null +++ b/stretchy_buffer.h @@ -0,0 +1,262 @@ +// stretchy_buffer.h - v1.03 - public domain - nothings.org/stb +// a vector<>-like dynamic array for C +// +// version history: +// 1.03 - compile as C++ maybe +// 1.02 - tweaks to syntax for no good reason +// 1.01 - added a "common uses" documentation section +// 1.0 - fixed bug in the version I posted prematurely +// 0.9 - rewrite to try to avoid strict-aliasing optimization +// issues, but won't compile as C++ +// +// Will probably not work correctly with strict-aliasing optimizations. +// +// The idea: +// +// This implements an approximation to C++ vector<> for C, in that it +// provides a generic definition for dynamic arrays which you can +// still access in a typesafe way using arr[i] or *(arr+i). However, +// it is simply a convenience wrapper around the common idiom of +// of keeping a set of variables (in a struct or globals) which store +// - pointer to array +// - the length of the "in-use" part of the array +// - the current size of the allocated array +// +// I find it to be the single most useful non-built-in-structure when +// programming in C (hash tables a close second), but to be clear +// it lacks many of the capabilities of C++ vector<>: there is no +// range checking, the object address isn't stable (see next section +// for details), the set of methods available is small (although +// the file stb.h has another implementation of stretchy buffers +// called 'stb_arr' which provides more methods, e.g. for insertion +// and deletion). +// +// How to use: +// +// Unlike other stb header file libraries, there is no need to +// define an _IMPLEMENTATION symbol. Every #include creates as +// much implementation is needed. +// +// stretchy_buffer.h does not define any types, so you do not +// need to #include it to before defining data types that are +// stretchy buffers, only in files that *manipulate* stretchy +// buffers. +// +// If you want a stretchy buffer aka dynamic array containing +// objects of TYPE, declare such an array as: +// +// TYPE *myarray = NULL; +// +// (There is no typesafe way to distinguish between stretchy +// buffers and regular arrays/pointers; this is necessary to +// make ordinary array indexing work on these objects.) +// +// Unlike C++ vector<>, the stretchy_buffer has the same +// semantics as an object that you manually malloc and realloc. +// The pointer may relocate every time you add a new object +// to it, so you: +// +// 1. can't take long-term pointers to elements of the array +// 2. have to return the pointer from functions which might expand it +// (either as a return value or by storing it to a ptr-to-ptr) +// +// Now you can do the following things with this array: +// +// sb_free(TYPE *a) free the array +// sb_count(TYPE *a) the number of elements in the array +// sb_push(TYPE *a, TYPE v) adds v on the end of the array, a la push_back +// sb_add(TYPE *a, int n) adds n uninitialized elements at end of array & returns pointer to first added +// sb_last(TYPE *a) returns an lvalue of the last item in the array +// a[n] access the nth (counting from 0) element of the array +// +// #define STRETCHY_BUFFER_NO_SHORT_NAMES to only export +// names of the form 'stb_sb_' if you have a name that would +// otherwise collide. +// +// Note that these are all macros and many of them evaluate +// their arguments more than once, so the arguments should +// be side-effect-free. +// +// Note that 'TYPE *a' in sb_push and sb_add must be lvalues +// so that the library can overwrite the existing pointer if +// the object has to be reallocated. +// +// In an out-of-memory condition, the code will try to +// set up a null-pointer or otherwise-invalid-pointer +// exception to happen later. It's possible optimizing +// compilers could detect this write-to-null statically +// and optimize away some of the code, but it should only +// be along the failure path. Nevertheless, for more security +// in the face of such compilers, #define STRETCHY_BUFFER_OUT_OF_MEMORY +// to a statement such as assert(0) or exit(1) or something +// to force a failure when out-of-memory occurs. +// +// Common use: +// +// The main application for this is when building a list of +// things with an unknown quantity, either due to loading from +// a file or through a process which produces an unpredictable +// number. +// +// My most common idiom is something like: +// +// SomeStruct *arr = NULL; +// while (something) +// { +// SomeStruct new_one; +// new_one.whatever = whatever; +// new_one.whatup = whatup; +// new_one.foobar = barfoo; +// sb_push(arr, new_one); +// } +// +// and various closely-related factorings of that. For example, +// you might have several functions to create/init new SomeStructs, +// and if you use the above idiom, you might prefer to make them +// return structs rather than take non-const-pointers-to-structs, +// so you can do things like: +// +// SomeStruct *arr = NULL; +// while (something) +// { +// if (case_A) { +// sb_push(arr, some_func1()); +// } else if (case_B) { +// sb_push(arr, some_func2()); +// } else { +// sb_push(arr, some_func3()); +// } +// } +// +// Note that the above relies on the fact that sb_push doesn't +// evaluate its second argument more than once. The macros do +// evaluate the *array* argument multiple times, and numeric +// arguments may be evaluated multiple times, but you can rely +// on the second argument of sb_push being evaluated only once. +// +// Of course, you don't have to store bare objects in the array; +// if you need the objects to have stable pointers, store an array +// of pointers instead: +// +// SomeStruct **arr = NULL; +// while (something) +// { +// SomeStruct *new_one = malloc(sizeof(*new_one)); +// new_one->whatever = whatever; +// new_one->whatup = whatup; +// new_one->foobar = barfoo; +// sb_push(arr, new_one); +// } +// +// How it works: +// +// A long-standing tradition in things like malloc implementations +// is to store extra data before the beginning of the block returned +// to the user. The stretchy buffer implementation here uses the +// same trick; the current-count and current-allocation-size are +// stored before the beginning of the array returned to the user. +// (This means you can't directly free() the pointer, because the +// allocated pointer is different from the type-safe pointer provided +// to the user.) +// +// The details are trivial and implementation is straightforward; +// the main trick is in realizing in the first place that it's +// possible to do this in a generic, type-safe way in C. +// +// Contributors: +// +// Timothy Wright (github:ZenToad) +// +// LICENSE +// +// See end of file for license information. + +#ifndef STB_STRETCHY_BUFFER_H_INCLUDED +#define STB_STRETCHY_BUFFER_H_INCLUDED + +#ifndef NO_STRETCHY_BUFFER_SHORT_NAMES +#define sb_free stb_sb_free +#define sb_push stb_sb_push +#define sb_count stb_sb_count +#define sb_add stb_sb_add +#define sb_last stb_sb_last +#endif + +#define stb_sb_free(a) ((a) ? free(stb__sbraw(a)),0 : 0) +#define stb_sb_push(a,v) (stb__sbmaybegrow(a,1), (a)[stb__sbn(a)++] = (v)) +#define stb_sb_count(a) ((a) ? stb__sbn(a) : 0) +#define stb_sb_add(a,n) (stb__sbmaybegrow(a,n), stb__sbn(a)+=(n), &(a)[stb__sbn(a)-(n)]) +#define stb_sb_last(a) ((a)[stb__sbn(a)-1]) + +#define stb__sbraw(a) ((int *) (a) - 2) +#define stb__sbm(a) stb__sbraw(a)[0] +#define stb__sbn(a) stb__sbraw(a)[1] + +#define stb__sbneedgrow(a,n) ((a)==0 || stb__sbn(a)+(n) >= stb__sbm(a)) +#define stb__sbmaybegrow(a,n) (stb__sbneedgrow(a,(n)) ? stb__sbgrow(a,n) : 0) +#define stb__sbgrow(a,n) (*((void **)&(a)) = stb__sbgrowf((a), (n), sizeof(*(a)))) + +#include + +static void * stb__sbgrowf(void *arr, int increment, int itemsize) +{ + int dbl_cur = arr ? 2*stb__sbm(arr) : 0; + int min_needed = stb_sb_count(arr) + increment; + int m = dbl_cur > min_needed ? dbl_cur : min_needed; + int *p = (int *) realloc(arr ? stb__sbraw(arr) : 0, itemsize * m + sizeof(int)*2); + if (p) { + if (!arr) + p[1] = 0; + p[0] = m; + return p+2; + } else { + #ifdef STRETCHY_BUFFER_OUT_OF_MEMORY + STRETCHY_BUFFER_OUT_OF_MEMORY ; + #endif + return (void *) (2*sizeof(int)); // try to force a NULL pointer exception later + } +} +#endif // STB_STRETCHY_BUFFER_H_INCLUDED + + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/