
/****************************************************************************
 *
 * VRML 2.0 to RW3.0 Converter
 * Copyright (C) 1998 Criterion Technologies
 *
 * Author  : Damian Scallan 
 *
 * Module  : Extrusion.h
 *                                                                         
 * Purpose : Convertes extrusion nodes
 *                                                                      
 ****************************************************************************/

/****************************************************************************
 Includes
 */

#include "rpplugin.h"
#include <rwcore.h>
#include <rtworld.h>

#include "extrusion.h"
#include "material.h"
#include "rpvrmlanim.h"
#include "rpvrml.h"
#include "builder.h"

static const char __RWUNUSED__ rcsid[] =
    "@@(#)$Id: extrusion.c,v 1.25 2001/08/17 14:57:16 johns Exp $";

/****************************************************************************
 Local (Static) Prototypes
 */

/****************************************************************************
 Local Defines
 */

typedef struct ExtrusionInfo ExtrusionInfo;
struct ExtrusionInfo
{
    sfbool              beginCap;
    sfbool              endCap;
    sfbool              ccw;
    sfbool              solid;
    sfbool              convex;
    sffloat             creaseAngle;
    Field              *crossSection;
    Field              *orientation;
    Field              *spine;
    Field              *scale;
};

/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

   Extrusion  methods

   !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */

static ExtrusionInfo *
ExtrusionInfo_Create(void)
{
    ExtrusionInfo      *ei;

    RWFUNCTION(RWSTRING("ExtrusionInfo_Create"));

    ei = (ExtrusionInfo *) RwMalloc(sizeof(ExtrusionInfo));
    if (!ei)
    {
        RWRETURN(NULL);
    }

    ei->beginCap = FALSE;
    ei->endCap = FALSE;
    ei->ccw = FALSE;
    ei->solid = FALSE;
    ei->convex = FALSE;
    ei->creaseAngle = (sffloat) (0);
    ei->crossSection = NULL;
    ei->orientation = NULL;
    ei->spine = NULL;
    ei->scale = NULL;

    RWRETURN(ei);
}

static              RwBool
ExtrusionInfo_Destroy(ExtrusionInfo * ei)
{
    RWFUNCTION(RWSTRING("ExtrusionInfo_Destroy"));
    RWASSERT(ei);

    if (ei)
    {
        RwFree(ei);

        RWRETURN(TRUE);
    }

    RWRETURN(FALSE);
}

static ExtrusionInfo *
ExtrusionInfo_Setup(ExtrusionInfo * ei, AbstractNode * an)
{
    RWFUNCTION(RWSTRING("ExtrusionInfo_Setup"));
    RWASSERT(ei);
    RWASSERT(an);

    if (ei && an)
    {
        AbstractField      *af;
        Field              *field;

        /* beginCap */
        af = AbstractNode_GetAbstractField(an, RWSTRING("beginCap"));
        if (af)
        {
            field = AbstractField_GetField(af);
            ei->beginCap = *FieldSfbool_GetValue(field);
        }

        /* endCap */
        af = AbstractNode_GetAbstractField(an, RWSTRING("endCap"));
        if (af)
        {
            field = AbstractField_GetField(af);
            ei->endCap = *FieldSfbool_GetValue(field);
        }

        /* ccw */
        af = AbstractNode_GetAbstractField(an, RWSTRING("ccw"));
        if (af)
        {
            field = AbstractField_GetField(af);
            ei->ccw = *FieldSfbool_GetValue(field);
        }

        /* solid */
        af = AbstractNode_GetAbstractField(an, RWSTRING("solid"));
        if (af)
        {
            field = AbstractField_GetField(af);
            ei->solid = *FieldSfbool_GetValue(field);
        }

        /* convex */
        af = AbstractNode_GetAbstractField(an, RWSTRING("convex"));
        if (af)
        {
            field = AbstractField_GetField(af);
            ei->convex = *FieldSfbool_GetValue(field);
        }

        /* crease angles */
        af = AbstractNode_GetAbstractField(an, RWSTRING("creaseAngle"));
        if (af)
        {
            field = AbstractField_GetField(af);
            ei->creaseAngle = *FieldSffloat_GetValue(field);
        }

        /* cross section */
        af = AbstractNode_GetAbstractField(an,
                                           RWSTRING("crossSection"));
        if (af)
        {
            field = AbstractField_GetField(af);
            ei->crossSection = field;
        }

        /* orientation */
        af = AbstractNode_GetAbstractField(an, RWSTRING("orientation"));
        if (af)
        {
            field = AbstractField_GetField(af);
            ei->orientation = field;
        }

        /* spine */
        af = AbstractNode_GetAbstractField(an, RWSTRING("spine"));
        if (af)
        {
            field = AbstractField_GetField(af);
            ei->spine = field;
        }

        /* scale */
        af = AbstractNode_GetAbstractField(an, RWSTRING("scale"));
        if (af)
        {
            field = AbstractField_GetField(af);
            ei->scale = field;
        }

        RWRETURN(ei);
    }

    RWERROR((E_RW_NULLP));
    RWRETURN(NULL);
}

static              RwBool
ExtrusionInfo_CheckValid(ExtrusionInfo * ei)
{
    RWFUNCTION(RWSTRING("ExtrusionInfo_CheckValid"));
    RWASSERT(ei);

    if (ei)
    {
        /* for now just return true */
        RWRETURN(TRUE);
    }

    RWERROR((E_RW_NULLP));
    RWRETURN(FALSE);
}

static              RwBool
Extrusion_SetTriangles(RpGeometry * geometry, ExtrusionInfo * ei)
{
    RWFUNCTION(RWSTRING("Extrusion_SetTriangles"));
    RWASSERT(geometry);
    RWASSERT(ei);

    if (geometry && ei)
    {
        RpTriangle         *triangles;
        RpMaterial         *material;
        RwInt32             numSpinePts, numCsPts;
        RwInt32             spineInd, csInd;

        RwInt32             numTris = 0;

        triangles = RpGeometryGetTriangles(geometry);
        material = Material_GetCurrent();

        numSpinePts = ei->spine->items;
        numCsPts = ei->crossSection->items;

        for (spineInd = 1; spineInd < numSpinePts; spineInd++)
        {
            RwUInt16            baseInd;

            baseInd = (RwUInt16) ((spineInd - 1) * numCsPts);

            for (csInd = 1; csInd < numCsPts; csInd++)
            {
                RwUInt16            ind1, ind2, ind3;

                /* upper triangle */
                ind1 = (RwUInt16) (baseInd + csInd - 1);
                ind2 = (RwUInt16) (ind1 + 1);
                ind3 = (RwUInt16) (ind2 + numCsPts);

                if (!ei->ccw)
                {
                    RpGeometryTriangleSetVertexIndices(geometry,
                                                       triangles, ind3,
                                                       ind2, ind1);
                }
                else
                {
                    RpGeometryTriangleSetVertexIndices(geometry,
                                                       triangles, ind1,
                                                       ind2, ind3);
                }
                RpGeometryTriangleSetMaterial(geometry, triangles,
                                              material);

                /* next triangle */
                triangles++;
                numTris++;

                /* lower triangle */
                ind2 = ind3;
                ind3 = (RwUInt16) (ind1 + numCsPts);

                if (!ei->ccw)
                {
                    RpGeometryTriangleSetVertexIndices(geometry,
                                                       triangles, ind3,
                                                       ind2, ind1);
                }
                else
                {
                    RpGeometryTriangleSetVertexIndices(geometry,
                                                       triangles, ind1,
                                                       ind2, ind3);
                }
                RpGeometryTriangleSetMaterial(geometry, triangles,
                                              material);

                /* next triangle */
                triangles++;
                numTris++;
            }
        }

        RWRETURN(TRUE);
    }

    RWERROR((E_RW_NULLP));
    RWRETURN(FALSE);
}

static              RwBool
Extrusion_SpineIsClosed(ExtrusionInfo * ei)
{
    RWFUNCTION(RWSTRING("Extrusion_SpineIsClosed"));
    RWASSERT(ei);

    if (ei)
    {
        RwInt32             lastPtInd;
        RwV3d              *firstPoint, *lastPoint;

        lastPtInd = ei->spine->items - 1;

        firstPoint = (RwV3d *) FieldMfvec3f_GetValue(ei->spine, 0);
        lastPoint =
            (RwV3d *) FieldMfvec3f_GetValue(ei->spine, lastPtInd);

        if ((firstPoint->x == lastPoint->x) &&
            (firstPoint->y == lastPoint->y) &&
            (firstPoint->z == lastPoint->z))
        {
            RWRETURN(TRUE);
        }

        RWRETURN(FALSE);
    }

    RWERROR((E_RW_NULLP));
    RWRETURN(FALSE);
}

static RwMatrix    *
Extrusion_SpineSetMatrix(ExtrusionInfo * ei, RwMatrix * matrix,
                         RwInt32 spineInd, RwV3d * prevAt)
{
    RWFUNCTION(RWSTRING("Extrusion_SpineSetMatrix"));
    RWASSERT(ei);
    RWASSERT(matrix);
    RWASSERT(prevAt);

    if (ei && matrix && prevAt)
    {
        RwInt32             pt1Ind, pt2Ind;
        RwV3d              *pt1, *pt2, *spinePt;
        RwV3d               at, up, right, pos;
        RwInt32             numSpinePts;
        RwBool              spineClosed;
        RwBool              atUndefined = FALSE;

        spineClosed = Extrusion_SpineIsClosed(ei);
        numSpinePts = ei->spine->items;

        /* current spine point */
        spinePt = (RwV3d *) FieldMfvec3f_GetValue(ei->spine, spineInd);
        pos = *spinePt;

        /* compute the up vector */
        if ((spineInd == 0) || (spineInd == (numSpinePts - 1)))
        {
            /* special case  */
            if (spineClosed)
            {
                pt1Ind = numSpinePts - 2;
                pt2Ind = 1;
            }
            else
            {
                if (spineInd == 0)
                {
                    pt1Ind = spineInd;
                    pt2Ind = spineInd + 1;
                }
                else
                {
                    pt1Ind = spineInd - 1;
                    pt2Ind = spineInd;
                }
                /* at vector will be undefined in this case */
                atUndefined = TRUE;
            }
        }
        else
        {
            /* general case */
            pt1Ind = spineInd - 1;
            pt2Ind = spineInd + 1;
        }

        pt1 = (RwV3d *) FieldMfvec3f_GetValue(ei->spine, pt1Ind);
        pt2 = (RwV3d *) FieldMfvec3f_GetValue(ei->spine, pt2Ind);
        RwV3dSub(&up, pt2, pt1);
        _rwV3dNormalize(&up, &up);

        /* compute the at vector */
        if (!atUndefined)
        {
            RwV3d               s0, s1;
            RwReal              len;

            RwV3dSub(&s0, pt2, spinePt);
            RwV3dSub(&s1, pt1, spinePt);
            RwV3dCrossProduct(&at, &s0, &s1);
            len = RwV3dLength(&at);
            if (len > (RwReal) (0))
            {
                RwReal              dot;

                _rwV3dNormalize(&at, &at);
                dot = RwV3dDotProduct(&at, prevAt);
                if (dot < (RwReal) (0))
                {
                    /* flip it */
                    RwV3dScale(&at, &at, (RwReal) (-1));
                }
            }
            else
            {
                /* spine points must be co-linear in this case */
                /* use the previous at value */
                at = *prevAt;
            }
        }
        else
        {
            /* use the previous at value */
            at = *prevAt;
        }

        /* compute the complete frenet matrix */
        RwV3dCrossProduct(&right, &up, &at);
        *RwMatrixGetAt(matrix) = at;
        *RwMatrixGetUp(matrix) = up;
        *RwMatrixGetRight(matrix) = right;

        /* RwMatrixUpdate(matrix); */
        RwMatrixOptimize(matrix, RWMATRIXOPTIMIZETOLERANCE);

        *prevAt = at;

        /* set any scalling */
        {
            RwV3d               scale3d;
            RwV2d               scale2d;

            if (ei->scale->items > 1)
            {
                scale2d =
                    *(RwV2d *) FieldMfvec2f_GetValue(ei->scale,
                                                     spineInd);
            }
            else
            {
                scale2d =
                    *(RwV2d *) FieldMfvec2f_GetValue(ei->scale, 0);
            }
            scale2d.x =
                (scale2d.x <= (RwReal) (0)) ? (RwReal) (1) : scale2d.x;
            scale2d.y =
                (scale2d.y <= (RwReal) (0)) ? (RwReal) (1) : scale2d.y;
            scale3d.x = scale2d.x;
            scale3d.y = (RwReal) (1);
            scale3d.z = scale2d.y;
            RwMatrixScale(matrix, &scale3d, rwCOMBINEPRECONCAT);
        }

        /* set any rotation */
        {
            sfrotation         *sfr;
            RwV3d               axis;
            RwReal              angle;

            if (ei->orientation->items > 1)
            {
                sfr =
                    FieldMfrotation_GetValue(ei->orientation, spineInd);
            }
            else
            {
                sfr = FieldMfrotation_GetValue(ei->orientation, 0);
            }

            axis.x = sfr->x;
            axis.y = sfr->y;
            axis.z = sfr->z;
            angle = (((sfr->angle)) * ((RwReal) (180.0 / rwPI)));

            RwMatrixRotate(matrix, &axis, angle, rwCOMBINEPRECONCAT);
        }

        /* set the position */
        *RwMatrixGetPos(matrix) = pos;

        /* RwMatrixUpdate(matrix); */
        RwMatrixOptimize(matrix, RWMATRIXOPTIMIZETOLERANCE);

        RWRETURN(matrix);
    }

    RWERROR((E_RW_NULLP));
    RWRETURN(NULL);
}

static RwV3d       *
Extrusion_SpineGetFirstAt(ExtrusionInfo * ei, RwV3d * at)
{
    RWFUNCTION(RWSTRING("Extrusion_SpineGetFirstAt"));
    RWASSERT(ei);
    RWASSERT(at);

    if (ei && at)
    {
        RwV3d               up, *pt1, *pt2;

        pt1 = (RwV3d *) FieldMfvec3f_GetValue(ei->spine, 0);
        pt2 = (RwV3d *) FieldMfvec3f_GetValue(ei->spine, 1);
        RwV3dSub(&up, pt2, pt1);
        _rwV3dNormalize(&up, &up);
        VerctorPerpendicular(at, &up);

        RWRETURN(at);
    }

    RWERROR((E_RW_NULLP));
    RWRETURN(NULL);
}

static              RwBool
Extrusion_SetMorphTarget(RpGeometry * geometry, ExtrusionInfo * ei)
{
    RWFUNCTION(RWSTRING("Extrusion_SetMorphTarget"));
    RWASSERT(geometry);
    RWASSERT(ei);

    if (geometry && ei)
    {
        RpMorphTarget      *morphTarget;
        RwV3d              *vertices;
        RwInt32             spineInd;
        RwInt32             numSpinePts, numCsPts;
        RwV3d               prevAt;

        morphTarget = RpGeometryGetMorphTarget(geometry, 0);
        vertices = RpMorphTargetGetVertices(morphTarget);

        numSpinePts = ei->spine->items;
        numCsPts = ei->crossSection->items;

        /* get the initial at vector */
        Extrusion_SpineGetFirstAt(ei, &prevAt);

        for (spineInd = 0; spineInd < numSpinePts; spineInd++)
        {
            RwMatrix            frenet;
            RwInt32             csPointInd;

            Extrusion_SpineSetMatrix(ei, &frenet, spineInd, &prevAt);

            /* transform the cross-section points */
            for (csPointInd = 0; csPointInd < numCsPts; csPointInd++)
            {
                RwV2d               csPoint2d;
                RwV3d               csPoint3d;

                csPoint2d = *(RwV2d *)
                    FieldMfvec3f_GetValue(ei->crossSection, csPointInd);
                csPoint3d.x = csPoint2d.x;
                csPoint3d.y = (RwReal) (0);
                csPoint3d.z = csPoint2d.y;
                RwV3dTransformPoints(&csPoint3d, &csPoint3d, 1,
                                     &frenet);
                *vertices = csPoint3d;

                vertices++;
            }
        }

        /* set the BoundingSphere */
        {
            RwSphere            bSphere;

            RpMorphTargetCalcBoundingSphere(morphTarget, &bSphere);
            RpMorphTargetSetBoundingSphere(morphTarget, &bSphere);
        }

        RWRETURN(TRUE);
    }

    RWERROR((E_RW_NULLP));
    RWRETURN(FALSE);
}

static RpGeometry  *
Extrusion_Geometry(ExtrusionInfo * ei)
{
    RWFUNCTION(RWSTRING("Extrusion_Geometry"));
    RWASSERT(ei);

    if (ei)
    {
        RpGeometry         *geometry;
        RwInt32             flags;
        RwInt32             numVertices, numTriangles;

        numVertices = ei->crossSection->items * ei->spine->items;
        numTriangles =
            (ei->crossSection->items - 1) * (ei->spine->items - 1) * 2;

        flags =
            rpGEOMETRYLIGHT | rpGEOMETRYNORMALS |
            rpGEOMETRYMODULATEMATERIALCOLOR;
        if (Material_IsCurrentTextured())
        {
            flags |= rpGEOMETRYTEXTURED;
        }

        if (!
            (geometry =
             RpGeometryCreate(numVertices, numTriangles, flags)))
        {
            RWRETURN(NULL);
        }

        if (RpGeometryLock(geometry, rpGEOMETRYLOCKALL))
        {
            Extrusion_SetTriangles(geometry, ei);
            Extrusion_SetMorphTarget(geometry, ei);
#if (0)
            Extrusion_SetVertexInfo(geometry, ei);
#endif /* (0) */
            RtGeometryCalculateVertexNormals(geometry);
            RpGeometryUnlock(geometry);
        }

        RWRETURN(geometry);
    }

    RWERROR((E_RW_NULLP));
    RWRETURN(NULL);
}

RwBool
Extrusion_Create(AbstractNode * an)
{
    RWFUNCTION(RWSTRING("Extrusion_Create"));
    RWASSERT(an);

    if (an)
    {
        char               *nodeName;
        ExtrusionInfo      *ei;

        nodeName = AbstractNode_GetBaseName(an);
        /*RWASSERT(rwstrcmp(nodeName, "Extrusion") == 0); */

        ei = ExtrusionInfo_Create();
        if (!ei)
        {
            RWRETURN(FALSE);
        }

        if (!ExtrusionInfo_Setup(ei, an))
        {
            ExtrusionInfo_Destroy(ei);

            RWRETURN(FALSE);
        }

        if (!ExtrusionInfo_CheckValid(ei))
        {
            ExtrusionInfo_Destroy(ei);

            RWRETURN(FALSE);
        }

        if (Build_GetType() == CLUMP)
        {
            RpGeometry         *geometry;

            geometry = Extrusion_Geometry(ei);
            if (!geometry)
            {
                ExtrusionInfo_Destroy(ei);

                RWRETURN(FALSE);
            }

            if (!Build_AddGeometry(geometry))
            {
                ExtrusionInfo_Destroy(ei);

                RWRETURN(FALSE);
            }
        }
        else                   /* WORLD */
        {

        }

        ExtrusionInfo_Destroy(ei);

#if (0)
        RWERROR((E_RP_VRML_EXTRUSION, an->lineNum));
#endif /* (0) */

        RWRETURN(TRUE);
    }

    RWRETURN(FALSE);
}
