/*
 * NodeCoordinate.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 "stdafx.h"

#include "NodeCoordinate.h"
#include "Proto.h"
#include "MFVec3f.h"
#include "ExposedField.h"
#include "Field.h"
#include "RenderState.h"
#include "DuneApp.h"
#include "Util.h"
#include "Vec3f.h"
#include "Scene.h"
#include "resource.h"
#include "NodeIndexedFaceSet.h"
#include "NodeIndexedLineSet.h"
#include "NodePointSet.h"
#include "NodeLineSet.h"
#include "NodeTriangleSet.h"
#include "NodeTriangleFanSet.h"
#include "NodeTriangleStripSet.h"
#include "NodeIndexedTriangleSet.h"
#include "NodeIndexedTriangleFanSet.h"
#include "NodeIndexedTriangleStripSet.h"
#include "NodeQuadSet.h"
#include "NodeIndexedQuadSet.h"
#include "NodeNurbsGroup.h"

ProtoCoordinate::ProtoCoordinate(Scene *scene)
  : Proto(scene, "Coordinate")
{
    point.set(
          addExposedField(MFVEC3F, "point", new MFVec3f()));
    setFieldFlags(point, EIF_RECOMMENDED);
}

Node *
ProtoCoordinate::create(Scene *scene)
{ 
    return new NodeCoordinate(scene, this); 
}

NodeCoordinate::NodeCoordinate(Scene *scene, Proto *def)
  : Node(scene, def)
{
}

void
NodeCoordinate::setField(int index, FieldValue *value)
{
    Node::setField(index, value);
    if (hasParent())
        getParent()->update();
}

void
NodeCoordinate::drawHandles()
{
    RenderState state;

    glPushAttrib(GL_LIGHTING);
    glDisable(GL_LIGHTING);
    glPushName(0);
    state.startDrawHandles();
    for (int ci = 0; ci < point()->getSFSize(); ci++) {
        state.setHandleColor(_scene, ci);
        glLoadName(ci);
        state.drawHandle(Vec3f(point()->getValue(ci)));
    }
    state.endDrawHandles();
    glPopName();
    glPopAttrib();
}

Vec3f
NodeCoordinate::getHandle(int handle, int *constraint, int *field)
{
    *field = point_Field() ;

    if (handle >= 0 && handle < point()->getSFSize()) {
        Vec3f ret((Vec3f)point()->getValue(handle));
        TheApp->PrintMessageWindowsVertex(IDS_VERTEX_SELECTED, "point", handle);
        return ret;
    } else {
        return Vec3f(0.0f, 0.0f, 0.0f);
    }
}

void
NodeCoordinate::setHandle(int handle, const Vec3f &v)
{
    MFVec3f *newValue = new MFVec3f(*point());

    float epsilon = TheApp->GetHandleEpsilon();
    int numPoints = point()->getSFSize();
    if (handle >= 0 && handle < numPoints) {
        Vec3f oldV = point()->getValue(handle);
        Vec3f newV = v;
        if (_scene->getXSymetricMode() && (fabs(oldV.x) < epsilon))
            newValue->setValue(handle * 3, 0);
        else
            newValue->setValue(handle * 3, newV.x);
        newValue->setValue(handle * 3 + 1, newV.y);
        newValue->setValue(handle * 3 + 2, newV.z);
        // set other handles for symetric modelling
        // which also snap handles at the same place
        setHandle(newValue, handle, newV, oldV, true);
        if (_scene->getXSymetricMode()) {
            NodeNurbsGroup *nurbsGroup = findNurbsGroup();
            if (nurbsGroup)
               nurbsGroup->setHandle(this, 1, newV, oldV);
        }
    }
}

void
NodeCoordinate::setHandle(MFVec3f *value, int handle,
                          const Vec3f &newV, const Vec3f &oldV,
                          bool already_changed)
{
    bool        changed = false;
    MFVec3f *newValue = (MFVec3f *) value->copy();

    if (_scene->getXSymetricMode()) {
        float epsilon = TheApp->GetHandleEpsilon();
        int numPoints = newValue->getSize() / 3;
        for (int i = 0; i < numPoints; i++) {
            if (i != handle) {
                Vec3f vPoint = point()->getValue(i);
                if (   (fabs(vPoint.z - oldV.z) < epsilon) 
                    && (fabs(vPoint.y - oldV.y) < epsilon)) {
                    if (fabs(vPoint.x + oldV.x) < epsilon) {
                        changed = true;
                        if (fabs(oldV.x) < epsilon)
                            newValue->setValue(i * 3, 0);
                        else
                            newValue->setValue(i * 3, - newV.x);
                        newValue->setValue(i * 3 + 1,   newV.y);
                        newValue->setValue(i * 3 + 2,   newV.z);
                    } else if (fabs(vPoint.x - oldV.x) < epsilon) {
                        changed = true;
                        if (fabs(oldV.x) < epsilon)
                            newValue->setValue(i * 3, 0);
                        else
                            newValue->setValue(i * 3, newV.x);
                        newValue->setValue(i * 3 + 1, newV.y);
                        newValue->setValue(i * 3 + 2, newV.z);
                    }         
                }                 
            }
        }
    }
    if (already_changed)
        changed = true;

    if (changed) {
        _scene->setField(this, point_Field(), newValue);
    }
}

void 
NodeCoordinate::draw(Node *node)
{
    switch (node->getType()) {
      case VRML_INDEXED_FACE_SET:
        ((NodeIndexedFaceSet *)node)->meshDraw();
        break;
      case VRML_INDEXED_LINE_SET:
        ((NodeIndexedLineSet *)node)->lineDraw();
        break;
      case VRML_POINT_SET:
        ((NodePointSet *)node)->pointDraw();
        break;
      case X3D_LINE_SET:
        ((NodeLineSet *)node)->lineDraw();
        break;
      case X3D_TRIANGLE_SET:
        ((NodeTriangleSet *)node)->meshDraw();
        break;
      case X3D_TRIANGLE_FAN_SET:
        ((NodeTriangleFanSet *)node)->meshDraw();
        break;
      case X3D_TRIANGLE_STRIP_SET:
        ((NodeTriangleStripSet *)node)->meshDraw();
        break;
      case X3D_INDEXED_TRIANGLE_SET:
        ((NodeIndexedTriangleSet *)node)->meshDraw();
        break;
      case X3D_INDEXED_TRIANGLE_FAN_SET:
        ((NodeIndexedTriangleFanSet *)node)->meshDraw();
        break;
      case X3D_INDEXED_TRIANGLE_STRIP_SET:
        ((NodeIndexedTriangleStripSet *)node)->meshDraw();
        break;
      case X3D_QUAD_SET:
        ((NodeQuadSet *)node)->meshDraw();
        break;
      case X3D_INDEXED_QUAD_SET:
        ((NodeIndexedQuadSet *)node)->meshDraw();
        break;
    }
}

void
NodeCoordinate::flatten(int direction, bool zero)
{
    MFVec3f *newValue = (MFVec3f *) point()->copy();

    if (newValue->getSFSize() == 0)
        return;

    float value = 0;

    if (!zero) {
        for (int i = 0; i < newValue->getSFSize(); i++)
            value += newValue->getValue(i)[direction] / newValue->getSFSize();
    }
    for (int i = 0; i < newValue->getSFSize(); i++) {
        SFVec3f vec(newValue->getValue(i));
        vec.setValue(direction, value);
        newValue->setSFValue(i, &vec);
    } 
    _scene->setField(this, point_Field(), newValue);

    if (hasParent())
        getParent()->update();
}

void
NodeCoordinate::flip(int index)
{
    MFVec3f *coords = point();
    if (coords != NULL)
        coords->flip(index);
    if (hasParent())
        getParent()->update();
}

void
NodeCoordinate::update()
{
    if (hasParent())
        getParent()->update();
}

void
NodeCoordinate::swap(int fromTo)
{
    MFVec3f *coords = point();
    if (coords != NULL)
        coords->swap(fromTo);
    if (hasParent())
        getParent()->update();
}

NodeNurbsGroup *
NodeCoordinate::findNurbsGroup()
{
    // find NurbsSet/NurbsGroup in 
    if (hasParent()) {
        Node* parent = getParent();
        if (parent->getType() == VRML_NURBS_SURFACE) {
            if (parent->hasParent()) {
                Node* grandParent = parent->getParent();
                if (grandParent->getType() == VRML_NURBS_GROUP) {
                    return (NodeNurbsGroup *) grandParent;
                } else if (grandParent->getType() == VRML_SHAPE)
                    if (grandParent->hasParent())
                        if (grandParent->getParent()->getType() == 
                            VRML_NURBS_GROUP)
                            return (NodeNurbsGroup *) grandParent->getParent();
            }
        } 
    }
    return NULL;
}

bool    
NodeCoordinate::hasTwoSides(void)
{
    if (hasParent())
        return getParent()->hasTwoSides();
    return false;
}

bool
NodeCoordinate::isDoubleSided(void)
{
    if (hasParent())
        return getParent()->isDoubleSided();
    return false;
}

void
NodeCoordinate::toggleDoubleSided(void)
{
    if (hasParent())
        getParent()->toggleDoubleSided();
}

void
NodeCoordinate::flipSide(void)
{
    if (hasParent())
        getParent()->flipSide();
}

bool    
NodeCoordinate::validHandle(int handle) 
{ 
    if ((handle < 0) || (handle >= point()->getSFSize()))
        return false;
    if (!_scene->getXSymetricMode())
        return true;
    return !_scene->checkSameOrXSymetricHandle(handle, point());
}

