/*-------------------------------------------------------------------------*/
/*  J3W ver 6.43  3D Animation Kit                                         */
/*  spaceh3d.cpp   4/ 8/2001                                               */
/*  Copyright (C) 1996 - 2001  Jun Mizutani  <mizutani.jun@nifty.ne.jp>    */
/*                      All rights reserved.                               */
/*                                                                         */
/*   This file is part of the J3W 3D Animation Kit, and is covered under   */
/*  the terms of the GNU General Public License, version 2. This file has  */
/*  NO WARRANTY. See file COPYING for copyright details.                   */
/*                                                                         */
/*-------------------------------------------------------------------------*/

#ifdef WS
#include <stdio.h>
#else //WS
#include <iostream.h>
#endif //WS
#include <stdlib.h>
#include <math.h>
#include "spaceh3d.h"

ClipType   FrontClip, BackClip, LeftClip, RightClip, TopClip, BottomClip;

TSpaceH3D::TSpaceH3D(int objNo, long front, long back):
                    light_dir(100.0, 0.0, 0.0)
{
    maxObj = objNo;
    pEye = 0;
    pLight = 0;
    ObjArray = new THObj3D*[maxObj];
    for(int i=0; i<objNo; i++) ObjArray[i] = 0;
    ObjCount = 0;
    VertCount = 0;
    angle = 0;
    parallel = 1;
    ColorNum = ColNum;
    Shade    = ColWidth;
    ClippedVertex = new Vector[MAX2DVERTEX];
    pglist = new PolygonType[MAXPOLYGON];
    pglistmax = 0;
    Polygon2D = new Vector2D[MAX2DVERTEX];
    max2D = 0;
    set_clipplane(front, back);
}

TSpaceH3D::~TSpaceH3D()
{
    reset_space();
    delete ObjArray;
    delete ClippedVertex;
    delete pglist;
    delete Polygon2D;
}

void TSpaceH3D::set_angle(int a)
{
   if (a == 0) angle = 0;
   else angle = 1;
}

void TSpaceH3D::reset_space()
{
    for(int i=0; i<maxObj; i++) delete_object(i);
}

void TSpaceH3D::delete_object(int n)
{
    if ((ObjCount > 0) && (n < maxObj) && (ObjArray[n] != 0)) {
        delete ObjArray[n];
        ObjArray[n] = 0;
        ObjCount--;
  }
}

int TSpaceH3D::append_object(int vn, int pn)
{
    if (ObjCount < maxObj) {
        int i = 0;
        while ((i<(maxObj-1)) && (ObjArray[i] != 0)) i++;
        ObjArray[i] = new THObj3D(vn, pn, 0);
        ObjCount++;
        return i;
    } else return -1;
}

int TSpaceH3D::append_child_object(int n, int vn, int pn)
{
    if (ObjCount < maxObj) {
        int i = 0;

        while ((i<(maxObj-1)) && (ObjArray[i] != 0)) i++;

        if (ObjArray[n]->child == 0) {
            ObjArray[i] = new THObj3D(vn, pn, ObjArray[n]);
            ObjArray[n]->child = ObjArray[i];
        } else {
            THObj3D *p = ObjArray[n]->child;
            while (p->brother != 0) p = p->brother;

            ObjArray[i] = new THObj3D(vn, pn, ObjArray[n]);
            p->brother = ObjArray[i];
        }
        ObjCount++;
        return i;
    } else return -1;
}

int TSpaceH3D::count_vertex()
{
    int n = 0;
    for(int i=0; i<maxObj; i++)
         if (ObjArray[i]) n += ObjArray[i]->Polygon.Vertex.GetCount();
    return n;
}

int TSpaceH3D::count_polygon()
{
    int n = 0;
    for(int i=0; i<maxObj; i++)
        if (ObjArray[i]) n += ObjArray[i]->Polygon.GetCount();
    return n;
}

void TSpaceH3D::display(long delta)
{
    if (pEye!=0) {
#ifdef WS
        PScreen->beginDraw();
		int i;
        for(i = 0; i<maxObj; i++)
#else //WS
        for(int i = 0; i<maxObj; i++)
#endif //WS
            if ((ObjArray[i] != 0) && (ObjArray[i]->parent == 0))
                ObjArray[i]->SetNodeWorldMatrix(delta);

#ifdef WS
        for(i=0; i<maxObj; i++)
#else //WS
        for(int i=0; i<maxObj; i++)
#endif //WS
            if (ObjArray[i] != 0) ObjArray[i]->show(pEye);
        if (0 < maxObj) {
            Clip3D_PolygonList();
            Polygon3Dto2D();
            ShadePolygon();
            if (pglistmax > 0) DepthSort();
        }
        if (PScreen->GetColorMode() == TRUECOLOR) {
            for(int i=0; i<max2D; i++)
                if (pglist[i].color >= 0) {
                    if (pglist[i].n == 2)
                        PScreen->line(Polygon2D[ pglist[i].index ],
                            Polygon2D[ pglist[i].index+1 ], pglist[i].color);
                    else {
                        int col = pglist[i].color;
                        if ((col == 255) || ((col > 15) && (col < 43))) {
                            PScreen->polygon(&Polygon2D[pglist[i].index],
                                              pglist[i].n, pglist[i].r+SYSCOLOR);
                        } else {
                            PScreen->polygon_rgb(&Polygon2D[pglist[i].index],
                                                  pglist[i].n, pglist[i].r,
                                                  pglist[i].g, pglist[i].b);
                        }
                    }
                }
        } else {
            for(int i=0; i<max2D; i++)
                if (pglist[i].color >= 0) {
                    if (pglist[i].n == 2)
                        PScreen->line(Polygon2D[ pglist[i].index ],
                            Polygon2D[ pglist[i].index+1 ], pglist[i].color);
                    else {
                        int col = pglist[i].r;
                        if ((col == 255) || ((col > 15) && (col < 43))) {
                            PScreen->polygon(&Polygon2D[pglist[i].index],
                                              pglist[i].n, pglist[i].r);
                        } else {
                            PScreen->polygon(&Polygon2D[pglist[i].index],
                                     pglist[i].n, pglist[i].r);
                        }
                    }
                }
        }
#ifdef WS
        PScreen->endDraw();
#endif //WS
    }
}

void TSpaceH3D::set_clipplane(long front, long back)
{
    FrontClip.kx = 1;   FrontClip.ky = 0;
    FrontClip.kz = 0;   FrontClip.m = -front;
    BackClip.kx = -1;   BackClip.ky = 0;
    BackClip.kz = 0;    BackClip.m = back;
    LeftClip.kx = 1;    LeftClip.ky = 1;
    LeftClip.kz = 0;    LeftClip.m = 0;
    RightClip.kx = 1;   RightClip.ky = -1;
    RightClip.kz = 0;   RightClip.m = 0;
    TopClip.kx = 1;     TopClip.ky = 0;
    TopClip.kz = 1;     TopClip.m  = 0;
    BottomClip.kx = 1;  BottomClip.ky = 0;
    BottomClip.kz = -1; BottomClip.m  = 0;
}

void  TSpaceH3D::ClipLine(Vector &p1, Vector &p2, ClipType &clip, Vector &p)
{
    long dx = p2.x - p1.x;
    long dy = p2.y - p1.y;
    long dz = p2.z - p1.z;
    double numerator = clip.kx*p1.x + clip.ky*p1.y + clip.kz*p1.z + clip.m;
    double denominator = -clip.kx*dx - clip.ky*dy - clip.kz*dz;
#ifdef WS
    p.x = j3w_round(dx * numerator / denominator) + p1.x;
    p.y = j3w_round(dy * numerator / denominator) + p1.y;
    p.z = j3w_round(dz * numerator / denominator) + p1.z;
#else //WS
    p.x = round(dx * numerator / denominator) + p1.x;
    p.y = round(dy * numerator / denominator) + p1.y;
    p.z = round(dz * numerator / denominator) + p1.z;
#endif //WS
}

inline int TSpaceH3D::visible(Vector &p, ClipType &clip)
{
    return (clip.kx*p.x + clip.ky*p.y + clip.kz*p.z + clip.m > 0);
}

int TSpaceH3D::Clip_polygon(int n, Vector *polygon_in, Vector* polygon_out,
                        ClipType &clipEq)
{
    Vector p1, p2 ,p;

    int OutCount = 0;

    if (n == 2) {
        p1 = polygon_in[0];
        p2 = polygon_in[1];
        if (visible(p1 , clipEq)) {
            polygon_out[OutCount++] = p1;
            if (!visible(p2, clipEq)) {
                ClipLine(p1,p2,clipEq,p);
                polygon_out[OutCount++] = p;
            } else polygon_out[OutCount++] = p2;
        } else {
            if (visible(p2, clipEq)) {
                ClipLine(p1,p2,clipEq,p);
                polygon_out[OutCount++] = p;
                polygon_out[OutCount++] = p2;
            }
        }
        return OutCount;
    }

    for ( int i=0; i<n; i++) {
        p1 = polygon_in[i];
        if (i != n-1) p2 = polygon_in[i+1];
        else p2 = polygon_in[0];
        if (visible(p1 , clipEq)) {
            polygon_out[OutCount++] = p1;
            if (! visible(p2, clipEq)) {
                ClipLine(p1,p2,clipEq,p);
                polygon_out[OutCount++] = p;
            }
        } else {
            if (visible(p2, clipEq)) {
                ClipLine(p1,p2,clipEq,p);
                polygon_out[OutCount++] = p;
            }
        }
    }
    return OutCount;
}

void TSpaceH3D::Clip_volume(int &n, Vector *polygon1)
{
    Vector polygon2[POLYGONVERTEX];
    n = Clip_polygon(n,polygon1, polygon2, BackClip);
    if (n > 0) n = Clip_polygon(n, polygon2, polygon1, FrontClip);
    if (n > 0) n = Clip_polygon(n, polygon1, polygon2, LeftClip);
    if (n > 0) n = Clip_polygon(n, polygon2, polygon1, RightClip);
    if (n > 0) n = Clip_polygon(n, polygon1, polygon2, TopClip);
    if (n > 0) n = Clip_polygon(n, polygon2, polygon1, BottomClip);
}

void TSpaceH3D::Clip3D_PolygonList()
{
    int n;
    Vector va[POLYGONVERTEX];
    Polygon3D *p;
    TPolygons *po;

    double wh_ratio = double(PScreen->get_width()) / double(PScreen->get_height());
    pglistmax = 0;
    VertCount = 0;
    for(int i=0; i<maxObj; i++)
        if (ObjArray[i] != 0) {
            po = &ObjArray[i]->Polygon;
            for(int j = 0; j<po->GetCount(); j++)
                if (po->pg[j] != 0) {
                    n = po->pg[j]->n;
                    p = po->pg[j];
                    int kk = 0;
                    for(int k=0; k<n; k++) {
                        if (p->index[k] < 0) {
                            THObj3D* pp = ObjArray[i]->parent;
                            if (pp != 0) {
                                int m = -(p->index[k]);
                                if (m < pp->Polygon.Vertex.GetCount()) {
                                    va[kk] = pp->Polygon.Vertex.vert[m]->eye;
#ifdef WS
                                    va[kk].z = j3w_round(va[kk].z * wh_ratio);
#else //WS
                                    va[kk].z = round(va[kk].z * wh_ratio);
#endif //WS
                                    if (angle == 1) va[kk].x = va[kk].x >> 1;
                                    kk++;
                                }
                            }
                        } else {
                            va[kk] = po->Vertex.vert[p->index[k]]->eye;
#ifdef WS
                            va[kk].z = j3w_round(va[kk].z * wh_ratio);
#else //WS
                            va[kk].z = round(va[kk].z * wh_ratio);
#endif //WS
                            if (angle == 1) va[kk].x = va[kk].x >> 1;
                            kk++;
                        }
                    }
                    n = kk;
                    Clip_volume(n, va);
                    if (n != 0) {
                        pglist[pglistmax].index = VertCount;
                        pglist[pglistmax].n = n;
                        pglist[pglistmax].color = p->color;
                        pglist[pglistmax].reflection = p->reflection;
                        pglist[pglistmax].ambient = p->ambient;
                        pglist[pglistmax].off_center = p->off_center;
                        for(int k=0; k<n; k++) ClippedVertex[VertCount+k] = va[k];
                        if (pglistmax < (MAXPOLYGON-1)) pglistmax++;
                        VertCount += n;
                    }
            }
        }
}

int SortCompare(const void *p, const void *q )
{
    if ( ((PolygonType *)p)->off_center == ((PolygonType *)q)->off_center)
        return 0;
    if ( ((PolygonType *)p)->off_center > ((PolygonType *)q)->off_center)
        return -1;
    else return 1;
}

void TSpaceH3D::DepthSort()
{
    qsort(pglist, pglistmax, sizeof(PolygonType), SortCompare);
}

void TSpaceH3D::Polygon3Dto2D()
{
    int  k, m;
    double xx, yy, zz;

    max2D = pglistmax;
    for (int i = 0; i<pglistmax; i++) {
        k = pglist[i].n;
        m = pglist[i].index;
        for(int j=0; j<k; j++) {
            xx = ClippedVertex[m+j].x;
            yy = ClippedVertex[m+j].y;
            zz = ClippedVertex[m+j].z;
            Polygon2D[m+j].x = int((yy / xx + 1.0)*(PScreen->get_width()-2) /2)
                                        + PScreen->get_left()+1;
            Polygon2D[m+j].y = int((1.0 + zz / xx)*(PScreen->get_height()-2) /2)
                                        + PScreen->get_top()+1;
        }
    }
}

double Power(double X, int N)
{
  if (X == 0) return 0;
  return exp(N * log(X));
}

void shading(double cos_theta, double specl, PolygonType *p)
{
    double specular = specl * HIGHLIGHT * p->reflection * 255;
    if (cos_theta < 0) cos_theta = 0;
    cos_theta = cos_theta * (1.0 - p->ambient) + p->ambient;
    p->r = int(0.5 + p->r * cos_theta + specular);
    if (p->r > 255) p->r = 255;
    p->g = int(0.5 + p->g * cos_theta + specular);
    if (p->g > 255) p->g = 255;
    p->b = int(0.5 + p->b * cos_theta + specular);
    if (p->b > 255) p->b = 255;
}

void TSpaceH3D::ShadePolygon()
{
    int  k, m;
    double x0,x1,x2,y0,y1,y2,z0,z1,z2;
    double cos_theta;

    max2D = pglistmax;
    for (int i = 0; i<pglistmax; i++) {
        k = pglist[i].n;
        m = pglist[i].index;

        if (k == 2) {
            if (pglist[i].color >= 0x100) {

                pglist[i].color &= 0xFF;
            } else if ((pglist[i].color > 15) && (ColorNum <= 16)) {

                if (pglist[i].color < 43)
                    pglist[i].color = ColNum*ColWidth + pglist[i].color -16 + SYSCOLOR;
            } else  pglist[i].color = (pglist[i].color) * Shade + SYSCOLOR;
        } else {
            x0 = ClippedVertex[m + 0].x;
            x1 = ClippedVertex[m + 1].x;
            x2 = ClippedVertex[m + 2].x;
            y0 = ClippedVertex[m + 0].y;
            y1 = ClippedVertex[m + 1].y;
            y2 = ClippedVertex[m + 2].y;
            z0 = ClippedVertex[m + 0].z;
            z1 = ClippedVertex[m + 1].z;
            z2 = ClippedVertex[m + 2].z;

            if ((x0 == x1) && (y0 == y1) && (z0 == z1) && (k > 3)) {
                x1 = x2; y1 = y2; z1 = z2;
                x2 = ClippedVertex[m + 3].x;
                y2 = ClippedVertex[m + 3].y;
                z2 = ClippedVertex[m + 3].z;
            }
            if ((x0 == x2) && (y0 == y2) && (z0 == z2) && (k > 3)) {
                x2 = ClippedVertex[m + 3].x;
                y2 = ClippedVertex[m + 3].y;
                z2 = ClippedVertex[m + 3].z;
            }
            if ((x1 == x2) && (y1 == y2) && (z1 == z2) && (k > 3)) {
                x2 = ClippedVertex[m + 3].x;
                y2 = ClippedVertex[m + 3].y;
                z2 = ClippedVertex[m + 3].z;
            }
            if (angle == 1) {
                x0 = x0 * 2;
                x1 = x1 * 2;
                x2 = x2 * 2;
            }
            Vector_d normal;
            normal.x = (y1-y0)*(z2-z1) - (z1-z0)*(y2-y1);
            normal.y = (z1-z0)*(x2-x1) - (x1-x0)*(z2-z1);
            normal.z = (x1-x0)*(y2-y1) - (y1-y0)*(x2-x1);
            normal.normalize();

            long sum_x = 0;
            long sum_y = 0;
            long sum_z = 0;
            for(int j=0; j < k; j++) {
              sum_x += ClippedVertex[m + j].x;
              sum_y += ClippedVertex[m + j].y;
              sum_z += ClippedVertex[m + j].z;
            }
            sum_x /= k;
            sum_y /= k;
            sum_z /= k;
            pglist[i].center_g = Vector(sum_x, sum_y, sum_z);

            if (angle == 1) pglist[i].center_g.x *= 2;

            pglist[i].off_center += pglist[i].center_g.x;

            Vector_d center = Vector(0,0,0) - pglist[i].center_g;
            center.normalize();
            cos_theta = center.dot_product(normal);
            if (cos_theta < 0) pglist[i].color = -1;
            else if (pglist[i].color >= 256) {

                pglist[i].color &= 0xFF;
            } else if ((pglist[i].color > 15) && (ColorNum <= 16)) {

                if (pglist[i].color < 43)
                    pglist[i].r = ColNum*ColWidth + pglist[i].color -16;
            } else {
                if (PScreen->GetColorMode() == PSEUDOCOLOR) {

                    pglist[i].r = (pglist[i].color) * Shade
                            + int((1-cos_theta)*(Shade-1)+0.5) + SYSCOLOR;
                } else {

                    int pal_color = pglist[i].color * Shade;
                    pglist[i].r = MyPalette[ pal_color].r;
                    pglist[i].g = MyPalette[ pal_color].g;
                    pglist[i].b = MyPalette[ pal_color].b;

                    Vector_d light_vec;
                    if (parallel) {
                        if (pLight == 0) {
                            light_vec = light_dir;
                            light_vec.neg();
                        } else {

                            Vector dir = pLight->GetDirection_world(light_dir);
                            light_vec = pEye->GetDirection_eye(dir);
                            light_vec.neg();
                        }
                     } else {
                        if (pLight == 0) {

                            light_vec = center;
                        } else {
                            Vector light;
                            long h, p, b;
                            pLight->GetWorldPosition(light, h, p, b);
                            light = pEye->WorldToLocal(light);
                            light_vec = light - pglist[i].center_g;
                        }
                    }
                    light_vec.normalize();

                    double cos_ln = light_vec.dot_product(normal);
                    Vector_d reflection = 2 * cos_ln * normal - light_vec;

                    double cos_nr = center.dot_product(reflection);
                    if (cos_nr < 0) cos_nr = 0;
                    double specular = Power(cos_nr, POWER);
                    shading(cos_ln, specular, &pglist[i]);
                }
            }
        }
    }
}

