/*
 * NodeNurbsSweptSurface.cpp
 *
 * Copyright (C) 1999 Stephen F. White, 2008 J. "MUFTI" Scheurich
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program (see the file "COPYING" for details); if 
 * not, write to the Free Software Foundation, Inc., 675 Mass Ave, 
 * Cambridge, MA 02139, USA.
 */

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

#include "stdafx.h"
#include "NodeNurbsSweptSurface.h"
#include "Scene.h"
#include "FieldValue.h"
#include "SFInt32.h"
#include "Mesh.h"
#include "MFFloat.h"
#include "MFInt32.h"
#include "MFVec2f.h"
#include "MFVec3f.h"
#include "SFNode.h"
#include "SFBool.h"
#include "SFVec3f.h"
#include "Vec2f.h"
#include "Vec3f.h"
#include "RenderState.h"
#include "DuneApp.h"
#include "NodeCoordinate.h"
#include "NodeNormal.h"
#include "NodeTextureCoordinate.h"
#include "NodeIndexedFaceSet.h"
#include "NodeNurbsCurve2D.h"
#include "NurbsSurfaceDegreeElevate.h"
#include "Util.h"
#include "Field.h"
#include "ExposedField.h"
#include "MoveCommand.h"

ProtoNurbsSweptSurface::ProtoNurbsSweptSurface(Scene *scene)
  : GeometryProto(scene, "NurbsSweptSurface")
{
    crossSectionCurve.set(
          addExposedField(SFNODE, "crossSectionCurve", new SFNode(NULL), 
                          VRML_NURBS_CURVE_2D));
    trajectoryCurve.set(
          addExposedField(SFNODE, "trajectoryCurve", new SFNode(NULL), 
                          VRML_NURBS_CURVE));
    ccw.set(
          addField(SFBOOL, "ccw", new SFBool(true)));
    solid.set(
          addField(SFBOOL, "solid", new SFBool(true)));
}

Node *
ProtoNurbsSweptSurface::create(Scene *scene)
{
    return new NodeNurbsSweptSurface(scene, this); 
}

NodeNurbsSweptSurface::NodeNurbsSweptSurface(Scene *scene, Proto *proto)
  : MeshMorphingNode(scene, proto)
{
    _extrusionDirty = true;
    _extrusion = (NodeExtrusion *) scene->createNode("Extrusion");
    _crossSectionCurve = (NodeNurbsCurve *) scene->createNode("NurbsCurve");
    _crossSectionCurve->setInternal(true);
}

NodeNurbsSweptSurface::~NodeNurbsSweptSurface()
{
    delete _extrusion;
    delete _crossSectionCurve;
}

void
NodeNurbsSweptSurface::createExtrusion()
{
    NodeNurbsCurve *spineCurve = (NodeNurbsCurve *) 
                                 trajectoryCurve()->getValue();
    if (spineCurve == NULL)
        return;

    NodeNurbsCurve2D *crossSectionCurve2D = (NodeNurbsCurve2D *)
                                            crossSectionCurve()->getValue();
    if (crossSectionCurve2D == NULL)
        return;
    
    MFVec2f *crossSectionPoints = crossSectionCurve2D->controlPoint();
    if (crossSectionPoints == NULL)
        return;
    if (crossSectionPoints->getSize() == 0)
        return;

    if (_extrusion == NULL)
        _extrusion = (NodeExtrusion *) _scene->createNode("Extrusion");

    if (_crossSectionCurve == NULL)
        _crossSectionCurve = (NodeNurbsCurve *) 
                             _scene->createNode("NurbsCurve");


    int len = crossSectionPoints->getSize() / 2;
    float *points = new float[len * 3];
    int i;
    for (i = 0; i < len; i++) {
         for (int j = 0; j < 2; j++)
             points[i * 3 + j] = crossSectionPoints->getValue(i)[j];
         points[i * 3 + 2] = 0.0f;
    }     
    _crossSectionCurve->setControlPoints(new MFVec3f(points, len * 3));
    float *weights = new float[crossSectionCurve2D->weight()->getSize()];
    for (i = 0; i < crossSectionCurve2D->weight()->getSize(); i++)
         weights[i] = crossSectionCurve2D->weight()->getValues()[i];
    _crossSectionCurve->weight(new MFFloat(weights, crossSectionCurve2D->
                                           weight()->getSize()));
    float *knots = new float[crossSectionCurve2D->knot()->getSize()];
    for (i = 0; i < crossSectionCurve2D->knot()->getSize(); i++)
         knots[i] = crossSectionCurve2D->knot()->getValues()[i];
    _crossSectionCurve->knot(new MFFloat(knots, crossSectionCurve2D->
                                                knot()->getSize()));
    _crossSectionCurve->order(new SFInt32(crossSectionCurve2D->order()->
                                          getValue()));
    _crossSectionCurve->tessellation(new SFInt32(crossSectionCurve2D->
                                                 tessellation()->getValue()));
    const Vec3f *chain = _crossSectionCurve->getChain();
    int chainLength = _crossSectionCurve->getChainLength();
    float *fchain = new float[chainLength * 2];
    for (i = 0; i < chainLength; i++) {
         fchain[i * 2    ] = chain[i].x;
         fchain[i * 2 + 1] = chain[i].y;
    }   
    _extrusion->crossSection(new MFVec2f(fchain, chainLength * 2));
    
    bool bsolid = false;

    _extrusion->solid(new SFBool(solid()->getValue()));
    _extrusion->ccw(new SFBool(ccw()->getValue()));
    _extrusion->creaseAngle(new SFFloat(M_PI / 2.0f));
    _extrusion->beginCap(new SFBool(false));
    _extrusion->endCap(new SFBool(false));

    chain = spineCurve->getChain();
    chainLength = spineCurve->getChainLength();
    fchain = new float[chainLength * 3];
    for (i = 0; i < chainLength; i++) {
         fchain[i * 3    ] = chain[i].x;
         fchain[i * 3 + 1] = chain[i].y;
         fchain[i * 3 + 2] = chain[i].z;
    }   
    _extrusion->spine(new MFVec3f(fchain, chainLength * 3));
    _extrusionDirty = false;
}

void
NodeNurbsSweptSurface::createMesh(bool cleanDoubleVertices)
{
    if (_extrusionDirty) {
        createExtrusion();
        _extrusionDirty = false;
    }

    if (!_extrusion) return;

    _mesh = _extrusion->getMesh();
    _meshDirty = false;
}

void
NodeNurbsSweptSurface::update()
{
    _extrusionDirty = true;
    _meshDirty = true;
}

void
NodeNurbsSweptSurface::setField(int index, FieldValue *value)
{
    _extrusionDirty = true;
    Node::setField(index, value);
    if (index == trajectoryCurve_Field())
        if (trajectoryCurve()->getValue() != NULL)
            trajectoryCurve()->getValue()->setInternal(true);
    if (index == crossSectionCurve_Field())
        if (crossSectionCurve()->getValue() != NULL)
            crossSectionCurve()->getValue()->setInternal(true);
    update();
}

void
NodeNurbsSweptSurface::draw()
{
    if (crossSectionCurve()->getValue() == NULL)
        return;
    if (trajectoryCurve()->getValue() == NULL)
        return;
    if (_extrusionDirty) {
        createExtrusion();
        _extrusionDirty = false;
    }

    if (!_extrusion) return;

    _extrusion->draw();
}

void 
NodeNurbsSweptSurface::findInterpolators(InterpolatorInfo& info)
{
    // there is only one (VRML97) matching interpolator
    // (to trajectoryCurve->controlPoint)

    NodeNurbsCurve *ntrajectoryCurve = (NodeNurbsCurve *) 
                                       trajectoryCurve()->getValue();
    if (ntrajectoryCurve) {
        Node *node = NULL;
        int field = -1;
        if (_scene->isX3d()) {
            node = ntrajectoryCurve->controlPointX3D()->getValue();
            field = ((NodeCoordinate *)node)->point_Field();
        } else {
            node = ntrajectoryCurve;
            field = ntrajectoryCurve->controlPoint_Field();
        }

        Interpolator *inter = _scene->findUpstreamInterpolator(node, field); 
        if (inter != NULL) {
            info.interpolator.append(inter);
            info.field.append(trajectoryCurve_Field());
        }
    }
}

void
NodeNurbsSweptSurface::copyData(NurbsSweptSurfaceData *data)
{
    NodeNurbsCurve *ntrajectoryCurve = (NodeNurbsCurve *) 
                                       trajectoryCurve()->getValue();
    if (ntrajectoryCurve) {
        if (_scene->isX3d()) {
            Node *node = ntrajectoryCurve->controlPointX3D()->getValue();
            MFVec3f *controlPoints = ((NodeCoordinate *)node)->point();
            data->controlPoint = (MFVec3f *)controlPoints->copy();
        } else {
            MFVec3f *controlPoints = ntrajectoryCurve->controlPoint();
            data->controlPoint = (MFVec3f *)controlPoints->copy();
        }
    }
}

void *
NodeNurbsSweptSurface::initializeData(void)
{
    NurbsSweptSurfaceData *data = new NurbsSweptSurfaceData();

    copyData(data);
    copyData(&_tempStoreData);

    return data;
}

void
NodeNurbsSweptSurface::loadDataFromInterpolators(void *nurbsSweptSurfaceData, 
                                                 Interpolator *inter,
                                                 int field, float key)
{
    NurbsSweptSurfaceData *data = (NurbsSweptSurfaceData *)
                                   nurbsSweptSurfaceData;
    inter->interpolate(key, (float *)data->controlPoint->getValues());
}

void
NodeNurbsSweptSurface::createMeshFromData(void* nurbsSweptSurfaceData, 
                                          bool optimize)
{
    NurbsSweptSurfaceData *data = (NurbsSweptSurfaceData *)
                                  nurbsSweptSurfaceData;
    NodeNurbsCurve *ntrajectoryCurve = (NodeNurbsCurve *) 
                                       trajectoryCurve()->getValue();
    if (ntrajectoryCurve) {
        MFVec3f *newData = (MFVec3f *)data->controlPoint->copy();
        if (_scene->isX3d()) {
            Node *node = ntrajectoryCurve->controlPointX3D()->getValue();
            ((NodeCoordinate *)node)->point(new MFVec3f(newData));
        } else {
            ntrajectoryCurve->controlPoint(new MFVec3f(newData));
        }
        _extrusionDirty = true;
        createMesh(optimize);
    }    
}

void
NodeNurbsSweptSurface::finalizeData(void* data)
{
    _extrusionDirty = true;
    createMeshFromData(&_tempStoreData, false);
    delete (NurbsSweptSurfaceData *)data;
}

Node *
NodeNurbsSweptSurface::convert2X3d(void)
{
    NodeNurbsCurve *ntrajectoryCurve = (NodeNurbsCurve *) 
                                       trajectoryCurve()->getValue();
    if (ntrajectoryCurve)
        ntrajectoryCurve->convert2X3d();
    return NULL;
}


Node *
NodeNurbsSweptSurface::convert2Vrml(void) 
{
    NodeNurbsCurve *ntrajectoryCurve = (NodeNurbsCurve *) 
                                       trajectoryCurve()->getValue();
    if (ntrajectoryCurve)
        ntrajectoryCurve->convert2Vrml();
    return NULL;
}

Vec3f   
NodeNurbsSweptSurface::getMinBoundingBox(void)
{
    if (_extrusionDirty) {
        createExtrusion();
        _extrusionDirty = false;
    }
 
    return _extrusion->getMinBoundingBox();
}

Vec3f   
NodeNurbsSweptSurface::getMaxBoundingBox(void)
{
    if (_extrusionDirty) {
        createExtrusion();
        _extrusionDirty = false;
    }
 
    return _extrusion->getMaxBoundingBox();
}

void
NodeNurbsSweptSurface::flip(int index)
{
    Node *ncrossSectionCurve = crossSectionCurve()->getValue();    
    if (ncrossSectionCurve)
        ncrossSectionCurve->flip(index);
    Node *ntrajectoryCurve = trajectoryCurve()->getValue();    
    if (ntrajectoryCurve)
        ntrajectoryCurve->flip(index);
    SFBool *bccw = new SFBool(!(ccw()->getValue()));
    ccw(bccw);
    _meshDirty = true;    
}

void
NodeNurbsSweptSurface::swap(int fromTo)
{
    Node *ncrossSectionCurve = crossSectionCurve()->getValue();    
    if (ncrossSectionCurve)
        ncrossSectionCurve->swap(fromTo);
    Node *ntrajectoryCurve = trajectoryCurve()->getValue();    
    if (ntrajectoryCurve)
        ntrajectoryCurve->swap(fromTo);
    SFBool *bccw = new SFBool(!(ccw()->getValue()));
    ccw(bccw);
    _meshDirty = true;    
}

Node *
NodeNurbsSweptSurface::toExtrusion(void)
{
    if (_extrusionDirty) {
        createExtrusion();
        _extrusionDirty = false;
    }

    if (!_extrusion) 
        return NULL;

    return _extrusion->copy();
}

