Basic wireframe rendering on PC. IIgs not working yet.

This commit is contained in:
Scott Duensing 2019-08-26 21:15:39 -05:00
parent 38bf774214
commit 06cc056bc5
10 changed files with 1152 additions and 115 deletions

1
.gitattributes vendored Normal file
View file

@ -0,0 +1 @@
font.xcf filter=lfs diff=lfs merge=lfs -text

7
.gitignore vendored
View file

@ -1,2 +1,7 @@
*.user
*.~ *.~
*.user
*.sta
*.dis
*.lnk
*.map
*.sym

23
build-All.sh Executable file
View file

@ -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

BIN
font.xcf (Stored with Git LFS) Normal file

Binary file not shown.

635
j3d.c Normal file
View file

@ -0,0 +1,635 @@
/*
* 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
#include <math.h>
#include <stdio.h>
#include <float.h>
#include <string.h>
#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; 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 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; 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 ===
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?
for (i=0; i<o->vertexCount; 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 (((*x1<clipMinX) && (*x2<clipMinX)) || // left
((*x1>clipMaxX) && (*x2>clipMaxX)) || // right
((*y1<clipMinY) && (*y2<clipMinY)) || // above
((*y1>clipMaxY) && (*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; x<sb_count((*world)->objects); 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

133
j3d.h Normal file
View file

@ -0,0 +1,133 @@
/*
* 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.
*/
#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_

View file

@ -2,12 +2,15 @@ JOEY = /home/scott/joey
include($$JOEY/dist/joey.pri) include($$JOEY/dist/joey.pri)
HEADERS += \ HEADERS += \
utarray.h stretchy_buffer.h \
j3d.h
SOURCES += \ SOURCES += \
main.c main.c \
j3d.c
OTHER_FILES += \ OTHER_FILES += \
build-All.sh \
postlink.sh \ postlink.sh \
notes.txt \ notes.txt \
cube.obj cube.obj

191
main.c
View file

@ -1,136 +1,103 @@
#include <stdio.h> /*
#include <math.h> * 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.
*/
#include "utarray.h"
#include <stdio.h>
#include <string.h>
#define JOEY_MAIN #define JOEY_MAIN
#include "joey.h" #include "joey.h"
#ifdef JOEY_IIGS #ifdef JOEY_IIGS
segment "j3d"; segment "j3dTest";
#endif #endif
#pragma GCC diagnostic push #include "j3d.h"
//#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;
static UT_icd j3VertexT_icd = { sizeof(j3VertexT), NULL, NULL, NULL }; void printAt(jlStaT *font, jint16 x, jint16 y, char *string) {
static UT_icd j3TriangleT_icd = { sizeof(j3TriangleT), NULL, NULL, NULL }; juint16 i;
static UT_icd j3WorldT_icd = { sizeof(j3WorldT), NULL, NULL, NULL }; byte c;
jint16 sx = x;
jint16 tx;
jint16 ty;
for (i=0; i<strlen(string); i++) {
bool j3WorldLoad(char *file, j3WorldT *world); c = (byte)string[i];
tx = c % 40;
ty = c / 40;
bool j3WorldLoad(char *file, j3WorldT *world) { jlDrawBlit8x8(font, tx, ty, sx++, y);
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;
} }
// Initialize object array inside world object
utarray_new(world->objects, &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) { int main(void) {
jlStaT *font = NULL;
j3WorldT world; j3WorldT *world = NULL;
j3ObjectT *object; bool r;
jlUtilStartup("JoeyLib 3D"); 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); //***TODO*** Add methods to inspect world/object information
printf("Verticies: %d\n", utarray_len(object->verticies)); //printf("Verticies: %d\n", sb_count(world->objects[0].verticies));
printf("Triangles: %d\n", utarray_len(object->triangles)); //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(); jlUtilShutdown();
} }
#pragma GCC diagnostic pop

View file

@ -3,5 +3,10 @@
GAME=$1 GAME=$1
export JOEY=$2 export JOEY=$2
pushd "${GAME}"
find . -type f -name "*.xcf" -exec ${JOEY}/utils/xcf2sta.sh {} \;
popd
mkdir -p data mkdir -p data
cp -f "${GAME}"/*.sta data/.
cp -f "${GAME}"/*.obj data/. cp -f "${GAME}"/*.obj data/.

262
stretchy_buffer.h Normal file
View file

@ -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 <stdlib.h>
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.
------------------------------------------------------------------------------
*/