#define PC

#include <stdlib.h>
#include <math.h>
#include <stdio.h>

#ifdef PC
    #include <dos.h>
    #include <sys\nearptr.h>
    #include <conio.h>
    #include <sys\types.h>
    #include <sys\stat.h>
#else
    #include <sys/types.h>
    #include <sys/stat.h>

    #define PI 3.141592654

#endif

#include <string.h>
#include <time.h>

/*  Definitions:
    Sectioned
*/

//  Solid Numbers
#define BOX     0
#define PLANE   1
#define QUADRIC 2

//  Map Numbers
#define CONSTANT    0
#define CYLINDRICAL 1
#define HYBRID      2
#define NORMAL      3
#define PLANAR      4
#define SPHERICAL   5

// Wavelengths
#define lUltraViolet    340.0
#define lBlue           460.0
#define lGreen          520.0
#define lRed            700.0
#define lInfraRed       1000.0

/*  Classes & Member functions:
    Heavily heierachial
    Operators not overloaded to improve efficency
    Trivial functions inlined
*/

// Prototypes to prevent problems
class fColour;
class Bitmap;
class Projection;
class Arena;
extern Arena Scene;

class Quality {
    public:
    unsigned char
        Aberration,
        //Doppler,
        //Intensity,
        Sky,
        HUD,
        Interpolation,
        AntiAliasing,
        Padding[3];
    long SkyIndex;
    float AAThreshold;
    float Intensity;
    float Doppler;
    float HUDScale;
    Projection *HUDView;
    Bitmap *HUDMap;
    };

Quality Globals;

// Integer colours
class iColour {
    public:
    unsigned char b, g, r;
    inline fColour Convert(void);
    };

// Float colours
class fColour {
    public:
    float b, g, r, a;
    inline iColour Convert(void);
    };

// Integer to float
inline fColour iColour::Convert(void) {
    fColour c;
    c.b = (float) b;
    c.g = (float) g;
    c.r = (float) r;
    c.a = (float) 0.0;
    return c;
    }

// Float to integer;
inline iColour fColour::Convert(void) {
    iColour c;
    if(b < 255.0) {
        c.b = (unsigned char) b;
        }
    else {
        c.b = 255;
        }
    if(g < 255.0) {
        c.g = (unsigned char) g;
        }
    else {
        c.g = 255;
        }
    if(r < 255.0) {
        c.r = (unsigned char) r;
        }
    else {
        c.r = 255;
        }
    return c;
    }

// VGA Display. ONLY CALL ON IBM-COMPATIBLE PCS

#ifdef PC

class Display {
    public:
    unsigned char *Real;
    void SetMode(void);
    inline void SetPage(short p);
    inline void SetPixel(long u, long v, iColour c);
    void ResetMode(void);
    };

void Display::SetMode(void) {
    union REGS r;

    r.w.ax = 0x4F02;
    r.w.bx = 0x0118;
    int386(0x10, &r, &r);

    __djgpp_nearptr_enable();
    Real = (unsigned char *) (__djgpp_conventional_base + 0xa0000);
    }

inline void Display::SetPage(short p) {
    union REGS r;
    r.w.ax = 0x4F05;
    r.w.bx = 0;
    r.w.dx = p;
    int386(0x10, &r, &r);
    }

inline void Display::SetPixel(long u, long v, iColour c) {
    long a;
    a = (v * 4 * 1024) + (u * 4);
    SetPage(a >> 16);
    a &= 0x0000FFFF;
    Real[a++] = c.b;
    Real[a++] = c.g;
    Real[a] = c.r;
    }

void Display::ResetMode(void) {
    union REGS r;
    SetPage(0);
    r.x.ax = 0x0003;
    int86(0x10, &r, &r);
    }

Display Screen;

#endif

// 24 bit colour bitmaps (truevision Targa)

class Bitmap {
    public:
    unsigned long uMax, vMax;
    iColour *Pixel;
    void Initialise(long u, long v);
    void LoadTGA(char *Name);
    void SaveTGA(char *Name);
    void ChromaKey(long u, long v, Bitmap &Insert, long iu0, long iv0, long iu1, long iv1);
    fColour SmoothColour(float u, float v);
    fColour Colour(long u, long v);
    void Line(long u1, long v1, long u2, long v2, iColour c);
    };

void Bitmap::Initialise(long u, long v) {
    uMax = u;
    vMax = v;
    Pixel = new iColour[uMax * vMax];
    }

void Bitmap::LoadTGA(char *Name) {
    FILE *Handle;
    unsigned char Header[18];
    Handle = fopen(Name, "rb");
    if(Handle == NULL) {
        printf("Error: can't open %.32s\n", Name);
        exit(EXIT_FAILURE);
        }

    fseek(Handle, 0, 0);
    fread(Header, 1, 18, Handle);
    uMax = ((long) Header[13] << 8) + Header[12];
    vMax = ((long) Header[15] << 8) + Header[14];

    printf("Loading %.32s (%li x %li)\n", Name, uMax, vMax);

    Pixel = new iColour[uMax * vMax];
    fseek(Handle, 18, 0);
    fread(Pixel, 3, uMax * vMax, Handle);
    fclose(Handle);
    }

void Bitmap::SaveTGA(char *Name) {
    FILE *Handle;
    unsigned char Header[18];

    Header[0] = 0;
    Header[1] = 0;
    Header[2] = 2;
    Header[3] = 0;
    Header[4] = 0;
    Header[5] = 0;
    Header[6] = 0;
    Header[7] = 0;
    Header[8] = 0;
    Header[9] = 0;
    Header[10] = 0;
    Header[11] = 0;
    Header[12] = uMax;
    Header[13] = ((long) uMax >> 8);
    Header[14] = vMax;
    Header[15] = ((long) vMax >> 8);
    Header[16] = 24;
    Header[17] = 0;

    Handle = fopen(Name, "wb");
    fseek(Handle, 0, 0);
    fwrite(Header, 1, 18, Handle);
    fseek(Handle, 18, 0);
    fwrite(Pixel, 3, uMax * vMax, Handle);
    fclose(Handle);

    #ifndef PC
        printf("%.32s successfully saved.\n", Name);
    #endif
    }

void Bitmap::ChromaKey(long u, long v, Bitmap &Insert, long iu0, long iv0, long iu1, long iv1) {
    iColour c, r;
    long i, j, k, l;

    r.b = 0;
    r.g = 0;
    r.r = 255;

    j = vMax - v - 1;
    l = Insert.vMax - iv0;
    do {
        l--;
        i = u;
        k = iu0;
        do {
            c = Insert.Pixel[k + l*Insert.uMax];
            if((c.b == r.b) && (c.g == r.g) && (c.r == r.r)) {
                }
            else {
                c.b = (long) ((long) c.b + Pixel[i + j*uMax].b) >> 1;
                c.g = (long) ((long) c.g + Pixel[i + j*uMax].g) >> 1;
                c.r = (long) ((long) c.r + Pixel[i + j*uMax].r) >> 1;
                Pixel[i + j*uMax] = c;
                #ifdef PC
		    Screen.SetPixel(i, vMax - j - 1, c);
                #endif
		}
            i++;
            k++;
            } while(k < iu1);
        j--;
        } while(l > Insert.vMax - iv1);

    }

fColour Bitmap::SmoothColour(float u, float v) {
    fColour Smoothed;
    iColour c00, c01, c10, c11;
    float du, dv;
    unsigned long iu, iv;

    u = fmod(u, 1.0);
    v = fmod(v, 1.0);

    if(u < 0.0) {
        u += 1.0;
        }
    if(v < 0.0) {
        v += 1.0;
        }

    // Integer coordinates
    iu = (long) ((float) u * uMax);
    iv = (long) ((float) v * vMax);

    c00 = Pixel[iu + (iv * uMax)];

    if(Globals.Interpolation) {
        // Fractional coordinates
        du = fmod(u * uMax, 1.0);
        dv = fmod(v * vMax, 1.0);
    
        if(iu < uMax - 1) {
           c10 = Pixel[(iu + 1) + (iv * uMax)];
            if(iv < vMax - 1) {
                c01 = Pixel[iu + ((iv + 1) * uMax)];
                c11 = Pixel[(iu + 1) + ((iv + 1) * uMax)];
                }
            else {
                c01 = Pixel[iu];
                c11 = Pixel[iu + 1];
                }
            }
        else {
            c10 = Pixel[iv * uMax];
            if(iv < vMax - 1) {
                c01 = Pixel[iu + ((iv + 1) * uMax)];
                c11 = Pixel[(iv + 1) * uMax];
                }
            else {
                c01 = Pixel[iu];
                c11 = Pixel[0];
                }
            }
        
        Smoothed.b = c00.b*(1-du)*(1-dv)
                   + c01.b*(1-du)*dv
                   + c10.b*du*(1-dv)
                   + c11.b*du*dv;
        Smoothed.g = c00.g*(1-du)*(1-dv)
                   + c01.g*(1-du)*dv
                   + c10.g*du*(1-dv)
                   + c11.g*du*dv;
        Smoothed.r = c00.r*(1-du)*(1-dv)
                   + c01.r*(1-du)*dv
                   + c10.r*du*(1-dv)
                   + c11.r*du*dv;

        return Smoothed;
        }
    else {
        return c00.Convert();
        }
    }

fColour Bitmap::Colour(long u, long v) {
    return Pixel[u + v*uMax].Convert();
    }


void Bitmap::Line(long u1, long v1, long u2, long v2, iColour c) {
    float u, v, du, dv;
    long i, j;

    du = (float) (u2 - u1); // Stops pathological cases
    dv = (float) (v2 - v1);

    i = (long) fabs(dv);
    if(fabs(du) > fabs(dv)) {
        i = (long) fabs(du);
        }

    if(i == 0) {
        i = 1;
        }

    du /= (float) i;
    dv /= (float) i;

    u = u1 - 1;
    v = v1;
    j = i;
    do {
        Pixel[((long) u) + (vMax - 1 - (long) v) * uMax].r >>= 1;
        Pixel[((long) u) + (vMax - 1 - (long) v) * uMax].g >>= 1;
        Pixel[((long) u) + (vMax - 1 - (long) v) * uMax].b >>= 1;
        Pixel[((long) u) + (vMax - 1 - (long) v) * uMax].r += c.r >> 1;
        Pixel[((long) u) + (vMax - 1 - (long) v) * uMax].g += c.g >> 1;
        Pixel[((long) u) + (vMax - 1 - (long) v) * uMax].b += c.b >> 1;
        Screen.SetPixel((long) u, (long) v, c);
        u += du;
        v += dv;
        } while(j--);
    u = u1 + 1;
    v = v1;
    j = i;
    do {
        Pixel[((long) u) + (vMax - 1 - (long) v) * uMax].r >>= 1;
        Pixel[((long) u) + (vMax - 1 - (long) v) * uMax].g >>= 1;
        Pixel[((long) u) + (vMax - 1 - (long) v) * uMax].b >>= 1;
        Pixel[((long) u) + (vMax - 1 - (long) v) * uMax].r += c.r >> 1;
        Pixel[((long) u) + (vMax - 1 - (long) v) * uMax].g += c.g >> 1;
        Pixel[((long) u) + (vMax - 1 - (long) v) * uMax].b += c.b >> 1;
        Screen.SetPixel((long) u, (long) v, c);
        u += du;
        v += dv;
        } while(j--);
    u = u1;
    v = v1 - 1;
    j = i;
    do {
        Pixel[((long) u) + (vMax - 1 - (long) v) * uMax].r >>= 1;
        Pixel[((long) u) + (vMax - 1 - (long) v) * uMax].g >>= 1;
        Pixel[((long) u) + (vMax - 1 - (long) v) * uMax].b >>= 1;
        Pixel[((long) u) + (vMax - 1 - (long) v) * uMax].r += c.r >> 1;
        Pixel[((long) u) + (vMax - 1 - (long) v) * uMax].g += c.g >> 1;
        Pixel[((long) u) + (vMax - 1 - (long) v) * uMax].b += c.b >> 1;
        Screen.SetPixel((long) u, (long) v, c);
        u += du;
        v += dv;
        } while(j--);
    u = u1;
    v = v1 + 1;
    j = i;
    do {
        Pixel[((long) u) + (vMax - 1 - (long) v) * uMax].r >>= 1;
        Pixel[((long) u) + (vMax - 1 - (long) v) * uMax].g >>= 1;
        Pixel[((long) u) + (vMax - 1 - (long) v) * uMax].b >>= 1;
        Pixel[((long) u) + (vMax - 1 - (long) v) * uMax].r += c.r >> 1;
        Pixel[((long) u) + (vMax - 1 - (long) v) * uMax].g += c.g >> 1;
        Pixel[((long) u) + (vMax - 1 - (long) v) * uMax].b += c.b >> 1;
        Screen.SetPixel((long) u, (long) v, c);
        u += du;
        v += dv;
        } while(j--);

    u = u1;
    v = v1;
    do {
        Pixel[((long) u) + (vMax - 1 - (long) v) * uMax] = c;
        Screen.SetPixel((long) u, (long) v, c);
        u += du;
        v += dv;
        } while(i--);

    }

Bitmap *Texture;
Bitmap Symbols;

// Mathematical classes -> functions

// 3 - Vector
class Vector {
    public:
    float x, y, z;
    inline float Norm(void);
    inline void Normalise(float r);
    inline float DotProduct(Vector &u);
    inline void CrossProduct(Vector &u, Vector &v);
    };

// Elucidean Norm
inline float Vector::Norm(void) {
    return sqrt(x*x + y*y + z*z);
    }

// Set length to r
inline void Vector::Normalise(float r) {
    r /= Norm();
    x *= r;
    y *= r;
    z *= r;
    }

// this . u
inline float Vector::DotProduct(Vector &u) {
    return u.x*x + u.y*y + u.z*z;
    }

// this = u * v
inline void Vector::CrossProduct(Vector &u, Vector &v) {
    x = (u.y * v.z) - (u.z * v.y);
    y = (u.z * v.x) - (u.x * v.z);
    z = (u.x * v.y) - (u.y * v.x);
    }

// 3 dimensional solids

// Box || axes
class SolidBox {
    public:
    Vector Min, Max;
    void Set(float nx, float ny, float nz, float xx, float xy, float xz);
    float Intersection(Vector &r, Vector &l);
    Vector Normal(Vector &l);
    };

void SolidBox::Set(float nx, float ny, float nz, float xx, float xy, float xz) {
    Min.x = nx;
    Min.y = ny;
    Min.z = nz;
    Max.x = xx;
    Max.y = xy;
    Max.z = xz;
    }

float SolidBox::Intersection(Vector &r, Vector &l) {
    Vector tMin, tMax;
    float t;

    if(r.x) {
        tMin.x = -(l.x - Min.x) / r.x;
        tMax.x = -(l.x - Max.x) / r.x;
        }
    else {
        tMin.x = 0.0;
        tMax.x = 0.0;
        }
    if(r.y) {
        tMin.y = -(l.y - Min.y) / r.y;
        tMax.y = -(l.y - Max.y) / r.y;
        }
    else {
        tMin.y = 0.0;
        tMax.y = 0.0;
        }
    if(r.z) {
        tMin.z = -(l.z - Min.z) / r.z;
        tMax.z = -(l.z - Max.z) / r.z;
        }
    else {
        tMin.z = 0.0;
        tMax.z = 0.0;
        }

    if(tMin.x < tMax.x) {
        t = tMin.x;
        tMin.x = tMax.x;
        tMax.x = t;
        }
    if(tMin.y < tMax.y) {
        t = tMin.y;
        tMin.y = tMax.y;
        tMax.y = t;
        }
    if(tMin.z < tMax.z) {
        t = tMin.z;
        tMin.z = tMax.z;
        tMax.z = t;
        }

    t = 0.0;

    if((tMin.y >= tMax.x) && (tMax.x >= tMax.y) && (tMin.z >= tMax.x) && (tMax.x >= tMax.z)) {
        t = -tMax.x;
        }
    if((tMin.x >= tMax.y) && (tMax.y >= tMax.x) && (tMin.z >= tMax.y) && (tMax.y >= tMax.z)) {
        t = -tMax.y;
        }
    if((tMin.x >= tMax.z) && (tMax.z >= tMax.x) && (tMin.y >= tMax.z) && (tMax.z >= tMax.y)) {
        t = -tMax.z;
        }

    return t;
    }

Vector SolidBox::Normal(Vector &l) {
    Vector n;
    float e[6], eMin;
    long i, s;

    // Error squares (closest side to l)

    e[0] = (l.x - Min.x)*(l.x - Min.x);
    e[1] = (l.y - Min.y)*(l.y - Min.y);
    e[2] = (l.z - Min.z)*(l.z - Min.z);
    e[3] = (l.x - Max.x)*(l.x - Max.x);
    e[4] = (l.y - Max.y)*(l.y - Max.y);
    e[5] = (l.z - Max.z)*(l.z - Max.z);

    // Find mimimum error:

    i = 1;
    s = 0;
    eMin = e[0];
    do {
        if(e[i] < eMin) {
            s = i;
            eMin = e[i];
            }
        } while(++i < 6);

    // Select normal

    n.x = 0.0;
    n.y = 0.0;
    n.z = 0.0;

    switch(s) {
        case 0:
            n.x = -1;
            break;
        case 1:
            n.y = -1;
            break;
        case 2:
            n.z = -1;
            break;
        case 3:
            n.x = 1;
            break;
        case 4:
            n.y = 1;
            break;
        case 5:
            n.z = 1;
            break;
        }

    return n;

    }

// Plane
class SolidPlane {
    public:
    float cx, cy, cz, k;
    void Set(float a, float b, float c, float d);
    float Intersection(Vector &r, Vector &l);
    Vector Normal(void);
    };

void SolidPlane::Set(float a, float b, float c, float d) {
    cx = a;
    cy = b;
    cz = c;
    k = d;
    }

float SolidPlane::Intersection(Vector &r, Vector &l) {
    float A, B, t;

    // Surface cx*x + cy*y + cz*z + k = 0;
    // Transformed by x = -r.x*t + l.x...
    // to A*t + B = 0

    A = -r.x*cx - r.y*cy - r.z*cz;
    B = l.x*cx + l.y*cy + l.x*cz + k;
    t = -B/A;

    return t;
    }

Vector SolidPlane::Normal(void) {
    Vector n;
    n.x = cx;
    n.y = cy;
    n.z = cz;
    n.Normalise(1.0);
    return n;
    }

// Quadric
class SolidQuadric {
    public:
    float cxx, cxy, cxz, cx, cyy, cyz, cy, czz, cz, k;
    void Set(float xx, float xy, float xz, float x, float yy, float yz, float y, float zz, float z, float c);
    float Intersection(Vector &r, Vector &l);
    Vector Normal(Vector &l);
    };

void SolidQuadric::Set(float xx, float xy, float xz, float x, float yy, float yz, float y, float zz, float z, float c) {
    cxx = xx;
    cxy = xy;
    cxz = xz;
    cx = x;
    cyy = yy;
    cyz = yz;
    cy = y;
    czz = zz;
    cz = z;
    k = c;
    }

float SolidQuadric::Intersection(Vector &r, Vector &l) {
    float A, B, C, t, D;
 
    // Surface cxx*x*x+cxy*x*y+cxz*x*z+cx*x+cyy*y*y+cyz*y*z+cy*y+czz*z*z+cz*z+k
    // Transformed by x = -r.x*t + l.x...
    // to A*t*t + B*t + C = 0

    A = + cxx*r.x*r.x
        + cxy*r.x*r.y
        + cxz*r.x*r.z
        + cyy*r.y*r.y
        + cyz*r.y*r.z
        + czz*r.z*r.z;
    B = - cxx*2.0*r.x*l.x
        - cxy*(r.x*l.y + r.y*l.x)
        - cxz*(r.x*l.z + r.z*l.x)
        - cx*r.x
        - cyy*2.0*r.y*l.y
        - cyz*(r.y*l.z + r.z*l.y)
        - cy*r.y
        - czz*2.0*r.z*l.z
        - cz*r.z;
    C = + cxx*l.x*l.x
        + cxy*l.x*l.y
        + cxz*l.x*l.z
        + cx*l.x
        + cyy*l.y*l.y
        + cyz*l.y*l.z
        + cy*l.y
        + czz*l.z*l.z
        + cz*l.z
        + k;

    // Discriminant
    D = B*B - 4*A*C;

    // Solution
    if(D > 0) {
        t = (-B + sqrt(D)) / (2 * A);
        return t;
        }
    else {
        return 0.0;
        }
    }

Vector SolidQuadric::Normal(Vector &l) {
    Vector n;

    // n = grad(Surface)

    n.x = 2.0*cxx*l.x + cxy*l.y + cxz*l.z + cx;
    n.y = cxy*l.x + 2.0*cyy*l.y + cyz*l.z + cy;
    n.z = cxz*l.z + cyz*l.y + 2.0*czz*l.z + cz;

    n.Normalise(1);

    return n;

    };


// Texture mapping methods

// Cylindrical mapping about axis s
class MapCylindrical {
    public:
    Bitmap *Image;
    Vector s, o;    // Axis & Scale, origin
    fColour Colour(Vector &l);
    };

fColour MapCylindrical::Colour(Vector &l) {
    float u, v;
    v = (s.x*(l.x - o.x) + s.y*(l.y - o.y) + s.z*(l.z - o.z)) / s.Norm();

    // Fixup

    u = 0.0;

    return Image->SmoothColour(u, v);
    }

// Hybrid mapping, suitable for boxes
class MapHybrid {
    public:
    long Image;
    float Ambient;
    Vector s, o;    // Scale, origin
    void Set(long i, float sx, float sy, float sz, float ox, float oy, float oz);
    fColour Colour(Vector &l, Vector &n);
    };

void MapHybrid::Set(long i, float sx, float sy, float sz, float ox, float oy, float oz) {
    Image = i;
    s.x = sx;
    s.y = sy;
    s.z = sz;
    o.x = ox;
    o.y = oy;
    o.z = oz;
    }

fColour MapHybrid::Colour(Vector &l, Vector &n) {
    float u, v;

    u = s.z*n.x*(l.z - o.z) + s.x*n.y*(l.x - o.x) + s.x*n.z*(l.x - o.x);
    v = s.y*n.x*(l.y - o.y) + s.z*n.y*(l.z - o.z) + s.y*n.z*(l.y - o.y);

    return Texture[Image].SmoothColour(u, v);
    }

// Normal mapping, suitable for curved objects
class MapNormal {
    public:
    Bitmap *Image;
    Vector s, o;    // Axis, origin
    fColour Colour(Vector &n);
    };

fColour MapNormal::Colour(Vector &n) {
    Vector w;
    float u, v;

    // Transform normal
    w.x = s.x*(n.x - o.x);
    w.y = s.y*(n.y - o.y);
    w.z = s.z*(n.z - o.z);

    u = atan2(w.x, w.z) / (2.0 * PI);
    v = 1 - (atan2(sqrt(w.x*w.x + w.z*w.z), w.y) / PI);

    return Image->SmoothColour(u, v);
    }

class MapPlanar {
    public:
    long Image;
    float Ambient;
    Vector su, sv, o;
    void Set(long i ,float sux, float suy, float suz, float svx, float svy, float svz, float ox, float oy, float oz);
    fColour Colour(Vector &l);
    };

void MapPlanar::Set(long i, float sux, float suy, float suz, float svx, float svy, float svz, float ox, float oy, float oz) {
    su.x = sux;
    su.y = suy;
    su.z = suz;
    sv.x = svx;
    sv.y = svy;
    sv.z = svz;
    o.x = ox;
    o.y = oy;
    o.z = oz;
    Image = i;
    }

fColour MapPlanar::Colour(Vector &l) {
    float u, v;
    u = su.x*(l.x - o.x) + su.y*(l.y - o.y) + su.z*(l.z - o.z);
    v = sv.x*(l.x - o.x) + sv.y*(l.y - o.y) + sv.z*(l.z - o.z);

    return Texture[Image].SmoothColour(u, v);
    }

// Spherical mapping, suitable for most objects
class MapSpherical {
    public:
    long Image;
    float Ambient;
    Vector s, o;    // Axis, origin
    void Set(long i, float sx, float sy, float sz, float ox, float oy, float oz);
    fColour Colour(Vector &l);
    };

void MapSpherical::Set(long i, float sx, float sy, float sz, float ox, float oy, float oz) {
    Image = i;
    s.x = sx;
    s.y = sy;
    s.z = sz;
    o.x = ox;
    o.y = oy;
    o.z = oz;
    }

fColour MapSpherical::Colour(Vector &l) {
    Vector w;
    float u, v;

    // Transform location
    w.x = s.x*(l.x - o.x);
    w.y = s.y*(l.y - o.y);
    w.z = s.z*(l.z - o.z);

    u = atan2(w.x, w.z) / (2.0 * PI);
    v = 1 - (atan2(sqrt(w.x*w.x + w.z*w.z), w.y) / PI);

    return Texture[Image].SmoothColour(u, v);
    }


class Object {
    public:
    void *Solid;
    void *Map;
    long SolidType;
    long MapType;
    float Intersection(Vector &r, Vector &l);
    Vector Normal(Vector &l);
    fColour Texture(Vector &l, Vector &n);
    };

float Object::Intersection(Vector &r, Vector &l) {
    switch(SolidType) {
        case BOX:
            return ((SolidBox *) Solid)->Intersection(r, l);
            break;
        case PLANE:
            return ((SolidPlane *) Solid)->Intersection(r, l);
            break;
        case QUADRIC:
            return ((SolidQuadric *) Solid)->Intersection(r, l);
            break;
        default:
            exit(EXIT_FAILURE);
            break;
        }
    }

Vector Object::Normal(Vector &l) {
    switch(SolidType) {
        case BOX:
            return ((SolidBox *) Solid)->Normal(l);
            break;
        case PLANE:
            return ((SolidPlane *) Solid)->Normal();
            break;
        case QUADRIC:
            return ((SolidQuadric *) Solid)->Normal(l);
            break;
        default:
            exit(EXIT_FAILURE);
            break;
        }
    }

fColour Object::Texture(Vector &l, Vector &n) {
    switch(MapType) {
        case CONSTANT:
            return *((fColour *) Map);
            break;
        case CYLINDRICAL:
            return ((MapCylindrical *) Map)->Colour(l);
            break;
        case HYBRID:
            return ((MapHybrid *) Map)->Colour(l, n);
            break;
        case NORMAL:
            return ((MapNormal *) Map)->Colour(n);
            break;
        case PLANAR:
            return ((MapPlanar *) Map)->Colour(l);
            break;
        case SPHERICAL:
            return ((MapSpherical *) Map)->Colour(l);
            break;
        default:
            exit(EXIT_FAILURE);
            break;
        }
    }

class LightSource {
    public:
    Vector o;       // Origin
    float b;        // Brightness at 1 lightsecond
    void Set(float ox, float oy, float oz, float b1);
    float Cast(Vector &l, Vector &n);
    };

void LightSource::Set(float ox, float oy, float oz, float b1) {
    o.x = ox;
    o.y = oy;
    o.z = oz;
    b = b1;
    }

float LightSource::Cast(Vector &l, Vector &n) {
    Vector w;   // Transformed coordinates
    float i;    // Illumination

    w.x = -(l.x - o.x);
    w.y = -(l.y - o.y);
    w.z = -(l.z - o.z);

    i = b * w.DotProduct(n) / pow((w.x*w.x + w.y*w.y + w.z*w.z), 1.5);
    if(i < 0) {
        i = 0;
        }
    return i;
    }


class Projection {
    public:
    Vector l, v, s, f;  // Location, Velocity, Sky, Focus
    Vector u, r;        // Up, Right
    void Construct(void);
    float Project(float du, float dr, Vector &ray, Vector &o, float &Intensity);
    };

void Projection::Construct(void) {
    r.CrossProduct(f,s);
    u.CrossProduct(f,r);
    r.Normalise(1.0);
    u.Normalise(1.0);
    }

class Arena {
    public:
    Bitmap Frame;
    Projection *Camera;
    Object *Solid;
    LightSource *Light;
    char *Name;
    long Solids, Lights, Cameras, Textures;
    float AspectRatio;
    
    void Run(void);
    void InsertHUD(Projection &Cam);
    void RenderFrame(Projection &Cam, Bitmap &Buffer);
    fColour CastRay(float u, float v, Projection &Cam, Bitmap &Buffer);
    void Load(char *SceneFile, char *CameraFile);
    };

float Projection::Project(float du, float dr, Vector &ray, Vector &o, float &Intensity) {
    Vector n;       // Newtonian normal
    Vector vn;      // Velocity normal
    Vector p;    // Perpendicular component to velocity
    float cosa;     // Length of parallel component to velocity
    float cosb;     // Transformed angle
    float k;

    n.x = f.x + (du * r.x) + (dr * u.x);    // Construct viewcone
    n.y = f.y + (du * r.y) + (dr * u.y);
    n.z = f.z + (du * r.z) + (dr * u.z);

    n.Normalise(1.0);   // Clip rays to speed of light

    if((!Globals.Aberration) || (v.Norm() == 0)) {
        ray.x = n.x;
        ray.y = n.y;
        ray.z = n.z;
        o = l;
        Intensity = 1.0;
        return 1.0;
        }

    vn.x = v.x / v.Norm();
    vn.y = v.y / v.Norm();
    vn.z = v.z / v.Norm();

    cosa = n.DotProduct(vn);        // Parallel component
    p.x = n.x - (cosa * vn.x);   // Perpendicular component
    p.y = n.y - (cosa * vn.y);
    p.z = n.z - (cosa * vn.z);

    // Aberation formula

    cosb = (cosa - v.Norm()) / (1 - cosa*v.Norm());

    // Find necessary perpendicular length to normalise ray

    if(p.Norm() > 0) {
        k = sqrt(1 - (cosb*cosb*(vn.x*vn.x + vn.y*vn.y + vn.z*vn.z))) / p.Norm();
        }
    else {
        k = 0.0;
        }
    // Thus...

    ray.x = cosb*vn.x + k*p.x;
    ray.y = cosb*vn.y + k*p.y;
    ray.z = cosb*vn.z + k*p.z; 

    Intensity = (1 - v.Norm()*cosb)*(1 - v.Norm()*cosb)/sqrt(1 - v.x*v.x - v.y*v.y - v.z*v.z);

    Intensity = 1.0 + (Intensity - 1.0)*Globals.Intensity; // Partial intensities for clarity

    if(!Globals.Doppler) {
        o = l;
        return 1.0;
        }

    o = l;
    return (1.0 - (v.Norm()*cosb)) / sqrt(1 - v.x*v.x - v.y*v.y - v.z*v.z);
    }

void Arena::Run(void) {
    long l;
    char FileName[16];

    l = 0;
    do {
        //Fixup
        //Camera[l].Construct();
        RenderFrame(Camera[l], Frame);

        if(Globals.HUD) {
            InsertHUD(Camera[l]);
            }
        sprintf(FileName, "%.32s%.4li.tga", Name, l);
        Frame.SaveTGA(FileName);
        } while(++l < Cameras);
    }

void PutString(char *p, long u, long v) {
    do {
        switch(*p) {
            case 0:
                return;
                break;
            case 32:    // Space
                u += 26 * 2;
                break;
            case 48:    // 0
                Scene.Frame.ChromaKey(u, v, Symbols, 0, 0, 32 * 2, 64 * 2 - 40);
                u += 26 * 2;
                break;
            case 49:    // 1
                Scene.Frame.ChromaKey(u, v, Symbols, 0, 64 * 2, 32 * 2, 64 * 2 * 2 - 40);
                u += 26 * 2;
                break;
            case 50:    // 2
                Scene.Frame.ChromaKey(u, v, Symbols, 0, 64 * 2 * 2, 32 * 2, 64 * 3 * 2 - 40);
                u += 26 * 2;
                break;
            case 51:    // 3
                Scene.Frame.ChromaKey(u, v, Symbols, 0, 64 * 3 * 2, 32 * 2, 64 * 4 * 2 - 40);
                u += 26 * 2;
                break;
            case 52:    // 4
                Scene.Frame.ChromaKey(u, v, Symbols, 0, 64 * 4 * 2, 32 * 2, 64 * 5 * 2 - 40);
                u += 26 * 2;
                break;
            case 53:    // 5
                Scene.Frame.ChromaKey(u, v, Symbols, 0, 64 * 5 * 2, 32 * 2, 64 * 6 * 2 - 40);
                u += 26 * 2;
                break;
            case 54:    // 6
                Scene.Frame.ChromaKey(u, v, Symbols, 0, 64 * 6 * 2, 32 * 2, 64 * 7 * 2 - 40);
                u += 26 * 2;
                break;
            case 55:    // 7
                Scene.Frame.ChromaKey(u, v, Symbols, 0, 64 * 7 * 2, 32 * 2, 64 * 8 * 2 - 40);
                u += 26 * 2;
                break;
            case 56:    // 8
                Scene.Frame.ChromaKey(u, v, Symbols, 0, 64 * 8 * 2, 32 * 2, 64 * 9 * 2 - 40);
                u += 26 * 2;
                break;
            case 57:    // 9
                Scene.Frame.ChromaKey(u, v, Symbols, 0, 64 * 9 * 2, 32 * 2, 64 * 10 * 2 - 40);
                u += 26 * 2;
                break;
            case 61:    // =
                Scene.Frame.ChromaKey(u, v, Symbols, 0, 64 * 10 * 2, 32 * 2, 64 * 11 * 2 - 40);
                u += 26 * 2;
                break;
            case 99:    // c
                Scene.Frame.ChromaKey(u, v, Symbols, 0, 64 * 11 * 2, 32 * 2, 64 * 12 * 2 - 40);
                u += 26 * 2;
                break;
            case 46:    // .
                Scene.Frame.ChromaKey(u, v, Symbols, 0, 64 * 12 * 2, 32 * 2, 64 * 13 * 2 - 40);
                u += 10 * 2;
                break;
            case 103:    // g
                Scene.Frame.ChromaKey(u, v, Symbols, 0, 64 * 13 * 2, 32 * 2, 64 * 14 * 2 - 40);
                u += 26 * 2;
                break;
            default:
                break;
            }
        if(u > Scene.Frame.uMax - (26 * 2)) {
            u = 26 * 2;
            v += 44 * 2;
            if(v > Scene.Frame.vMax - (44 * 2)) {
                return;
                }
            }
        p++;
        } while(1);
    }

void Transform(long &u, long &v, Vector t, Bitmap &Frame) {
    float x, y, z;

    // Center

    t.x -= Globals.HUDView->l.x;
    t.y -= Globals.HUDView->l.y;
    t.z -= Globals.HUDView->l.z;

    // Project onto HUDView axes

    x = t.DotProduct(Globals.HUDView->r)/Scene.AspectRatio;
    y = t.DotProduct(Globals.HUDView->u);
    z = t.DotProduct(Globals.HUDView->f) / Globals.HUDView->f.DotProduct(Globals.HUDView->f);

    // Inverse z projection onto screen

    u = (long) ((float) ((x / z) - 1) * (float) (Globals.HUDMap->uMax/2)) + Frame.uMax - 32;
    v = (long) ((float) ((y / z) - 1) * (float) (Globals.HUDMap->vMax/2)) + Frame.vMax - 32;

    }

void PlaceHUDMarkers(Projection &Cam, Bitmap &Frame) {
    long u1, v1, u2, v2, du, dv;
    Vector t;
    iColour c;
    float k;


    // Squish camera

    k = (Cam.f.Norm() + Cam.r.Norm() + Cam.u.Norm())/3.0;
    Cam.f.Normalise(Cam.f.Norm()/k);
    Cam.r.Normalise((Frame.uMax/Frame.vMax)*Scene.AspectRatio/k);
    Cam.u.Normalise(1/k);

    k = Globals.HUDScale;

    // Origin

    Transform(u1, v1, Cam.l, Frame);

    // Camera

    c.r = 0;
    c.g = 0;
    c.b = 240;

    // Corners
    t.x = Cam.l.x + (Cam.f.x + Cam.r.x + Cam.u.x) * k;
    t.y = Cam.l.y + (Cam.f.y + Cam.r.y + Cam.u.y) * k;
    t.z = Cam.l.z + (Cam.f.z + Cam.r.z + Cam.u.z) * k;
    Transform(u2, v2, t, Frame);
    Frame.Line(u1, v1, u2, v2, c);
    t.x -= 2.0*Cam.r.x*k;
    t.y -= 2.0*Cam.r.y*k;
    t.z -= 2.0*Cam.r.z*k;
    Transform(u2, v2, t, Frame);
    Frame.Line(u1, v1, u2, v2, c);
    t.x -= 2.0*Cam.u.x*k;
    t.y -= 2.0*Cam.u.y*k;
    t.z -= 2.0*Cam.u.z*k;
    Transform(u2, v2, t, Frame);
    Frame.Line(u1, v1, u2, v2, c);
    t.x += 2.0*Cam.r.x*k;
    t.y += 2.0*Cam.r.y*k;
    t.z += 2.0*Cam.r.z*k;
    Transform(u2, v2, t, Frame);
    Frame.Line(u1, v1, u2, v2, c);

    t.x += 2.0*Cam.u.x*k;
    t.y += 2.0*Cam.u.y*k;
    t.z += 2.0*Cam.u.z*k;
    Transform(u1, v1, t, Frame);
    Frame.Line(u1, v1, u2, v2, c);
    t.x -= 2.0*Cam.r.x*k;
    t.y -= 2.0*Cam.r.y*k;
    t.z -= 2.0*Cam.r.z*k;
    Transform(u2, v2, t, Frame);
    Frame.Line(u1, v1, u2, v2, c);
    t.x -= 2.0*Cam.u.x*k;
    t.y -= 2.0*Cam.u.y*k;
    t.z -= 2.0*Cam.u.z*k;
    Transform(u1, v1, t, Frame);
    Frame.Line(u1, v1, u2, v2, c);
    t.x += 2.0*Cam.r.x*k;
    t.y += 2.0*Cam.r.y*k;
    t.z += 2.0*Cam.r.z*k;
    Transform(u2, v2, t, Frame);
    Frame.Line(u1, v1, u2, v2, c);

    // Origin

    Transform(u1, v1, Cam.l, Frame);

    // Velocity

    c.r = 0;
    c.g = 240;
    c.b = 0;

    t.x = Cam.l.x + Cam.v.x*k*2;
    t.y = Cam.l.y + Cam.v.y*k*2;
    t.z = Cam.l.z + Cam.v.z*k*2;

    Transform(u2, v2, t, Frame);
    Frame.Line(u1, v1, u2, v2, c);

    du = (u2 - u1);
    dv = (v2 - v1);

    u1 = u2 - du/3 - dv/6;
    v1 = v2 - dv/3 + du/6;

    Frame.Line(u1, v1, u2, v2, c);

    u1 = u2 - du/3 + dv/6;
    v1 = v2 - dv/3 - du/6;

    Frame.Line(u1, v1, u2, v2, c);

    }

void Arena::InsertHUD(Projection &Cam) {
    float g;
    char Buffer[256];
   
    sprintf(Buffer, "%3.3fc", Cam.v.Norm());
    if(strlen(Buffer) == 2) {
        PutString(Buffer, Frame.uMax - 26*2*2 - 32, 30);
        }
    else {
        PutString(Buffer, Frame.uMax - (26*2*strlen(Buffer)) + 12, 32);
        }

    g = 1/sqrt(1 - Cam.v.Norm()*Cam.v.Norm());

    sprintf(Buffer, "g=%3.3g", g);
    PutString(Buffer, 30, Frame.vMax - 104);

    Scene.Frame.ChromaKey(
        Scene.Frame.uMax - Globals.HUDMap->uMax - 32,
        Scene.Frame.vMax - Globals.HUDMap->vMax - 32,
        *Globals.HUDMap,
        0, 0, Globals.HUDMap->uMax, Globals.HUDMap->vMax);

    PlaceHUDMarkers(Cam, Frame);

    }

void Arena::RenderFrame(Projection &Cam, Bitmap &Buffer) {
    fColour c00, c01, c10, c11;
    float e;
    long u, v;
    Cam.Construct();

    v = 0;
    do {
        u = 0;
        do {
            c11 = CastRay((float) u - 0.25, (float) v - 0.25, Cam, Buffer);

            if(Globals.AntiAliasing) {
                if(u && v) {
                    // Non-edge - check for aliasing
                    c00 = Buffer.Colour(u-1,v-1);
                    c01 = Buffer.Colour(u-1,v);
                    c10 = Buffer.Colour(u,v-1);
                    e = (c00.r - c01.r)*(c00.r - c01.r)
                      + (c00.r - c10.r)*(c00.r - c10.r)
                      + (c00.r - c11.r)*(c00.r - c11.r)
                      + (c00.g - c01.g)*(c00.g - c01.g)
                      + (c00.g - c10.g)*(c00.g - c10.g)
                      + (c00.g - c11.g)*(c00.g - c11.g)
                      + (c00.b - c01.b)*(c00.b - c01.b)
                      + (c00.b - c10.b)*(c00.b - c10.b)
                      + (c00.b - c11.b)*(c00.b - c11.b);
                    if(e > Globals.AAThreshold) {
                        c00 = CastRay((float) u - 0.75, (float) v - 0.75, Cam, Buffer);
                        c01 = CastRay((float) u - 0.75, (float) v - 0.25, Cam, Buffer);
                        c10 = CastRay((float) u - 0.25, (float) v - 0.75, Cam, Buffer);
                        c11.r = (c00.r + c01.r + c10.r + c11.r) / 4.0;
                        c11.g = (c00.g + c01.g + c10.g + c11.g) / 4.0;
                        c11.b = (c00.b + c01.b + c10.b + c11.b) / 4.0;
                        }
                    }
                else {
                    // Edge - antialias
                    c00 = CastRay((float) u - 0.75, (float) v - 0.75, Cam, Buffer);
                    c01 = CastRay((float) u - 0.75, (float) v - 0.25, Cam, Buffer);
                    c10 = CastRay((float) u - 0.25, (float) v - 0.75, Cam, Buffer);
                    c11.r = (c00.r + c01.r + c10.r + c11.r) / 4.0;
                    c11.g = (c00.g + c01.g + c10.g + c11.g) / 4.0;
                    c11.b = (c00.b + c01.b + c10.b + c11.b) / 4.0;
                    }
                }

            Buffer.Pixel[u + (Buffer.vMax - 1 - v)*Buffer.uMax] = c11.Convert();
            #ifdef PC
            Screen.SetPixel(u, v, c11.Convert());
            #endif

            } while(++u < (signed long) Buffer.uMax);
        } while(++v < (signed long) Buffer.vMax);
    }

fColour Arena::CastRay(float u, float v, Projection &Cam, Bitmap &Buffer) {
    fColour c, d;
    Vector r, o;    // Ray, origin
    Vector l, n;    // Location (of intersection), Normal
    float Doppler;   // Spectrum shift
    float Illumination, Intensity;
    float su, sv;   // Sky coordinates
    float t, tMax;
    float cuv, cir; // Off spectrum predictions
    float sb, sg, sr;   // Shifted wavelengths
    long i, nMax;

    Doppler = Cam.Project(
        (2.0*(u / Buffer.uMax) - 1.0)*AspectRatio*Buffer.uMax/Buffer.vMax,
        2.0*(v / Buffer.vMax) - 1.0,
        r, o, Intensity);

    tMax = -1e5;
    nMax = -1;
    i = 0;
    while(i < Solids) {
        t = Solid[i].Intersection(r, o);
        if((tMax < t) && (t < 0)) {
            tMax = t;
            nMax = i;
            }
        i++;
        } 

    if(nMax != -1) {
        l.x = -(r.x * tMax) + o.x;        
        l.y = -(r.y * tMax) + o.y;
        l.z = -(r.z * tMax) + o.z;

        n = Solid[nMax].Normal(l);
        c = Solid[nMax].Texture(l, n);

        // Dodgy - keep map members in same order!
        Illumination = ((MapSpherical *) Solid[nMax].Map)->Ambient;
        i = 0;
        do {
            Illumination += Light[i].Cast(l, n);
            } while(++i < Lights);

        c.r *= sqrt(Illumination);
        c.g *= sqrt(Illumination);
        c.b *= sqrt(Illumination);
        }
    else {
        if(Globals.Sky) {
            su = atan2(r.x, r.z) / (2.0 * PI);
            sv = 1.0 - (atan2(sqrt(r.x*r.x + r.z*r.z), r.y) / PI);
            c = Texture[Globals.SkyIndex].SmoothColour(su, sv);
            }
        else {
            c.r = 0.0;
            c.b = 0.0;
            c.g = 0.0;
            }
        }

    // Dimming;

    if(Globals.Intensity) {
        c.b /= Intensity;
        c.g /= Intensity;
        c.r /= Intensity;
        }

    Doppler = 1 + (Doppler - 1) * Globals.Doppler;

    // Spectrum shift...
    if(Globals.Doppler) {
        cuv = 0.5*c.b + 0.25*c.g + 0.125*c.r;
        cir = 0.5*c.r + 0.25*c.g + 0.125*c.b;

        sb = lBlue  / Doppler;
        sg = lGreen / Doppler;
        sr = lRed   / Doppler;

        if(sb < lUltraViolet) {
            d.b = cuv * sb / lUltraViolet;
            }
        else {
            if(sb < lBlue) {
                d.b = (c.b - cuv) * (sb - lUltraViolet) / (lBlue - lUltraViolet) + cuv;
                }
            else {
                if(sb < lGreen) {
                    d.b = (c.g - c.b) * (sb - lBlue) / (lGreen - lBlue) + c.b;
                    }
                else {
                    if(sb < lRed) {
                        d.b = (c.r - c.g) * (sb - lGreen) / (lRed - lGreen) + c.g;
                        }
                    else {
                        if(sb < lInfraRed) {
                            d.b = (cir - c.r) * (sb - lRed) / (lInfraRed - lRed) + c.r;
                            }
                        else {
                            d.b = (lInfraRed / sb) * cir;
                            }
                        }
                    }
                }
            }
            
        if(sg < lUltraViolet) {
            d.g = cuv * sg / lUltraViolet;
            }
        else {
            if(sg < lBlue) {
                d.g = (c.b - cuv) * (sg - lUltraViolet) / (lBlue - lUltraViolet) + cuv;
                }
            else {
                if(sg < lGreen) {
                    d.g = (c.g - c.b) * (sg - lBlue) / (lGreen - lBlue) + c.b;
                    }
                else {
                    if(sg < lRed) {
                        d.g = (c.r - c.g) * (sg - lGreen) / (lRed - lGreen) + c.g;
                        }
                    else {
                        if(sg < lInfraRed) {
                            d.g = (cir - c.r) * (sg - lRed) / (lInfraRed - lRed) + c.r;
                            }
                        else {
                            d.g = (lInfraRed / sg) * cir;
                            }
                        }
                    }
                }
            }

        if(sr < lUltraViolet) {
            d.r = cuv * sr / lUltraViolet;
            }
        else {
            if(sr < lBlue) {
                d.r = (c.b - cuv) * (sr - lUltraViolet) / (lBlue - lUltraViolet) + cuv;
                }
            else {
                if(sr < lGreen) {
                    d.r = (c.g - c.b) * (sr - lBlue) / (lGreen - lBlue) + c.b;
                    }
                else {
                    if(sr < lRed) {
                        d.r = (c.r - c.g) * (sr - lGreen) / (lRed - lGreen) + c.g;
                        }
                    else {
                        if(sr < lInfraRed) {
                            d.r = (cir - c.r) * (sr - lRed) / (lInfraRed - lRed) + c.r;
                            }
                        else {
                            d.r = (lInfraRed / sr) * cir;
                            }
                        }
                    }
                }
            }

        // Allow for partial redshifts

        return d;
        }
    else {
        return c;
        }
    }

char *Find(char *String, char *Token) {
    char *a;
    a = strstr(String, Token);
    if(a == NULL) {
        printf("Error: Can't find token [ %.32s ] in %.32s\n", Token, String);
        exit(EXIT_FAILURE);
        }
    else {
        a += strlen(Token);
        }
    return a;
    }

void Arena::Load(char *SceneFile, char *CameraFile) {
    struct stat FileStatus;
    FILE *Handle;
    void **Map;
    long *MapType;
    char *Input, *Buffer, *a, *b;
    long i, j, k, L, Maps;
    
    Handle = fopen(SceneFile,"rb");
    if(Handle == NULL) {
        printf("Error: File %.32s not found\n", SceneFile);
        exit(EXIT_FAILURE);
        }
    fstat(fileno(Handle), &FileStatus);
    Input = new char[ FileStatus.st_size + 2 ];
    if(Input == NULL) {
        printf("Error: Out of memory for %.32s\n", SceneFile);
        exit(EXIT_FAILURE);
        }
    fseek(Handle, 0, 0);
    fread(Input, FileStatus.st_size, 1, Handle);
    Input[ FileStatus.st_size + 1 ] = 0;
        printf("Scene file %.32s loaded\nParsing...\n", SceneFile);     
    fclose(Handle);

    a = Find(Input, "FileName");
    a = Find(a, "\"");
    b = Find(a, "\"") - 1;
    L = (long) b - (long) a;
    Name = new char[L + 1];
    memcpy(Name, a, L);
    Name[L] = 0;
        printf("Output %.32s****.tga\n", Name);     

    a = Find(Input, "Frames");
    Frame.uMax = atol(a);
    a = Find(a, ",");
    Frame.vMax = atol(a);
    a = Find(a, ",");
    AspectRatio = atof(a);
        printf("Image (%li, %li) Aspect %f\n", Frame.uMax, Frame.vMax, AspectRatio);

    Frame.Pixel = new iColour[Frame.uMax * Frame.vMax];

    // Fixup
    // For debugging (harmless anyway)
    memset(Frame.Pixel, 0, 3*Frame.uMax*Frame.vMax);

    a = Find(Input, "Aberration");
    Globals.Aberration = atoi(a);
    a = Find(Input, "Doppler");
    Globals.Doppler = atof(a);
    a = Find(Input, "Intensity");
    Globals.Intensity = atof(a);
    a = Find(Input, "Sky");
    Globals.Sky = atoi(a);
    a = Find(a, ",");
    Globals.SkyIndex = atol(a) - 1;
    a = Find(Input, "HUD");
    Globals.HUD = atoi(a);
    a = Find(Input, "Interpolation");
    Globals.Interpolation = atoi(a);
    a = Find(Input, "AntiAliasing");
    Globals.AntiAliasing = atoi(a);
    a = Find(a, ",");
    Globals.AAThreshold = atof(a);
        printf("Globals:\n    Aberration %i\n    Doppler %g\n    Intensity %g\n    Sky %i SkyImage %li\n    HUD %i\n    Interpolation %i\n    AntiAliasing %i Threshold %f\n",
            Globals.Aberration,
            Globals.Doppler,
            Globals.Intensity,
            Globals.Sky, Globals.SkyIndex,
            Globals.HUD,
            Globals.Interpolation,
            Globals.AntiAliasing, Globals.AAThreshold);

    if(Globals.HUD) {
        Symbols.LoadTGA("textures/Symbols2.tga");
        printf("HUD Symbols loaded\n");

        Globals.HUDView = new Projection[1];

        a = Find(Input, "HUDView");

        b = Find(a, "Location");
        b = Find(b, "<");
        Globals.HUDView->l.x = atof(b);
        b = Find(b, ",");
        Globals.HUDView->l.y = atof(b);
        b = Find(b, ",");
        Globals.HUDView->l.z = atof(b);

        Globals.HUDView->v.x = 0.0;
        Globals.HUDView->v.y = 0.0;
        Globals.HUDView->v.z = 0.0;

        b = Find(a, "Focus");
        b = Find(b, "<");        
        Globals.HUDView->f.x = atof(b);
        b = Find(b, ",");
        Globals.HUDView->f.y = atof(b);
        b = Find(b, ",");
        Globals.HUDView->f.z = atof(b);

        b = Find(a, "Sky");
        b = Find(b, "<");
        Globals.HUDView->s.x = atof(b);
        b = Find(b, ",");
        Globals.HUDView->s.y = atof(b);
        b = Find(b, ",");
        Globals.HUDView->s.z = atof(b);

        Globals.HUDMap = new Bitmap[1];

        b = Find(a, "Size");
        Globals.HUDMap->uMax = atoi(b);
        b = Find(b, ",");
        Globals.HUDMap->vMax = atoi(b);

        b = Find(a, "Scale");
        Globals.HUDScale = atof(b);

        Globals.HUDMap->Initialise(Globals.HUDMap->uMax, Globals.HUDMap->vMax);

        printf("HUD Map initialised\n");

        }

    a = Find(Input, "Textures");
    Textures = atol(a);

    Texture = new Bitmap[Textures];
    if(Texture == NULL) {
        printf("Error:Cannot initialise %li textures\n", Textures);
        exit(EXIT_FAILURE);
        }

    i = 0;
    do {
        a = Find(a, "\"");
        b = Find(a, "\"") - 1;
        L = (long) b - (long) a;
        Buffer = new char[L + 1];
        memcpy(Buffer, a, L);
        Buffer[L] = 0;

        Texture[i].LoadTGA(Buffer);

        free(Buffer);

        a = strstr(a, ",");
        } while(++i < Textures);

    a = Find(Input, "Lights");
    Lights = atol(a);

    Light = new LightSource[Lights];

    if(Light == NULL) {
        printf("Error:Cannot initialise %li light(s)\n", Lights);
        }

    i = 0;
    do {
        a = Find(a, "Light");
        j = atol(a) - 1;
        a = Find(a, "<");
        Light[j].o.x = atof(a);
        a = Find(a, ",");
        Light[j].o.y = atof(a);
        a = Find(a, ",");
        Light[j].o.z = atof(a);
        a = Find(a, ",");
        Light[j].b = atof(a);

        printf("Light %li, brightness %f at < %f, %f, %f >\n", j, Light[j].b, Light[j].o.x, Light[j].o.y, Light[j].o.z);

        } while(++i < Lights);

    a = Find(Input, "Maps");
    Maps = atol(a);

    Map = new void *[Maps];
    MapType = new long[Maps];

    if((Map == NULL) || (MapType == NULL)) {
        printf("Error:Cannot initialise %li map(s)\n", Maps);
        exit(EXIT_FAILURE);
        }

    i = 0;
    do {
        a = Find(a, "Mapping");
        j = atol(a) - 1;

        printf("Map %li ", j);

        a = Find(a, "Type");
        a = Find(a, "<");

        if(!strncmp(a, "Planar", 6)) {
            MapType[j] = PLANAR;
            }
        if(!strncmp(a, "Spherical", 9)) {
            MapType[j] = SPHERICAL;
            }
        if(!strncmp(a, "Hybrid", 6)) {
            MapType[j] = HYBRID;
            }

        switch(MapType[j]) {
            case PLANAR:
                Map[j] = new MapPlanar[1];

                a = Find(a, "Texture");
                ((MapPlanar *) Map[j])->Image = atol(a) - 1;
                a = Find(a, "Ambient");
                ((MapPlanar *) Map[j])->Ambient = atof(a);
                a = Find(a, "<");
                ((MapPlanar *) Map[j])->su.x = atof(a);
                a = Find(a, ",");
                ((MapPlanar *) Map[j])->su.y = atof(a);
                a = Find(a, ",");
                ((MapPlanar *) Map[j])->su.z = atof(a);
                a = Find(a, "<");
                ((MapPlanar *) Map[j])->sv.x = atof(a);
                a = Find(a, ",");
                ((MapPlanar *) Map[j])->sv.y = atof(a);
                a = Find(a, ",");
                ((MapPlanar *) Map[j])->sv.z = atof(a);
                a = Find(a, "<");
                ((MapPlanar *) Map[j])->o.x = atof(a);
                a = Find(a, ",");
                ((MapPlanar *) Map[j])->o.y = atof(a);
                a = Find(a, ",");
                ((MapPlanar *) Map[j])->o.z = atof(a);

                printf("Planar, texture %li, ambient %f\n",
                    ((MapPlanar *) Map[j])->Image,
                    ((MapPlanar *) Map[j])->Ambient);

                break;
            case SPHERICAL:
                Map[j] = new MapSpherical[1];
                a = Find(a, "Texture");
                ((MapSpherical *) Map[j])->Image = atol(a) - 1;
                a = Find(a, "Ambient");
                ((MapSpherical *) Map[j])->Ambient = atof(a);
                a = Find(a, "<");
                ((MapSpherical *) Map[j])->s.x = atof(a);
                a = Find(a, ",");
                ((MapSpherical *) Map[j])->s.y = atof(a);
                a = Find(a, ",");
                ((MapSpherical *) Map[j])->s.z = atof(a);
                a = Find(a, "<");
                ((MapSpherical *) Map[j])->o.x = atof(a);
                a = Find(a, ",");
                ((MapSpherical *) Map[j])->o.y = atof(a);
                a = Find(a, ",");
                ((MapSpherical *) Map[j])->o.z = atof(a);

                printf("Spherical, texture %li, ambient %f\n",
                    ((MapSpherical *) Map[j])->Image,
                    ((MapSpherical *) Map[j])->Ambient);
                
                break;
            case HYBRID:
                Map[j] = new MapHybrid[1];
                a = Find(a, "Texture");
                ((MapHybrid *) Map[j])->Image = atol(a) - 1;
                a = Find(a, "Ambient");
                ((MapHybrid *) Map[j])->Ambient = atof(a);
                a = Find(a, "<");
                ((MapHybrid *) Map[j])->s.x = atof(a);
                a = Find(a, ",");
                ((MapHybrid *) Map[j])->s.y = atof(a);
                a = Find(a, ",");
                ((MapHybrid *) Map[j])->s.z = atof(a);
                a = Find(a, "<");
                ((MapHybrid *) Map[j])->o.x = atof(a);
                a = Find(a, ",");
                ((MapHybrid *) Map[j])->o.y = atof(a);
                a = Find(a, ",");
                ((MapHybrid *) Map[j])->o.z = atof(a);

                printf("Hybrid, texture %li, ambient %f\n",
                    ((MapHybrid *) Map[j])->Image,
                    ((MapHybrid *) Map[j])->Ambient);
                
                break;

            default:
                printf("Error: Unknown mapping type at %.32s\n", a);
                exit(EXIT_FAILURE);
                break;
            }
        } while(++i < Maps);

    a = Find(Input, "Solids");
    Solids = atol(a);

    Solid = new Object[Solids];

    if(Solid == NULL) {
        printf("Error:Cannot initialise %li solid(s)\n", Solids);
        exit(EXIT_FAILURE);
        }

    i = 0;
    while(i++ < Solids) {
        a = Find(a, "Solid");
        j = atol(a) - 1;
        a = Find(a, "Type");
        a = Find(a, "<");

        if(!strncmp(a, "Plane", 5)) {
            Solid[j].SolidType = PLANE;
            }
        if(!strncmp(a, "Quadric", 7)) {
            Solid[j].SolidType = QUADRIC;
            }
        if(!strncmp(a, "Box", 3)) {
            Solid[j].SolidType = BOX;
            }
        
        switch(Solid[j].SolidType) {
            case BOX:
                Solid[j].Solid = new SolidBox[1];

                a = Find(a, "<");
                ((SolidBox *) Solid[j].Solid)->Min.x = atof(a);
                a = Find(a, ",");
                ((SolidBox *) Solid[j].Solid)->Min.y = atof(a);
                a = Find(a, ",");
                ((SolidBox *) Solid[j].Solid)->Min.z = atof(a);
                a = Find(a, "<");
                ((SolidBox *) Solid[j].Solid)->Max.x = atof(a);
                a = Find(a, ",");
                ((SolidBox *) Solid[j].Solid)->Max.y = atof(a);
                a = Find(a, ",");
                ((SolidBox *) Solid[j].Solid)->Max.z = atof(a);

                a = Find(a, "Map");
                k = atol(a) - 1;

                Solid[j].Map = Map[k];
                Solid[j].MapType = MapType[k];

                printf("Solid %li Box, map %li\n", j, k);

                break;
            case PLANE:
                Solid[j].Solid = new SolidPlane[1];

                a = Find(a, "<");
                ((SolidPlane *) Solid[j].Solid)->cx = atof(a);
                a = Find(a, ",");
                ((SolidPlane *) Solid[j].Solid)->cy = atof(a);
                a = Find(a, ",");
                ((SolidPlane *) Solid[j].Solid)->cz = atof(a);
                a = Find(a, ",");
                ((SolidPlane *) Solid[j].Solid)->k = atof(a);

                a = Find(a, "Map");
                k = atol(a) - 1;

                Solid[j].Map = Map[k];
                Solid[j].MapType = MapType[k];

                printf("Solid %li Plane, map %li\n", j, k);

                break;
            case QUADRIC:
                Solid[j].Solid = new SolidQuadric[1];

                a = Find(a, "<");
                ((SolidQuadric *) Solid[j].Solid)->cxx = atof(a);
                a = Find(a, ",");
                ((SolidQuadric *) Solid[j].Solid)->cxy = atof(a);
                a = Find(a, ",");
                ((SolidQuadric *) Solid[j].Solid)->cxz = atof(a);
                a = Find(a, ",");
                ((SolidQuadric *) Solid[j].Solid)->cx = atof(a);
                a = Find(a, ",");
                ((SolidQuadric *) Solid[j].Solid)->cyy = atof(a);
                a = Find(a, ",");
                ((SolidQuadric *) Solid[j].Solid)->cyz = atof(a);
                a = Find(a, ",");
                ((SolidQuadric *) Solid[j].Solid)->cy = atof(a);
                a = Find(a, ",");
                ((SolidQuadric *) Solid[j].Solid)->czz = atof(a);
                a = Find(a, ",");
                ((SolidQuadric *) Solid[j].Solid)->cz = atof(a);
                a = Find(a, ",");
                ((SolidQuadric *) Solid[j].Solid)->k = atof(a);
                
                a = Find(a, "Map");
                k = atol(a) - 1;

                Solid[j].Map = Map[k];
                Solid[j].MapType = MapType[k];

                printf("Solid %li Quadric, map %li\n", j, k);

                break;
            default:
                printf("Error: Unknown object type at %.40s\n", a);
                exit(EXIT_FAILURE);
                break;
            }
        }

    free(Input);
    free(Map);
    free(MapType);

    Handle = fopen(CameraFile,"rb");
    if(Handle == NULL) {
        printf("Error: File %.32s not found\n", CameraFile);
        exit(EXIT_FAILURE);
        }
    fstat(fileno(Handle), &FileStatus);
    Input = new char[ FileStatus.st_size + 2];
    if(Input == NULL) {
        printf("Error: Out of memory for %.32s\n", CameraFile);
        exit(EXIT_FAILURE);
        }
    fseek(Handle, 0, 0);
    fread(Input, FileStatus.st_size, 1, Handle);
    Input[ FileStatus.st_size + 1 ] = 0;
        printf("Camera file %.32s loaded\nParsing...\n", CameraFile);
    fclose(Handle);

    a = Find(Input, "Cameras");
    Cameras = atol(a);

    Camera = new Projection[Cameras];

    if(Camera == NULL) {
        printf("Error:Cannot initialise %li camera(s)\n", Cameras);
        exit(EXIT_FAILURE);
        }
        
    i = 0;
    do {
        a = Find(a, "Camera");
        j = atol(a) - 1;

        b = Find(a, "Location");
        b = Find(b, "<");
        Camera[j].l.x = atof(b);
        b = Find(b, ",");
        Camera[j].l.y = atof(b);
        b = Find(b, ",");
        Camera[j].l.z = atof(b);

        b = Find(a, "Velocity");
        b = Find(b, "<");
        Camera[j].v.x = atof(b);
        b = Find(b, ",");
        Camera[j].v.y = atof(b);
        b = Find(b, ",");
        Camera[j].v.z = atof(b);

        b = Find(a, "Focus");
        b = Find(b, "<");
        Camera[j].f.x = atof(b);
        b = Find(b, ",");
        Camera[j].f.y = atof(b);
        b = Find(b, ",");
        Camera[j].f.z = atof(b);

        b = Find(a, "Sky");
        b = Find(b, "<");
        Camera[j].s.x = atof(b);
        b = Find(b, ",");
        Camera[j].s.y = atof(b);
        b = Find(b, ",");
        Camera[j].s.z = atof(b);

        } while(++i < Cameras);

    printf("%s parsed successfully\n", CameraFile);

    free(Input);

    }

Arena Scene;

int main(int argc, char *argv[]) {
    time_t Start, End;

    printf("\nRelativistic Raytracer Version 0.2\n    Antony Searle 1997\n\n");

    switch(argc) {
        case 2:
            Scene.Load(argv[1], argv[1]);   // Camera info in scene file
            break;
        case 3:
            Scene.Load(argv[1], argv[2]);   // Camera info separate
            break;
        default:
            printf("Syntax: SceneFile [CameraFile]\n\n");
            exit(EXIT_FAILURE);
            break;
        }

    #ifdef PC
    Screen.SetMode();
    #endif

    if(Globals.HUD) {
        // i = Globals.Sky;
        // Globals.Sky = 0;
        Scene.RenderFrame(*Globals.HUDView, *Globals.HUDMap);    
        // Globals.Sky = i;
        }

    time(&Start);    

    Scene.Run();

    time(&End);    

    #ifdef PC

    //getch();

    Screen.ResetMode();
    #endif

    printf("\nRelativistic Raytracer Version 0.2\n    Antony Searle 1997\n\n");

    switch(argc) {
        case 2:
            printf("Run of %s [%li frame(s)]\n", argv[1], Scene.Cameras);
            break;
        case 3:
            printf("Run of %s and %s [%li frame(s)]\n", argv[1], argv[2], Scene.Cameras);
            break;
        }

    printf("    Saved as %s0000.tga -> %s%.4li.tga\n", Scene.Name, Scene.Name, Scene.Cameras - 1);
    printf("    %g seconds elapsed, %g seconds/frame.\n", difftime(End,Start), difftime(End,Start) / Scene.Cameras);

    }
