/*
 * NodeIndexedFaceSet.cpp
 *
 * Copyright (C) 1999 Stephen F. White
 * 
 * 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 <stdio.h>
#include <string.h>
#include "stdafx.h"

#include "NodeIndexedFaceSet.h"
#include "Proto.h"
#include "Scene.h"
#include "FieldValue.h"
#include "Node.h"
#include "Mesh.h"
#include "Face.h"
#include "Vec3f.h"
#include "NodeColor.h"
#include "NodeColorRGBA.h"
#include "NodeCoordinate.h"
#include "NodeNormal.h"
#include "NodeTextureCoordinate.h"
#include "NodeIndexedLineSet.h"

ProtoIndexedFaceSet::ProtoIndexedFaceSet(Scene *scene)
  : GeometryProto(scene, "IndexedFaceSet")
{
    color.set(
          addExposedField(SFNODE, "color", new SFNode(NULL), COLOR_NODE));
    coord.set(
          addExposedField(SFNODE, "coord", new SFNode(NULL), COORDINATE_NODE));
    normal.set(
          addExposedField(SFNODE, "normal", new SFNode(NULL), VRML_NORMAL));
    texCoord.set(
          addExposedField(SFNODE, "texCoord", new SFNode(NULL), 
                          TEXTURE_COORDINATE_NODE));
    if (TheApp->getCoverMode()) {
        // non standard Covise/COVER extensions
        texCoord2.set(
          addExposedField(SFNODE, "texCoord2", new SFNode(NULL), 
                          TEXTURE_COORDINATE_NODE));
        setFieldFlags(texCoord2, FF_COVER_ONLY);
        texCoord3.set(
          addExposedField(SFNODE, "texCoord3", new SFNode(NULL), 
                          TEXTURE_COORDINATE_NODE));
        setFieldFlags(texCoord3, FF_COVER_ONLY);
        texCoord4.set(
          addExposedField(SFNODE, "texCoord4", new SFNode(NULL), 
                          TEXTURE_COORDINATE_NODE));
        setFieldFlags(texCoord4, FF_COVER_ONLY);
    }
    ccw.set(
          addField(SFBOOL, "ccw", new SFBool(true)));
    colorIndex.set(
          addField(MFINT32, "colorIndex", new MFInt32(), new SFInt32(-1)));
    colorPerVertex.set(
          addField(SFBOOL, "colorPerVertex", new SFBool(true)));
    convex.set(
          addField(SFBOOL, "convex", new SFBool(true)));
    coordIndex.set(
          addField(MFINT32, "coordIndex", new MFInt32(), new SFInt32(-1)));
    creaseAngle.set(
          addField(SFFLOAT, "creaseAngle", new SFFloat(0.0), 
                   new SFFloat(0.0f)));
    normalIndex.set(
          addField(MFINT32, "normalIndex", new MFInt32(), new SFInt32(-1)));
    normalPerVertex.set(
          addField(SFBOOL, "normalPerVertex", new SFBool(true)));
    solid.set(
          addField(SFBOOL, "solid", new SFBool(true)));
    texCoordIndex.set(
          addField(MFINT32, "texCoordIndex", new MFInt32(), new SFInt32(-1)));
    if (TheApp->getCoverMode()) {
        // non standard Covise/COVER extensions
        texCoordIndex2.set(
          addField(MFINT32, "texCoordIndex2", new MFInt32(), new SFInt32(-1)));
        setFieldFlags(texCoordIndex2, FF_COVER_ONLY);
        texCoordIndex3.set(
          addField(MFINT32, "texCoordIndex3", new MFInt32(), new SFInt32(-1)));
        setFieldFlags(texCoordIndex3, FF_COVER_ONLY);
        texCoordIndex4.set(
          addField(MFINT32, "texCoordIndex4", new MFInt32(), new SFInt32(-1)));
        setFieldFlags(texCoordIndex4, FF_COVER_ONLY);
    }
    ComposedGeometryElements()
    addEventIn(MFINT32, "set_colorIndex", 0, colorIndex);
    addEventIn(MFINT32, "set_coordIndex", 0, coordIndex);
    addEventIn(MFINT32, "set_normalIndex", 0, normalIndex);
    addEventIn(MFINT32, "set_texCoordIndex", 0, texCoordIndex);
}

Node *
ProtoIndexedFaceSet::create(Scene *scene)
{ 
      Node *node = new NodeIndexedFaceSet(scene, this);
      return node;
//    return new NodeIndexedFaceSet(scene, this); 
}

NodeIndexedFaceSet::NodeIndexedFaceSet(Scene *scene, Proto *def)
  : MeshBasedNode(scene, def)
{
}

NodeIndexedFaceSet::~NodeIndexedFaceSet()
{
}

void 
NodeIndexedFaceSet::draw()
{
    Node *ncoord = coord()->getValue();
    if (ncoord != NULL) {
        glPushName(coord_Field());       // field coord
        glPushName(0);                   // index 0
        ((NodeCoordinate *)ncoord)->draw(this);
        glPopName();
        glPopName();
    }
}

void
NodeIndexedFaceSet::setField(int index, FieldValue *value)
{
    _meshDirty = true;
    Node::setField(index, value);
}

MFVec3f *
NodeIndexedFaceSet::getCoordinates() 
{
    Node *ncoord = coord()->getValue();
    if (ncoord == NULL)
        return NULL;
    else
        return ((NodeCoordinate *)ncoord)->point();
}

MFInt32 *
NodeIndexedFaceSet::getCoordIndex()
{
    return coordIndex();
}

MFInt32 *
NodeIndexedFaceSet::getColorIndex()
{
    return colorIndex();
}

MFInt32 *
NodeIndexedFaceSet::getNormalIndex()
{
    return normalIndex();
}

MFVec2f *
NodeIndexedFaceSet::getTextureCoordinates()
{
    Node *ntexCoord = texCoord()->getValue();
    if (ntexCoord == NULL)
        return NULL;
    else
        return ((NodeTextureCoordinate *)ntexCoord)->point();
}

MFInt32 *
NodeIndexedFaceSet::getTexCoordIndex()
{
    return texCoordIndex();
}

void
NodeIndexedFaceSet::createMesh(bool cleanDoubleVertices)
{
    Node *coord = ((SFNode *) getField(coord_Field()))->getValue();
    bool bcolorPerVertex = colorPerVertex()->getValue();
    bool bnormalPerVertex = normalPerVertex()->getValue();
    MFInt32 *colorIndex = getColorIndex();
    MFInt32 *coordIndex = (MFInt32 *)getCoordIndex()->copy();
    MFInt32 *normalIndex = getNormalIndex();
    MFInt32 *texCoordIndex = getTexCoordIndex();
   
    if (!coord || ((NodeCoordinate *) coord)->point()->getType() != MFVEC3F)
        return;

    MFVec3f *coords = ((NodeCoordinate *)coord)->point();
    MFVec3f *normals = NULL;
    MFFloat *colors = NULL;
    MFVec2f *texCoords = NULL;

    if (normal()->getValue())
        if (normal()->getValue()->getType() == VRML_NORMAL)
            normals = ((NodeNormal *)(normal()->getValue()))->vector();
    
    int meshFlags = 0;
    if (color()->getValue()) {
        if (color()->getValue()->getType() == VRML_COLOR) 
            colors = ((NodeColor *)(color()->getValue()))->color();
        else if (color()->getValue()->getType() == X3D_COLOR_RGBA) {
            colors = ((NodeColorRGBA *)(color()->getValue()))->color();
            meshFlags |= MESH_COLOR_RGBA;
        }
    }    

    if (texCoord()->getValue()) 
        if (texCoord()->getValue()->getType() == VRML_TEXTURE_COORDINATE)
            texCoords = ((NodeTextureCoordinate *)(texCoord()->getValue()))
                         ->point();
    
    if (bcolorPerVertex)
        if (colorIndex->getSize() != coordIndex->getSize())
            colorIndex = NULL;
    if (texCoordIndex->getSize() != coordIndex->getSize())
        texCoordIndex = NULL;
    if ((texCoordIndex == NULL) || (texCoord()->getValue() == NULL))
        texCoords = generateTextureCoordinates(coords, coordIndex);
    if (bnormalPerVertex)
        if (normalIndex->getSize() != coordIndex->getSize())
            normalIndex = NULL;
    float transparency = 0;
    if (hasParent())
        transparency = getParent()->getTransparency();
    if (ccw()->getValue())
        meshFlags |= MESH_CCW;
    if (solid()->getValue())
        meshFlags |= MESH_SOLID;
    if (convex()->getValue())
        meshFlags |= MESH_CONVEX;
    if (bcolorPerVertex)
        meshFlags |= MESH_COLOR_PER_VERTEX;
    if (bnormalPerVertex)
        meshFlags |= MESH_NORMAL_PER_VERTEX;

    if (_mesh)
        delete _mesh;
    _mesh = new Mesh(coords, coordIndex, normals, normalIndex, colors, 
                     colorIndex, texCoords, texCoordIndex,
                     creaseAngle()->getValue(), meshFlags, transparency);
}

Node * 
NodeIndexedFaceSet::toIndexedLineSet(void)
{
    if (_mesh == NULL)
        return NULL;
    NodeCoordinate *ncoord = (NodeCoordinate *)_scene->createNode("Coordinate");
    ncoord->point(new MFVec3f((MFVec3f *)_mesh->getVertices()->copy()));
    NodeIndexedLineSet *node = (NodeIndexedLineSet *)
                               _scene->createNode("IndexedLineSet");
    node->coord(new SFNode(ncoord));
    node->coordIndex(new MFInt32((MFInt32 *)_mesh->getCoordIndex()->copy()));
    return node;
}

void
NodeIndexedFaceSet::flip(int index)
{
    NodeCoordinate *ncoord = (NodeCoordinate *)coord()->getValue();
    if (ncoord)
        if (ncoord->getType() == VRML_COORDINATE)
            ncoord->flip(index);
    NodeNormal *nnormal = (NodeNormal *)normal()->getValue();
    if (nnormal)
        if (nnormal->getType() == VRML_NORMAL)
            nnormal->flip(index);
    SFBool *bccw = new SFBool(!(ccw()->getValue()));
    ccw(bccw);
    _meshDirty = true;
}

void
NodeIndexedFaceSet::swap(int fromTo)
{
    NodeCoordinate *ncoord = (NodeCoordinate *)coord()->getValue();
    if (ncoord)
        if (ncoord->getType() == VRML_COORDINATE) 
             ncoord->swap(fromTo);
    NodeNormal *nnormal = (NodeNormal *)normal()->getValue();
    if (nnormal)
        if (nnormal->getType() == VRML_NORMAL)
            nnormal->swap(fromTo);
    SFBool *bccw = new SFBool(!(ccw()->getValue()));
    ccw(bccw);
    _meshDirty = true;
}

void
NodeIndexedFaceSet::setNormalFromMesh(Node *nnormal)
{
    if (nnormal->getType() != VRML_NORMAL)
        return;

    if (meshDirty() || (_mesh == NULL)) {
        createMesh();
        _meshDirty = false;
    }

    if (_mesh == NULL)
        return;

    MFVec3f *v = _mesh->getNormals();
    if (v != NULL) {
        ((NodeNormal *)nnormal)->vector(v);
        MFInt32 *ni = _mesh->getNormalIndex();
        if (ni != NULL)
            normalIndex(ni);
        normalPerVertex(new SFBool(true));
    }
}

void
NodeIndexedFaceSet::setTexCoordFromMesh(Node *ntexCoord)
{
    if (ntexCoord->getType() != VRML_TEXTURE_COORDINATE)
        return;
    if (_mesh == NULL)
        return;
    MFVec2f *v = _mesh->generateTextureCoordinates();
    if (v != NULL) {
        ((NodeTextureCoordinate *)ntexCoord)->point(v);
        MFInt32 *ni = (MFInt32 *)getTexCoordIndex()->copy();
        if (ni != NULL)
            texCoordIndex(ni);
    }
}


int NodeIndexedFaceSet::getProfile(void) const
{ 
// TODO:
// X3D each face is terminated with coordIndex = -1, even the last
// X3D check repeating coordIndex

/* the following criteria are required from each IndexedFaceSet 
   "otherwise the results are undefined" in ISO/IEC 19775:2005 13.3.6

   - X3D check planar polygons
   - X3D check 3 non-coincident vertices
   - X3D check self-intersection polygon
  
   In ISO/IEC 19775:2005 table B3 (interchange profile) this criteria are
   repeated as requirement for the interchange profile, but they are not 
   repeated in ISO/IEC 19775:2005 table E.3 (immersive profile) and
   ISO/IEC 19775:2005 table F.3 (full profile).
   
    Conclusion: IndexedFaceSets in X3D files using the full or immersive
    profile may produce undefined results, while in the interchange
    profile it may not produce undefined results 8-(

   TODO: find out what is going on 8-(
*/
    MFInt32* coords = coordIndex();          
    if (coords) 
        if (coords->getSize() > 0) {
            if (coords->getValue(coords->getSize() - 1) != -1)
                return PROFILE_IMMERSIVE;
            int vertexCounter = 0;
            for (int i = 0; i < coords->getSize(); i++)
                if (coords->getValue(i) == -1) {
                    if (vertexCounter < 3)
                        return PROFILE_IMMERSIVE;
                    else
                        vertexCounter = 0;
                    vertexCounter++;
                }
        }
    if (_mesh != NULL)
        if (!(_mesh->onlyPlanarFaces()))
            return PROFILE_IMMERSIVE;
    if (hasInput("set_colorIndex"))
        return PROFILE_IMMERSIVE;
    if (hasInput("set_normalIndex"))
        return PROFILE_IMMERSIVE;
    if (!isDefault(ccw_Field()))
        return PROFILE_IMMERSIVE;
    if (!isDefault(normal_Field()))
        return PROFILE_IMMERSIVE;
    if (!isDefault(convex_Field()))
        return PROFILE_IMMERSIVE;
    if (!isDefault(normalIndex_Field()))
        return PROFILE_IMMERSIVE;
    float fcreaseAngle = creaseAngle()->getValue();
    if ((fcreaseAngle != 0) && (fcreaseAngle != M_PI))
        return PROFILE_IMMERSIVE;         
    return PROFILE_INTERCHANGE; 
}

