Basic wireframe rendering on PC. IIgs not working yet.
This commit is contained in:
parent
38bf774214
commit
06cc056bc5
10 changed files with 1152 additions and 115 deletions
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
font.xcf filter=lfs diff=lfs merge=lfs -text
|
7
.gitignore
vendored
7
.gitignore
vendored
|
@ -1,2 +1,7 @@
|
|||
*.user
|
||||
*.~
|
||||
*.user
|
||||
*.sta
|
||||
*.dis
|
||||
*.lnk
|
||||
*.map
|
||||
*.sym
|
||||
|
|
23
build-All.sh
Executable file
23
build-All.sh
Executable 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
BIN
font.xcf
(Stored with Git LFS)
Normal file
Binary file not shown.
635
j3d.c
Normal file
635
j3d.c
Normal 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
133
j3d.h
Normal 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_
|
7
j3d.pro
7
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
|
||||
|
|
191
main.c
191
main.c
|
@ -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
|
||||
#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; i<strlen(string); i++) {
|
||||
c = (byte)string[i];
|
||||
tx = c % 40;
|
||||
ty = c / 40;
|
||||
jlDrawBlit8x8(font, tx, ty, sx++, y);
|
||||
}
|
||||
|
||||
// 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) {
|
||||
|
||||
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
|
||||
|
|
|
@ -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/.
|
||||
|
|
262
stretchy_buffer.h
Normal file
262
stretchy_buffer.h
Normal 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.
|
||||
------------------------------------------------------------------------------
|
||||
*/
|
Loading…
Add table
Reference in a new issue