/*****
 * drawpath3.h
 *
 * Stores a path3 that has been added to a picture.
 *****/

#ifndef DRAWPATH3_H
#define DRAWPATH3_H

#include "drawelement.h"
#include "path3.h"
#include "beziercurve.h"

namespace camp {

class drawPath3 : public drawElement {
protected:
  const path3 g;
  triple center;
  bool straight;
  prc::RGBAColour diffuse;
  prc::RGBAColour emissive;
  prc::RGBAColour specular;
  double opacity;
  double shininess;
  double metallic;
  double fresnel0;
  bool invisible;
  Interaction interaction;
  triple Min,Max;
  bool billboard;
  size_t centerIndex;
public:
#ifdef HAVE_GL
  BezierCurve R;
#endif
  void init() {
    billboard=interaction == BILLBOARD;
    centerIndex=0;
  }

  drawPath3(path3 g, triple center, const vm::array& p, double opacity,
            double shininess, double metallic, double fresnel0,
            Interaction interaction, const string& key="") :
    drawElement(key), g(g), center(center),
    straight(g.piecewisestraight()), opacity(opacity),
    shininess(shininess), metallic(metallic), fresnel0(fresnel0),
    interaction(interaction), Min(g.min()), Max(g.max()) {
    init();

    pen Pen=vm::read<camp::pen>(p,0);
    invisible=Pen.invisible();
    diffuse=rgba(Pen);
    emissive=rgba(vm::read<camp::pen>(p,1));
    specular=rgba(vm::read<camp::pen>(p,2));
  }

  drawPath3(const double* t, const drawPath3 *s) :
    drawElement(s->KEY), g(camp::transformed(t,s->g)), straight(s->straight),
    diffuse(s->diffuse), emissive(s->emissive), specular(s->specular),
    opacity(s->opacity), shininess(s->shininess),
    metallic(s->metallic), fresnel0(s->fresnel0),
    invisible(s->invisible), interaction(s->interaction),
    Min(g.min()), Max(g.max()) {
    init();
    center=t*s->center;
  }

  virtual ~drawPath3() {}

  bool is3D() {return true;}

  void bounds(const double* t, bbox3& B) {
    if(t != NULL) {
      const path3 tg(camp::transformed(t,g));
      B.add(tg.min());
      B.add(tg.max());
    } else {
      B.add(Min);
      B.add(Max);
    }
  }

  void ratio(const double* t, pair &b, double (*m)(double, double), double,
             bool &first) {
    pair z;
    if(t != NULL) {
      const path3 tg(camp::transformed(t,g));
      z=tg.ratio(m);
    } else z=g.ratio(m);

    if(first) {
      b=z;
      first=false;
    } else b=pair(m(b.getx(),z.getx()),m(b.gety(),z.gety()));
  }

  void meshinit() {
    if(billboard)
      centerIndex=centerindex(center);
  }

  bool write(prcfile *out, unsigned int *, double, groupsmap&);
  bool write(abs3Doutfile *out);

  void render(double, const triple&, const triple&, double,
              bool remesh);

  drawElement *transformed(const double* t);
};

class drawNurbsPath3 : public drawElement {
protected:
  size_t degree;
  size_t n;
  triple *controls;
  double *weights;
  double *knots;
  prc::RGBAColour color;
  bool invisible;
  triple Min,Max;

#ifdef HAVE_LIBGLM
  GLfloat *Controls;
  GLfloat *Knots;
#endif

public:
  drawNurbsPath3(const vm::array& g, const vm::array* knot,
                 const vm::array* weight, const pen& p, const string& key="") :
    drawElement(key), color(rgba(p)), invisible(p.invisible()) {
    size_t weightsize=checkArray(weight);

    string wrongsize="Inconsistent NURBS data";
    n=checkArray(&g);

    if(n == 0 || (weightsize != 0 && weightsize != n))
      reportError(wrongsize);

    controls=new(UseGC) triple[n];

    size_t k=0;
    for(size_t i=0; i < n; ++i)
      controls[k++]=vm::read<triple>(g,i);

    if(weightsize > 0) {
      size_t k=0;
      weights=new(UseGC) double[n];
      for(size_t i=0; i < n; ++i)
        weights[k++]=vm::read<double>(weight,i);
    } else weights=NULL;

    size_t nknots=checkArray(knot);

    if(nknots <= n+1 || nknots > 2*n)
      reportError(wrongsize);

    degree=nknots-n-1;

    run::copyArrayC(knots,knot,0,NoGC);

#ifdef HAVE_LIBGLM
    Controls=NULL;
#endif
  }

  drawNurbsPath3(const double* t, const drawNurbsPath3 *s) :
    drawElement(s->KEY), degree(s->degree), n(s->n), weights(s->weights),
    knots(s->knots), color(s->color), invisible(s->invisible) {
    controls=new(UseGC) triple[n];
    for(unsigned int i=0; i < n; ++i)
      controls[i]=t*s->controls[i];

#ifdef HAVE_LIBGLM
    Controls=NULL;
#endif
  }

  bool is3D() {return true;}

  void bounds(const double* t, bbox3& b);

  virtual ~drawNurbsPath3() {}

  bool write(prcfile *out, unsigned int *, double, groupsmap&);

  void displacement();
  void ratio(const double* t, pair &b, double (*m)(double, double), double fuzz,
             bool &first);

  void render(double size2, const triple& Min, const triple& Max,
              double perspective, bool remesh);

  drawElement *transformed(const double* t);
};

// Draw a pixel.
class drawPixel : public drawElement {
  triple v;
  pen p;
  prc::RGBAColour color;
  double width;
  bool invisible;
  triple Min,Max;
public:
#ifdef HAVE_GL
  Pixel R;
#endif
  drawPixel(const triple& v, const pen& p, double width, const string& key="")
    : drawElement(key), v(v), p(p), color(rgba(p)), width(width),
      invisible(p.invisible()) {}

  void bounds(const double* t, bbox3& B) {
    Min=Max=(t != NULL) ? t*v : v;
    B.add(Min);
  }

  void ratio(const double* t, pair &b, double (*m)(double, double), double,
             bool &first) {
    triple V=(t != NULL) ? t*v : v;
    pair z=pair(xratio(V),yratio(V));

    if(first) {
      b=z;
      first=false;
    } else b=pair(m(b.getx(),z.getx()),m(b.gety(),z.gety()));
  }

  void render(double size2, const triple& b, const triple& B,
              double perspective, bool remesh);

  bool write(prcfile *out, unsigned int *, double, groupsmap&);
  bool write(abs3Doutfile *out);

  drawElement *transformed(const double* t);
};

}

#endif
