/*
 * NodeBox.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 "NodeBox.h"
#include "Scene.h"
#include "Proto.h"
#include "FieldValue.h"
#include "FieldCommand.h"
#include "SFVec3f.h"
#include "SFFloat.h"
#include "RenderState.h"
#include "Node.h"
#include "Util.h"
#include "MFNode.h"
#include "SFNode.h"
#include "SFInt32.h"
//#include "MFFloat.h"
#include "MFVec3f.h"
#include "NodeShape.h"
#include "NodeAppearance.h"
#include "NodeMaterial.h"
#include "NodeIndexedFaceSet.h"
#include "NodeCoordinate.h"
#include "NodeNormal.h"
#include "NodeNurbsGroup.h"
#include "NodeNurbsSurface.h"
#include "Util.h"

ProtoBox::ProtoBox(Scene *scene)
  : Proto(scene, "Box")
{
    size.set(
          addField(SFVEC3F, "size", new SFVec3f(2.0f, 2.0f, 2.0f), 
                   new SFFloat(0.0f)));

    solid.set(
          addField(SFBOOL, "solid", new SFBool(true)));
    setFieldFlags(solid, FF_X3D_ONLY);
    
    texCoord.set(
          addExposedField(SFNODE, "texCoord", new SFNode(NULL), 
                          GENERATED_TEXTURE_COORDINATE_NODE));
    setFieldFlags(solid, FF_KAMBI_ONLY);
}

Node *
ProtoBox::create(Scene *scene)
{ 
    return new NodeBox(scene, this); 
}

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

void
NodeBox::drawHandles()
{
    const float *fsize = size()->getValue();
    RenderState state;

    glPushMatrix();
    glScalef(fsize[0] * 0.5f, fsize[1] * 0.5f, fsize[2] * 0.5f);
    glPushAttrib(GL_LIGHTING);
    glDisable(GL_LIGHTING);
    glPushName(size_Field());
    state.startDrawHandles();
    for (int i = 0; i < 8; i++) {
        state.setHandleColor(_scene, i);
        glLoadName(i);
        state.drawHandle(boxCorners[i]);
    }
    state.endDrawHandles();
    glPopName();
    glPopAttrib();
    glPopMatrix();
}

Vec3f
NodeBox::getHandle(int handle, int * /* constraint */, int *field)
{
    const float *fsize = size()->getValue();
    switch (handle) {
      case TRF: case TLF: case TRB: case TLB:
      case BRF: case BLF: case BRB: case BLB:
        *field = size_Field();
        return Vec3f(fsize) * Vec3f(boxCorners[handle]) * 0.5f;
      default:
        assert(0);
        return Vec3f(0.0f, 0.0f, 0.0f);
    }
}

void
NodeBox::setHandle(int handle, const Vec3f &v)
{
    switch (handle) {
      case TRF: case TLF: case TRB: case TLB:
      case BRF: case BLF: case BRB: case BLB:
        _scene->setField(this, size_Field(), 
                         new SFVec3f(v * 2.0f * Vec3f(boxCorners[handle])));
        break;
    }
}

Node*
NodeBox::toNurbs(int nuAreaPoints, int uDegree, int nvAreaPoints, 
                 int vDegree, int nzAreaPoints)
{
  //build a box with NURBS from 1 surface
  const float *fsize = size()->getValue();
  NodeNurbsSurface *node = (NodeNurbsSurface *)
                           _scene->createNode("NurbsSurface");
  int i,j;
  int index1 = 0;
  int index2;
  int tmpindex;
  float xSize = fsize[0];
  float ySize = fsize[1];
  float zSize = fsize[2];    
  float dz = zSize / (nvAreaPoints - 1);
  float tmpxSize;
  float tmpySize;
  float zPosition;
  int uOrder = uDegree + 1;
  int vOrder = vDegree + 1;  
  int uDimension = 4 * nuAreaPoints + 4 * uDegree - 7;
  int vDimension = nvAreaPoints + 2 * (nzAreaPoints-1) + 2*vDegree - 2;
  float *uKnots = new float[uDimension + uOrder];
  float *vKnots = new float[vDimension + vOrder];
  float *controlPoints = new float[uDimension * vDimension * 3];
  float *weights = new float[uDimension * vDimension];   
  
  for (i=0; i<uDimension * vDimension * 3; i++) {
    controlPoints[i]=0.0f;
  }

  //surface orthogonal z-axis
  for (j=0; j<nzAreaPoints-1; j++) {
    zPosition = zSize / 2;
    tmpxSize = j * xSize / (nzAreaPoints-1);
    tmpySize = j * ySize / (nzAreaPoints-1);
    makeNurbsRectangle(tmpxSize, tmpySize, zPosition, nuAreaPoints, uDegree);
    for (i=0; i<_rectangle.size(); i++) {
      controlPoints[(j*uDimension + i)*3    ] = _rectangle[i].x;  
      controlPoints[(j*uDimension + i)*3 + 1] = _rectangle[i].y;
      controlPoints[(j*uDimension + i)*3 + 2] = _rectangle[i].z;  
    }
    index1++;
  }

  //make sharp edge
  tmpindex = 0;
  for (j=index1; j<index1+vDegree-1; j++) {
    makeNurbsRectangle(xSize, ySize, zPosition, nuAreaPoints, uDegree);
    for (i=0; i<_rectangle.size(); i++) {
      controlPoints[(j*uDimension + i)*3    ] = _rectangle[i].x;  
      controlPoints[(j*uDimension + i)*3 + 1] = _rectangle[i].y;
      controlPoints[(j*uDimension + i)*3 + 2] = _rectangle[i].z;  
    }
    tmpindex++;
  }            
  index1 = index1 + tmpindex;

  //surfaces in direction of z-axis
  tmpindex = 0;
  for (j=index1, index2=0; j<index1+nvAreaPoints; j++, index2++) {  
    zPosition = zSize / 2 - index2 * dz;
    makeNurbsRectangle(xSize, ySize, zPosition, nuAreaPoints, uDegree);
    for (i=0; i<_rectangle.size(); i++) {
      controlPoints[(j*uDimension + i)*3    ] = _rectangle[i].x;  
      controlPoints[(j*uDimension + i)*3 + 1] = _rectangle[i].y;
      controlPoints[(j*uDimension + i)*3 + 2] = _rectangle[i].z;  
    }
    tmpindex++;
  }
  index1 = index1 + tmpindex;

  //make sharp edge
  tmpindex = 0;
  for (j=index1; j<index1+vDegree-1; j++) {
    zPosition = -zSize / 2;
    makeNurbsRectangle(xSize, ySize, zPosition, nuAreaPoints, uDegree);
    for (i=0; i<_rectangle.size(); i++) {
      controlPoints[(j*uDimension + i)*3    ] = _rectangle[i].x;  
      controlPoints[(j*uDimension + i)*3 + 1] = _rectangle[i].y;
      controlPoints[(j*uDimension + i)*3 + 2] = _rectangle[i].z;  
    }
    tmpindex++;
  }              
  index1 = index1 + tmpindex;
  
  //surface orthogonal z-axis
  index2 = 0;
  tmpindex = 0;
  for (j=index1, index2=1; j<index1+nzAreaPoints-1; j++, index2++) {
    tmpxSize = xSize - index2 * xSize / (nzAreaPoints-1);
    tmpySize = ySize - index2 * ySize / (nzAreaPoints-1);
    makeNurbsRectangle(tmpxSize, tmpySize, zPosition, nuAreaPoints, uDegree);
    for (i=0; i<_rectangle.size(); i++) {
      controlPoints[(j*uDimension + i)*3    ] = _rectangle[i].x;  
      controlPoints[(j*uDimension + i)*3 + 1] = _rectangle[i].y;
      controlPoints[(j*uDimension + i)*3 + 2] = _rectangle[i].z;  
    }
    tmpindex++;
  }              
  index1 = index1 + tmpindex;

  makeKnotvectors(uOrder, uDimension, vOrder, vDimension);

  for (i=0; i<_uKnots.size(); i++) {
    uKnots[i] = _uKnots[i];
  }
  for (i=0; i<_vKnots.size(); i++) {
    vKnots[i] = _vKnots[i];
  }  
  
  for (i=0; i<uDimension*vDimension; i++) {
    weights[i] = 1.0f;
  }

  node->setField(node->uDimension_Field(), new SFInt32(uDimension));
  node->setField(node->vDimension_Field(), new SFInt32(vDimension));
  node->uKnot(new MFFloat(uKnots, uDimension + uOrder));
  node->vKnot(new MFFloat(vKnots, vDimension + vOrder));
  node->setField(node->uOrder_Field(), new SFInt32(uOrder));
  node->setField(node->vOrder_Field(), new SFInt32(vOrder));
  node->setField(node->uClosed_Field(), new SFBool(true));
  node->setField(node->vClosed_Field(), new SFBool(true));
  node->createControlPoints(new MFVec3f(controlPoints, 
                                        uDimension * vDimension * 3));
  node->weight(new MFFloat(weights, uDimension * vDimension));
  return node; 
}


Node*
NodeBox::toNurbs(int nuAreaPoints, int uDegree, int nvAreaPoints, int vDegree)
{
  //build a box with NURBS from 6 seperate NURBS-surfaces
  
  Node *node = _scene->createNode("NurbsGroup");
  
  FieldValue *box = new MFNode();

  _nuAreaPoints = nuAreaPoints;
  _nvAreaPoints = nvAreaPoints;
  
  _uOrder = uDegree + 1;
  _vOrder = vDegree + 1;
  _uDimension = nuAreaPoints;
  _vDimension = nvAreaPoints;
  
  int i;

  for (i=1; i<=6; i++) {
    Node *surface = makeNurbsSurfaces(i);
    box = box->addNode(surface);
  } 
  
  ((NodeNurbsGroup *)node)->children((MFNode *)box);
  
  return node;
}



Node*
NodeBox::makeNurbsSurfaces(int surface)
{
  int i,j;
  
  NodeNurbsSurface *node = (NodeNurbsSurface *)
                            _scene->createNode("NurbsSurface");
  const float *fsize = size()->getValue();
  
  float xSize = fsize[0];
  float ySize = fsize[1];
  float zSize = fsize[2];  

  float xPosition = xSize / 2;
  float yPosition = ySize / 2;
  float zPosition = zSize / 2;
  float dx = (float) xSize / (_nuAreaPoints-1);
  float dy = (float) ySize / (_nuAreaPoints-1);
  float dz = (float) zSize / (_nvAreaPoints-1);
  int uOrder = _uOrder;
  int vOrder = _vOrder;
  int uDimension = _uDimension;
  int vDimension = _vDimension;
  float *uKnots;
  float *vKnots;
  float *controlPoints;
  float *weights;

  switch (surface) {
  case 1:
    uKnots = new float[uDimension + uOrder];
    vKnots = new float[vDimension + vOrder];
    controlPoints = new float[uDimension * vDimension * 3];
    weights = new float[uDimension * vDimension];      
    makeKnotvectors(uOrder, uDimension, vOrder, vDimension);
    for (j = 0; j < vDimension; j++) {
      for (i = 0; i < uDimension; i++) {
        controlPoints[(j*uDimension + i)*3    ] = xPosition;
        controlPoints[(j*uDimension + i)*3 + 1] = (float) +ySize / 2 - dy * i;
        controlPoints[(j*uDimension + i)*3 + 2] = (float) -zSize /2 + dz * (vDimension - 1 - j);
        weights[j*uDimension + i] = 1.0f;
      }
    }    
    break;
  case 2:
    uKnots = new float[uDimension + uOrder];
    vKnots = new float[vDimension + vOrder];
    controlPoints = new float[uDimension * vDimension * 3];
    weights = new float[uDimension * vDimension];      
    makeKnotvectors(uOrder, uDimension, vOrder, vDimension);
    for (j = 0; j < vDimension; j++) {
      for (i = 0; i < uDimension; i++) {
        controlPoints[(j*uDimension + i)*3    ] = -xPosition;
        controlPoints[(j*uDimension + i)*3 + 1] = (float) -ySize / 2 + dy * i;
        controlPoints[(j*uDimension + i)*3 + 2] = (float) -zSize /2 + dz * (vDimension - 1 - j);
        weights[j*uDimension + i] = 1.0f;
      }
    }
    
    break;
  case 3:
    uKnots = new float[uDimension + uOrder];
    vKnots = new float[vDimension + vOrder];
    controlPoints = new float[uDimension * vDimension * 3];
    weights = new float[uDimension * vDimension];      
    makeKnotvectors(uOrder, uDimension, vOrder, vDimension);
    for (j = 0; j < vDimension; j++) {
      for (i = 0; i < uDimension; i++) {
        controlPoints[(j*uDimension + i)*3    ] = (float) -xSize / 2 + dx * i;
        controlPoints[(j*uDimension + i)*3 + 1] = yPosition;
        controlPoints[(j*uDimension + i)*3 + 2] = (float) -zSize /2 + dz * (vDimension - 1 - j);
        weights[j*uDimension + i] = 1.0f;
      }
    }

    break;
  case 4:    
    uKnots = new float[uDimension + uOrder];
    vKnots = new float[vDimension + vOrder];
    controlPoints = new float[uDimension * vDimension * 3];
    weights = new float[uDimension * vDimension];      
    makeKnotvectors(uOrder, uDimension, vOrder, vDimension); 
    for (j = 0; j < vDimension; j++) {
      for (i = 0; i < uDimension; i++) {
        controlPoints[(j*uDimension + i)*3    ] = (float) xSize / 2 - dx * i;
        controlPoints[(j*uDimension + i)*3 + 1] = -yPosition;
        controlPoints[(j*uDimension + i)*3 + 2] = (float) -zSize /2 + dz * (vDimension - 1 - j);
        weights[j*uDimension + i] = 1.0f;
      }
    }

    break;
  case 5:
    vOrder = _uOrder;
    vDimension = _uDimension;
    uKnots = new float[uDimension + uOrder];
    vKnots = new float[vDimension + vOrder];
    controlPoints = new float[uDimension * vDimension * 3];
    weights = new float[uDimension * vDimension];      
    makeKnotvectors(uOrder, uDimension, vOrder, vDimension);
    for (j = 0; j < vDimension; j++) {
      for (i = 0; i < uDimension; i++) {
        controlPoints[(j*uDimension + i)*3    ] = (float) xSize / 2 - dx * i;
        controlPoints[(j*uDimension + i)*3 + 1] = (float) -ySize /2 + dy * (vDimension - 1 - j);
        controlPoints[(j*uDimension + i)*3 + 2] = zPosition;
        weights[j*uDimension + i] = 1.0f;
      }
    }

    break;
  case 6:
    vOrder= _uOrder;
    vDimension = _uDimension;
    uKnots = new float[uDimension + uOrder];
    vKnots = new float[vDimension + vOrder];
    controlPoints = new float[uDimension * vDimension * 3];
    weights = new float[uDimension * vDimension];      
    makeKnotvectors(uOrder, uDimension, vOrder, vDimension);
    for (j = 0; j < vDimension; j++) {
      for (i = 0; i < uDimension; i++) {
        controlPoints[(j*uDimension + i)*3    ] = (float) -xSize / 2 + dx * i;
        controlPoints[(j*uDimension + i)*3 + 1] = (float) -ySize /2 + dy * (vDimension - 1 - j);
        controlPoints[(j*uDimension + i)*3 + 2] = -zPosition;
        weights[j*uDimension + i] = 1.0f;
      }
    }

    break;
  }

  for (i=0; i<_uKnots.size(); i++) {
    uKnots[i] = _uKnots[i];
  }
  for (i=0; i<_vKnots.size(); i++) {
    vKnots[i] = _vKnots[i];
  }

  node->setField(node->uDimension_Field(), new SFInt32(uDimension));
  node->setField(node->vDimension_Field(), new SFInt32(vDimension));
  node->uKnot(new MFFloat(uKnots, uDimension + uOrder));
  node->vKnot(new MFFloat(vKnots, vDimension + vOrder));
  node->setField(node->uOrder_Field(), new SFInt32(uOrder));
  node->setField(node->vOrder_Field(), new SFInt32(vOrder));
  node->createControlPoints(new MFVec3f(controlPoints, 
                                        uDimension * vDimension * 3));
  node->weight(new MFFloat(weights, uDimension * vDimension));

  return node;
}


void
NodeBox::makeKnotvectors(int uOrder, int uDimension, int vOrder, int vDimension)
{
  int i;
  
  if (_uKnots.size() > 0) {
    _uKnots.remove(0, _uKnots.size()-1);
  }
  if (_vKnots.size() > 0) {
    _vKnots.remove(0, _vKnots.size()-1);
  }

  for (i = 0; i < uOrder; i++) {
    _uKnots[i] = 0.0f;
    _uKnots[uDimension + i] = (float) (uDimension - uOrder + 1);
  }
  for (i = 0; i < uDimension - uOrder; i++) {
    _uKnots[uOrder + i] = (float) (i + 1);
  }

  for (i = 0; i < vOrder; i++) {
    _vKnots[i] = 0.0f;
    _vKnots[vDimension + i] = (float) (vDimension - vOrder + 1);
  }
  for (i = 0; i < vDimension - vOrder; i++) {
    _vKnots[vOrder + i] = (float) (i + 1);
  }
}


void
NodeBox::makeNurbsRectangle(float xSize, float ySize, float zPosition, 
                            int nuAreaPoints, int uDegree)
{
  int i;
  int index1;
  float dx = xSize / (nuAreaPoints - 1);
  float dy = ySize / (nuAreaPoints - 1);
  
  index1 = 0;

  for (i=0; i<uDegree-1; i++) {
    _rectangle[index1].x = 0;           
    _rectangle[index1].y = ySize / 2;           
    _rectangle[index1].z = zPosition;           
    index1++;
  }
  for (i=0; i<uDegree-1; i++) {
    _rectangle[index1].x = xSize / 2;           
    _rectangle[index1].y = ySize / 2;           
    _rectangle[index1].z = zPosition;           
    index1++;
  }
  for (i=0; i<nuAreaPoints; i++) {
    _rectangle[index1].x = xSize / 2;  
    _rectangle[index1].y = ySize / 2 - i * dy;           
    _rectangle[index1].z = zPosition;           
    index1++;
  }
  for (i=0; i<uDegree-1; i++) {
    _rectangle[index1].x = xSize / 2;          
    _rectangle[index1].y = - ySize / 2;           
    _rectangle[index1].z = zPosition;           
    index1++;
  }
  for (i=1; i<nuAreaPoints; i++) {
    _rectangle[index1].x = xSize / 2 - i * dx;          
    _rectangle[index1].y = - ySize / 2;            
    _rectangle[index1].z = zPosition;
    index1++;
  }    
  for (i=0; i<uDegree-1; i++) {
    _rectangle[index1].x = - xSize / 2;          
    _rectangle[index1].y = - ySize / 2;          
    _rectangle[index1].z = zPosition;
    index1++;
    }    
  for (i=1; i<nuAreaPoints; i++) {
    _rectangle[index1].x = - xSize / 2;              
    _rectangle[index1].y = - ySize / 2 + i * dy;         
    _rectangle[index1].z = zPosition;
    index1++;
  }    
  for (i=0; i<uDegree-1; i++) {
    _rectangle[index1].x = - xSize / 2;          
    _rectangle[index1].y = ySize / 2;         
    _rectangle[index1].z = zPosition;
    index1++;
  }    
  for (i=1; i<nuAreaPoints/2; i++) {
    _rectangle[index1].x = - xSize / 2 + 2 * i * dx;          
    _rectangle[index1].y = ySize / 2;             
    _rectangle[index1].z = zPosition;
    index1++;
  }
  for (i=0; i<uDegree-1; i++) {
    _rectangle[index1].x = 0;           
    _rectangle[index1].y = ySize / 2;           
    _rectangle[index1].z = zPosition;           
    index1++;
  }
        
}

void
NodeBox::swap(int fromTo)
{
    size()->swap(fromTo);
    update();
}

void
NodeBox::createMesh(bool cleanDoubleVertices)
{
    int i;
    const float *fsize = size()->getValue();
    float fpoint[] = {
        -fsize[0]/2.0f,  fsize[1]/2.0f,  fsize[2]/2.0f,
        -fsize[0]/2.0f, -fsize[1]/2.0f,  fsize[2]/2.0f,
         fsize[0]/2.0f,  fsize[1]/2.0f,  fsize[2]/2.0f,
         fsize[0]/2.0f, -fsize[1]/2.0f,  fsize[2]/2.0f,
         fsize[0]/2.0f,  fsize[1]/2.0f, -fsize[2]/2.0f,
         fsize[0]/2.0f, -fsize[1]/2.0f, -fsize[2]/2.0f,
        -fsize[0]/2.0f,  fsize[1]/2.0f, -fsize[2]/2.0f,
        -fsize[0]/2.0f, -fsize[1]/2.0f, -fsize[2]/2.0f 
    };
    int icoordIndex[] = {
         0, 1, 3, 2, -1, 
         4, 5, 7, 6, -1, 
         6, 0, 2, 4, -1, 
         1, 7, 5, 3, -1, 
         6, 7, 0, -1,
         7, 1, 0, -1, 
         2, 5, 4, -1,
         2, 3, 5, -1
    }; 
    float ftexCoord[] = {
        0, 1,
        0, 0,
        1, 1,
        1, 0
    };
    int itexCoordIndex[] = {
        0, 1, 3, 2, -1, 
        0, 1, 3, 2, -1, 
        0, 1, 3, 2, -1, 
        0, 1, 3, 2, -1, 
        0, 1, 2, -1,
        1, 3, 2, -1, 
        0, 3, 2, -1,
        0, 1, 3, -1
    };

    MFVec3f *coords = new MFVec3f();
    for (i = 0; i < sizeof(fpoint)/sizeof(float); i += 3)
        coords->appendSFValue(fpoint[i], fpoint[i + 1], fpoint[i + 2]); 

    MFInt32 *coordIndex = new MFInt32();
    for (i = 0; i < sizeof(icoordIndex)/sizeof(int); i++)
        coordIndex->appendSFValue(icoordIndex[i]);

    MFVec2f *texCoords = new MFVec2f();
    for (i = 0; i < sizeof(ftexCoord)/sizeof(float); i += 2)
        texCoords->appendSFValue(ftexCoord[i], ftexCoord[i + 1]);

    MFInt32 *texCoordIndex = new MFInt32();
    for (i = 0; i < sizeof(itexCoordIndex)/sizeof(int); i++)
        texCoordIndex->appendSFValue(itexCoordIndex[i]);

    MFVec3f *normals = NULL;
    MFInt32 *normalIndex = NULL;
    MFColor *colors = NULL;
    MFInt32 *colorIndex = NULL;

    int meshFlags = MESH_NORMAL_PER_VERTEX | MESH_CCW;
    if (_scene->isX3d()) {
        if (solid()->getValue())
            meshFlags |= MESH_SOLID;
    } else
        meshFlags |= MESH_SOLID;

    float transparency = 0;
    if (hasParent())
        transparency = getParent()->getTransparency();

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

bool    
NodeBox::validHandle(int handle) 
{ 
    switch(handle) {
      case TRF: case TLF: case TRB: case TLB:
      case BRF: case BLF: case BRB: case BLB:
        return _scene->getSelectedHandlesSize() <= 1; 
    }
    return false;
}

