blob: 0fb970941d7aec02c187c5d93dc1efd2ac061278 [file] [log] [blame]
#include <stdio.h>
#include <math.h>
#include <stdlib.h>
#include <sys/time.h>
#include <unistd.h>
/* Simple raytracer
* ----------------
* Features reflections, anti-aliasing
* and soft-shadows.
*
* Written by Marcus Geelnard, benchmarkified by
* Peter Rundberg, biff@ce.chalmers.se
*/
#define WIDTH 640
#define HEIGHT 480
#define EPSILON (1e-5) /* Very small value, used for coordinate-comparsions */
#define MAXT (1e5) /* Maximum t-distance for an intersection-point */
#define MAXREC 6 /* Maximum amount of recursions (reflection etc.) */
/* #define DISTRIB 12 */ /* Amount of distributed rays per "virtual" ray */
int DISTRIB;
#define DISTLEVELS 3 /* How deep in the recursion-tree to allow distribution */
typedef unsigned char UBYTE;
typedef struct { double x,y,z; } VECTOR;
UBYTE memory[3*WIDTH*HEIGHT];
typedef struct {
VECTOR color; /* Object color (r,g,b) */
double diffuse; /* Diffuse reflection (0-1) */
double reflect; /* Relefction (0-1) */
double roughness; /* How rough the reflection is (0=very sharp) */
} TEXTURE;
typedef struct {
VECTOR pos; /* Position (x,y,z) */
double r; /* Radius (or size) */
TEXTURE t; /* Texture */
} OBJ;
/*
* Objects ( = spheres ). Only one sphere. Add more if you like :)
*/
OBJ objs[] = {
/* Object 1 */
{
{ 0, 4, 1.0 }, 1,
{
{ 1.0, 0.4, 0.0 },
0.4,
0.8,
0.02
}
},
/* Object 2 */
{
{ -1, 3, 0.4 }, 0.4,
{
{ 0.5, 0.3, 1.0 },
0.5,
0.9,
0.01
}
},
/* Object 3 */
{
{ -0.3, 1, 0.4 }, 0.4,
{
{ 0.1, 0.95, 0.2 },
0.6,
0.8,
0.01
}
},
/* Object 4 */
{
{ 1.0, 2, 0.4 }, 0.4,
{
{ 0.86, 0.83, 0 },
0.7,
0.6,
0.01
}
}
};
#define NUMOBJS 4
/*
* Ground position (z-pos), and textures (tiled)
*/
double Groundpos = 0.0;
TEXTURE Groundtxt[2] = {
{
{ 0.0, 0.1, 0.5 },
0.8,
0.44,
0.02
},
{
{ 0.6, 1.0, 0.5 },
0.8,
0.44,
0.01
},
};
/*
* Only one light-source is supported (and it's white).
*/
VECTOR Lightpos = {-3.0, 1.0, 5.0};
double Lightr = 0.4; /* Light-radius (for soft shadows) */
/*
* The camera position (x,y,z), and orientation.
*/
VECTOR Camerapos = {1.5, -1.4, 1.2};
VECTOR Cameraright = {3.0, 1.0, 0.0};
VECTOR Cameradir = {-1.0, 3.0, 0.0};
VECTOR Cameraup = {0.0, 0.0, 2.3717};
/*
* Ambient lighting (0.0-1.0)
*/
double Ambient = 0.3;
/*
* Skycolors (Skycolor[0] = horizon, Skycolor[1] = zenit )
*/
VECTOR Skycolor[2] = { { 0.5, 0.3, 0.7 }, { 0.0, 0.0, 0.2 } };
/**************************************************************************
*
* Helpers (geometrical etc).
*
**************************************************************************/
unsigned long rnd = 0x52462467L;
static double Jitter( void )
{
rnd = (1103515245L*rnd + 12345L) & 0x7fffffffL;
return( 1.0-((double)rnd/(double)0x3fffffff) );
}
static void ReflectVector( VECTOR *v2, VECTOR *v1, VECTOR *n )
{
double a, b;
b = n->x*n->x + n->y*n->y + n->z*n->z; /* b = |n|^2 */
a = v1->x*n->x + v1->y*n->y + v1->z*n->z; /* a = v1·n */
a = -2.0 * a / b; /* a = -2*(v1·n)/|n|^2 */
v2->x = v1->x + a*n->x; /* v2 = v1 + n*a */
v2->y = v1->y + a*n->y;
v2->z = v1->z + a*n->z;
}
static double VectorLength( VECTOR *v )
{
return( sqrt( v->x*v->x + v->y*v->y + v->z*v->z ) );
}
static void ScaleVector( VECTOR *v, double s )
{
v->x *= s; v->y *= s; v->z *= s;
}
static void DistribVector( VECTOR *d, VECTOR *n, double sa, double sb )
{
VECTOR a, b;
double nl;
if( fabs( n->z ) > EPSILON ) {
a.x = n->y*n->z; a.y = -n->x*n->z; a.z = 0.0;
b.x = a.y*n->z; b.y = -a.x*n->z; b.z = a.x*n->y - a.y*n->x;
} else {
a.x = n->y; a.y = -n->x; a.z = 0.0;
b.x = b.y = 0.0; b.z = 1.0;
}
nl = VectorLength( n );
ScaleVector( &a, sa*(nl/VectorLength( &a ))*Jitter() );
ScaleVector( &b, sb*(nl/VectorLength( &b ))*Jitter() );
d->x = a.x+b.x; d->y = a.y+b.y; d->z = a.z+b.z;
}
/**************************************************************************
*
* Object intersection calculation routines.
*
**************************************************************************/
static double IntersectObjs( VECTOR *LinP, VECTOR *LinD,
VECTOR *Pnt, VECTOR *Norm, TEXTURE **txt )
{
int objn, tilenum;
double t, ttmp, A, B, C;
VECTOR Pos;
t = -1.0;
/* Try intersection with ground plane first */
if( fabs(LinD->z) > EPSILON ) {
ttmp = (Groundpos - LinP->z)/LinD->z;
if( ( ttmp > EPSILON ) && ( ttmp < MAXT ) ) {
t = ttmp;
Pnt->x = LinP->x + LinD->x*t; /* Calculate intersection point */
Pnt->y = LinP->y + LinD->y*t;
Pnt->z = LinP->z + LinD->z*t;
Norm->x = 0.0; /* Surface normal (always up) */
Norm->y = 0.0;
Norm->z = 1.0;
tilenum = ( ((int)(Pnt->x+50000.0))+((int)(Pnt->y+50000.0)) ) & 1;
*txt = & Groundtxt[ tilenum ];
}
}
/* Get closest intersection (if any) */
for( objn = 0; objn < NUMOBJS; objn++ ) {
Pos = objs[objn].pos;
Pos.x -= LinP->x; /* Translate object into "line-space" */
Pos.y -= LinP->y;
Pos.z -= LinP->z;
A = 1.0 / (LinD->x*LinD->x + LinD->y*LinD->y + LinD->z*LinD->z);
B = (Pos.x*LinD->x + Pos.y*LinD->y + Pos.z*LinD->z) * A;
C = (objs[objn].r*objs[objn].r - Pos.x*Pos.x - Pos.y*Pos.y - Pos.z*Pos.z) * A;
if( (A = C + B*B) > 0.0 ) { /* ...else no hit */
A = sqrt(A);
if( (ttmp = B - A) < EPSILON ) ttmp = B + A;
if( (EPSILON<ttmp) && ( (ttmp<t)||(t<0.0) ) ) {
t = ttmp;
Pnt->x = LinD->x*t; /* Calculate intersection point */
Pnt->y = LinD->y*t;
Pnt->z = LinD->z*t;
Norm->x = Pnt->x-Pos.x; /* Calcualate surface normal */
Norm->y = Pnt->y-Pos.y;
Norm->z = Pnt->z-Pos.z;
Pnt->x += LinP->x; /* Translate object back to "true-space" */
Pnt->y += LinP->y;
Pnt->z += LinP->z;
*txt = &objs[objn].t; /* Get surface properties */
}
}
}
return( t );
}
/**************************************************************************
*
* Line-tracer routine (works recursively).
*
**************************************************************************/
static void TraceLine( VECTOR *LinP, VECTOR *LinD, VECTOR *Color, int reccount )
{
VECTOR Pnt, Norm, LDir, NewDir, NewDir2, TmpCol, TmpCol2;
VECTOR TmpPnt, TmpNorm, D;
double t, A, cosfi;
TEXTURE *txt, *tmptxt;
int i, shadowcount, usedist;
Color->x = Color->y = Color->z = 0.0;
if( reccount > 0 ) {
/* Only use distributed tracing in higher nodes of the recursion tree */
usedist = ( (MAXREC-reccount) < DISTLEVELS ) ? 1 : 0;
/* Try intersection with objects */
t = IntersectObjs( LinP, LinD, &Pnt, &Norm, &txt );
/* Get light-intensity in intersection-point (store in cosfi) */
if( t > EPSILON ) {
LDir.x = Lightpos.x-Pnt.x; /* Get line to light from surface */
LDir.y = Lightpos.y-Pnt.y;
LDir.z = Lightpos.z-Pnt.z;
cosfi = LDir.x*Norm.x + LDir.y*Norm.y + LDir.z*Norm.z;
if(cosfi > 0.0) { /* If angle between lightline and normal < PI/2 */
shadowcount = 0;
if( usedist ) {
A = Lightr / VectorLength( &LDir );
for( i = 0; i < DISTRIB; i++ ) {
DistribVector( &D, &LDir, A, A );
NewDir = LDir;
NewDir.x += D.x; NewDir.y += D.y; NewDir.z += D.z;
/* Check for shadows (ignore hit info, may be used though) */
t = IntersectObjs( &Pnt, &NewDir, &TmpPnt, &TmpNorm, &tmptxt );
if( ( t < EPSILON ) || ( t > 1.0 ) ) shadowcount++;
}
} else {
t = IntersectObjs( &Pnt, &LDir, &TmpPnt, &TmpNorm, &tmptxt );
if( ( t < EPSILON ) || ( t > 1.0 ) ) shadowcount = DISTRIB;
}
if( shadowcount > 0 ) {
A = Norm.x*Norm.x + Norm.y*Norm.y + Norm.z*Norm.z;
A *= LDir.x*LDir.x + LDir.y*LDir.y + LDir.z*LDir.z;
cosfi = (cosfi/sqrt(A))*txt->diffuse*(double)shadowcount/DISTRIB;
} else {
cosfi = 0.0;
}
} else {
cosfi = 0.0;
}
Color->x = txt->color.x*(Ambient+cosfi);
Color->y = txt->color.y*(Ambient+cosfi);
Color->z = txt->color.z*(Ambient+cosfi);
if( txt->reflect > EPSILON ) {
ReflectVector( &NewDir, LinD, &Norm );
TmpCol.x = TmpCol.y = TmpCol.z = 0.0;
if( usedist && ( txt->roughness > EPSILON ) ) {
for( i = 0; i < DISTRIB; i++ ) {
DistribVector( &D, &NewDir, txt->roughness, txt->roughness );
NewDir2 = NewDir;
NewDir2.x += D.x; NewDir2.y += D.y; NewDir2.z += D.z;
TraceLine( &Pnt, &NewDir2, &TmpCol2, reccount-1 );
TmpCol.x += TmpCol2.x;
TmpCol.y += TmpCol2.y;
TmpCol.z += TmpCol2.z;
}
ScaleVector( &TmpCol, 1.0/DISTRIB );
} else {
TraceLine( &Pnt, &NewDir, &TmpCol, reccount-1 );
}
Color->x += TmpCol.x * txt->reflect;
Color->y += TmpCol.y * txt->reflect;
Color->z += TmpCol.z * txt->reflect;
}
} else {
/* Get sky-color (interpolate between horizon and zenit) */
A = sqrt( LinD->x*LinD->x + LinD->y*LinD->y );
if( A > 0.0 ) A = atan( fabs( LinD->z ) / A )*0.63661977;
else A = 1.0;
Color->x = Skycolor[1].x*A + Skycolor[0].x*(1.0-A);
Color->y = Skycolor[1].y*A + Skycolor[0].y*(1.0-A);
Color->z = Skycolor[1].z*A + Skycolor[0].z*(1.0-A);
}
/* Make sure that the color does not exceed the maximum level */
if(Color->x > 1.0) Color->x = 1.0;
if(Color->y > 1.0) Color->y = 1.0;
if(Color->z > 1.0) Color->z = 1.0;
}
}
static void TraceScene(void)
{
VECTOR PixColor, Col, LinD, Scale;
VECTOR LinD2, D;
int sx, sy, i;
Scale.y = 1.0;
for( sy = 0; sy < HEIGHT; sy++ ) {
Scale.z = ((double)(HEIGHT/2-sy))/(double)HEIGHT;
for( sx = 0; sx < WIDTH; sx++ ) {
Scale.x = ((double)(sx-WIDTH/2))/(double)WIDTH;
/* Calculate line-direction (from camera-center through a pixel) */
LinD.x = Cameraright.x*Scale.x + Cameradir.x*Scale.y + Cameraup.x*Scale.z;
LinD.y = Cameraright.y*Scale.x + Cameradir.y*Scale.y + Cameraup.y*Scale.z;
LinD.z = Cameraright.z*Scale.x + Cameradir.z*Scale.y + Cameraup.z*Scale.z;
/* Get color for pixel */
#if (DISTLEVELS > 0)
PixColor.x = PixColor.y = PixColor.z = 0.0;
for( i = 0; i < DISTRIB; i++ ) {
DistribVector( &D, &LinD, 0.5/(double)WIDTH, 0.5/(double)HEIGHT );
LinD2 = LinD; LinD2.x += D.x; LinD2.y += D.y; LinD2.z += D.z;
TraceLine( &Camerapos, &LinD2, &Col, MAXREC );
PixColor.x += Col.x;
PixColor.y += Col.y;
PixColor.z += Col.z;
}
ScaleVector( &PixColor, 1.0/DISTRIB );
#else
TraceLine( &Camerapos, &LinD, &PixColor, MAXREC );
#endif
memory[3*(sx+sy*WIDTH)]=(UBYTE)(PixColor.x*255.0);
memory[3*(sx+sy*WIDTH)+1]=(UBYTE)(PixColor.y*255.0);
memory[3*(sx+sy*WIDTH)+2]=(UBYTE)(PixColor.z*255.0);
}
}
}
/**************************************************************************
*
* main() - Camera emulation and picture output to stdout.
*
**************************************************************************/
int main(int c, char *v[])
{
int i;
FILE *in_fp;
fprintf(stderr,"Compile date: %s\n", COMPDATE);
fprintf(stderr,"Compiler switches: %s\n", CFLAGS);
in_fp=fopen(v[1],"r");
if (!in_fp) {
printf("ERROR: Could not open indata file\n");
exit(1);
}
fscanf(in_fp,"%d",&DISTRIB);
fclose(in_fp);
/* End of Benchmark stuff */
/* Write PPM header to stdout */
fprintf( stdout, "P6" ); fputc( 10, stdout );
fprintf( stdout, "%d %d", WIDTH, HEIGHT ); fputc( 10, stdout );
fprintf( stdout, "255" ); fputc( 10, stdout );
/***...calculate image...***/
TraceScene();
/***...write image to stdout...***/
for (i=0 ; i<3*WIDTH*HEIGHT ; ) {
fputc( memory[i++]&~1, stdout);
fputc( memory[i++]&~1, stdout);
fputc( memory[i++]&~1, stdout);
}
return 0; /***...ANSI C wants main to return an int...***/
}