// Copyright (C) 2010, Guy Barrand. All rights reserved.
// See the file tools.license for terms.

#ifndef tools_sg_zb_action
#define tools_sg_zb_action

#include "zb_manager"

#include "render_action"
#include "primitive_visitor"
#include "../glprims"

#include "../zb/buffer"

#include "../colorfs"
#include "../lina/plane"
#include "../mathf"
#include "../lina/vec3d" //ZZ=double

namespace tools {
namespace sg {

class zb_action : public render_action {
  TOOLS_ACTION(zb_action,tools::sg::zb_action,render_action)
private:  
  zb_action& get_me() {return *this;}
public:
  virtual void draw_vertex_array(gl::mode_t a_mode,size_t a_floatn,const float* a_xyzs){
    m_pv.add_primitive(a_mode,a_floatn,a_xyzs);
  }

  virtual void draw_vertex_array_xy(gl::mode_t a_mode,size_t a_floatn,const float* a_xys){
    m_pv.add_primitive_xy(a_mode,a_floatn,a_xys);
  }

  virtual void draw_vertex_color_array(gl::mode_t a_mode,size_t a_floatn,
                                       const float* a_xyzs,const float* a_rgbas){
    m_pv.add_primitive_rgba(a_mode,a_floatn,a_xyzs,a_rgbas);
  }

  virtual void draw_vertex_normal_array(gl::mode_t a_mode,size_t a_floatn,
                                        const float* a_xyzs,const float* a_nms){
    m_pv.add_primitive_normal(a_mode,a_floatn,a_xyzs,a_nms);
  }

  virtual void draw_vertex_color_normal_array(gl::mode_t a_mode,size_t a_floatn,
                                              const float* a_xyzs,const float* a_rgbas,const float* a_nms){
    // We expect a_nms of size : 3*(a_floatn/3)
    // (then one normal per 3D point).
    m_pv.add_primitive_normal_rgba(a_mode,a_floatn,a_xyzs,a_nms,a_rgbas);
  }

  virtual void clear_color(float a_r,float a_g,float a_b,float a_a){
    zb::buffer::ZPixel pix;
    zb::buffer::rgba2pix(a_r,a_g,a_b,a_a,pix);
    m_zb.clear_color_buffer(pix);
  }
  virtual void color4f(float a_r,float a_g,float a_b,float a_a){
    m_rgba.set_value(a_r,a_g,a_b,a_a);
  }
  virtual void line_width(float a_v){m_line_width = a_v;}
  virtual void point_size(float a_v) {m_point_size = a_v;}
  virtual void set_polygon_offset(bool a_v) {m_POLYGON_OFFSET_FILL = a_v;}
  virtual void normal(float a_x,float a_y,float a_z) {
    m_normal.set_value(a_x,a_y,a_z);
  }

  virtual void set_winding(winding_type a_v) {
    m_ccw = (a_v==winding_ccw?true:false);
  }
  virtual void set_shade_model(shade_type) {}

  virtual void set_depth_test(bool a_on) {m_DEPTH_TEST = a_on;}

  virtual void set_cull_face(bool a_on) {m_CULL_FACE = a_on;}
  virtual void set_point_smooth(bool a_on) {m_POINT_SMOOTH = a_on;}
  virtual void set_line_smooth(bool a_on) {m_LINE_SMOOTH = a_on;}

  virtual void load_proj_matrix(const mat4f& a_mtx) {
    m_proj = a_mtx;
  }

  virtual void load_model_matrix(const mat4f& a_mtx) {
    m_model = a_mtx;
    set_normal_matrix();
  }

  virtual unsigned int max_lights() {return 1000;}

  virtual void enable_light(unsigned int,
                            float a_dx,float a_dy,float a_dz,
                            float a_r,float a_g,float a_b,float a_a,
                            float a_ar,float a_ag,float a_ab,float a_aa){
    m_light_color.set_value(a_r,a_g,a_b,a_a);
    m_light_ambient.set_value(a_ar,a_ag,a_ab,a_aa);
    m_light_direction.set_value(a_dx,a_dy,a_dz);
    m_light_direction.normalize();
    m_light_on = true;
  }

  virtual void set_lighting(bool a_value) {m_light_on = a_value;}
  virtual void set_blend(bool a_value) {m_blend = a_value;}

  virtual void restore_state(unsigned int /*a_ret_num_light*/) {
    const sg::state& _state = state();
    m_proj = _state.m_proj;
    m_model = _state.m_model;
    set_normal_matrix();

    m_rgba = _state.m_color;
    m_normal = _state.m_normal;

    m_ccw = (_state.m_winding==winding_ccw?true:false);
    m_POLYGON_OFFSET_FILL = _state.m_GL_POLYGON_OFFSET_FILL;
    m_CULL_FACE = _state.m_GL_CULL_FACE;
    m_POINT_SMOOTH = _state.m_GL_POINT_SMOOTH;
    m_LINE_SMOOTH = _state.m_GL_LINE_SMOOTH;
    m_line_width = _state.m_line_width;
    m_point_size = _state.m_point_size;
    m_light_on = _state.m_GL_LIGHTING;
    m_DEPTH_TEST = _state.m_GL_DEPTH_TEST;
    m_blend = _state.m_GL_BLEND;
  }

  /////////////////////////////////////////////////////////////////
  /// texture /////////////////////////////////////////////////////
  /////////////////////////////////////////////////////////////////
  virtual void draw_vertex_array_texture(gl::mode_t,size_t a_xyzn,const float* a_xyzs,
                                         gstoid a_id,const float* a_tcs) {
    img_byte img;
    if(!m_mgr.find(a_id,img)) return;
    m_pv.add_texture(m_out,a_xyzn,a_xyzs,img,a_tcs);
  }

  virtual void draw_vertex_normal_array_texture(gl::mode_t a_mode,
                                                size_t a_xyzn,const float* a_xyzs,const float* /*a_nms*/,
                                                gstoid a_id,const float* a_tcs) {
    draw_vertex_array_texture(a_mode,a_xyzn,a_xyzs,a_id,a_tcs);
  }

  /////////////////////////////////////////////////////////////////
  /// VBO /////////////////////////////////////////////////////////
  /////////////////////////////////////////////////////////////////
  virtual void begin_gsto(gstoid) {}
  virtual void draw_gsto_v(gl::mode_t,size_t,bufpos){}
  virtual void draw_gsto_vc(gl::mode_t,size_t,bufpos,bufpos) {}
  virtual void draw_gsto_vn(gl::mode_t,size_t,bufpos,bufpos) {}
  virtual void draw_gsto_vcn(gl::mode_t,size_t,bufpos,bufpos,bufpos) {}
  virtual void end_gsto() {}
  /////////////////////////////////////////////////////////////////
  /////////////////////////////////////////////////////////////////
  /////////////////////////////////////////////////////////////////
  virtual sg::render_manager& render_manager() {return m_mgr;}
public:
  zb_action(zb_manager& a_mgr,std::ostream& a_out,unsigned int a_ww,unsigned int a_wh)
  :parent(a_out,a_ww,a_wh)
  ,m_mgr(a_mgr)
  ,m_pv(get_me())
  ,m_light_color(colorf_white())
  ,m_light_ambient(colorf_black())
  ,m_light_direction(vec3f(0,0,-1))
  ,m_normal(0,0,1)

  ,m_ccw(true)
  ,m_POLYGON_OFFSET_FILL(false)
  ,m_CULL_FACE(true)
  ,m_POINT_SMOOTH(false)
  ,m_LINE_SMOOTH(false)
  ,m_line_width(1)
  ,m_point_size(1)
  ,m_light_on(false)
  ,m_DEPTH_TEST(true)
  ,m_blend(false)
  {
    m_vp_mtx.set_identity();
    m_vp_mtx.mul_translate(float(m_ww)/2,float(m_wh)/2,0);
    m_vp_mtx.mul_scale(float(m_ww)/2,float(m_wh)/2,1);

    m_zb.change_size(a_ww,a_wh);

    m_proj.set_identity();
    m_model.set_identity();
    m_normal_matrix.set_identity();
  }
  virtual ~zb_action(){}
protected:
  zb_action(const zb_action& a_from)
  :parent(a_from)
  ,m_mgr(a_from.m_mgr)
  ,m_vp_mtx(a_from.m_vp_mtx)
  ,m_pv(a_from.m_pv)
  ,m_light_color(a_from.m_light_color)
  ,m_light_ambient(a_from.m_light_ambient)
  ,m_light_direction(a_from.m_light_direction)
  ,m_normal(a_from.m_normal)

  ,m_proj(a_from.m_proj)
  ,m_model(a_from.m_model)
  ,m_normal_matrix(a_from.m_normal_matrix)
  ,m_rgba(a_from.m_rgba)
  ,m_ccw(a_from.m_ccw)
  ,m_POLYGON_OFFSET_FILL(a_from.m_POLYGON_OFFSET_FILL)
  ,m_CULL_FACE(a_from.m_CULL_FACE)
  ,m_POINT_SMOOTH(a_from.m_POINT_SMOOTH)
  ,m_LINE_SMOOTH(a_from.m_LINE_SMOOTH)
  ,m_line_width(a_from.m_line_width)
  ,m_point_size(a_from.m_point_size)
  ,m_light_on(a_from.m_light_on)
  ,m_DEPTH_TEST(a_from.m_DEPTH_TEST)
  ,m_blend(a_from.m_blend)
  {}
  zb_action& operator=(const zb_action& a_from){
    parent::operator=(a_from);
    m_vp_mtx = a_from.m_vp_mtx;
    m_pv = a_from.m_pv;
    m_light_color = a_from.m_light_color;
    m_light_ambient = a_from.m_light_ambient;
    m_light_direction = a_from.m_light_direction;
    m_normal = a_from.m_normal;

    m_proj = a_from.m_proj;
    m_model = a_from.m_model;
    m_normal_matrix = a_from.m_normal_matrix;
    m_rgba = a_from.m_rgba;
    m_ccw = a_from.m_ccw;
    m_POLYGON_OFFSET_FILL = a_from.m_POLYGON_OFFSET_FILL;
    m_CULL_FACE = a_from.m_CULL_FACE;
    m_POINT_SMOOTH = a_from.m_POINT_SMOOTH;
    m_LINE_SMOOTH = a_from.m_LINE_SMOOTH;
    m_line_width = a_from.m_line_width;
    m_point_size = a_from.m_point_size;
    m_light_on = a_from.m_light_on;
    m_DEPTH_TEST = a_from.m_DEPTH_TEST;
    m_blend = a_from.m_blend;
    return *this;
  }
public:
  void clear_color_buffer(float a_r,float a_g,float a_b,float a_a){
    clear_color(a_r,a_g,a_b,a_a);
  }
  void clear_color_buffer(const colorf& a_color){
    clear_color(a_color.r(),a_color.g(),a_color.b(),a_color.a());
  }
  
  void clear_depth_buffer() {m_zb.clear_depth_buffer();}
protected:
  typedef unsigned char uchar;
protected:
  static void color2pix(const colorf& a_rgba,zb::buffer::ZPixel& a_pix) {
    zb::buffer::rgba2pix(a_rgba.r(),a_rgba.g(),a_rgba.b(),a_rgba.a(),a_pix);
  }
public:
  zb::buffer::ZPixel* get_color_buffer(unsigned int& a_width,unsigned int& a_height) const {return m_zb.get_color_buffer(a_width,a_height);}

  bool get_rgbs(bool a_top_to_bottom,std::vector<uchar>& a_buffer) {
    a_buffer.clear();
    if(!m_ww || !m_wh) return false;
    size_t sz = 3 * m_ww * m_wh;
    a_buffer.resize(sz);
    uchar* pos = a_buffer.data();
    zb::buffer::ZPixel pix;
    uchar* _pix = 0;
    for(unsigned int row=0;row<m_wh;row++) {
      for(unsigned int col=0;col<m_ww;col++) {
        if(!m_zb.get_clipped_pixel(col,a_top_to_bottom?row:m_wh-1-row,pix)){
          m_out << "tools::sg::zb_action::get_rgbs : can't get zbuffer pixel" << std::endl;
          *pos = 0xFF;pos++;
          *pos = 0x00;pos++;
          *pos = 0x00;pos++;
        } else {
          _pix = (uchar*)&pix;
          *pos = *_pix;_pix++;pos++;
          *pos = *_pix;_pix++;pos++;
          *pos = *_pix;_pix++;pos++;
        }
      }
    }
    return true;
  }

  bool get_rgbas(bool a_top_to_bottom,std::vector<uchar>& a_buffer) {
    a_buffer.clear();
    if(!m_ww || !m_wh) return false;
    size_t sz = 4 * m_ww * m_wh;
    a_buffer.resize(sz);
    if(a_top_to_bottom) {
      ::memcpy(a_buffer.data(),m_zb.zimage(),sz);
      return true;
    }
    size_t stride = m_ww*4;
    uchar* zpos = ((uchar*)m_zb.zimage())+sz-stride;
    uchar* pos = a_buffer.data();
    for(unsigned int row=0;row<m_wh;row++,pos+=stride,zpos-=stride) ::memcpy(pos,zpos,stride);
    return true;
  }
  bool get_rgbas_cocoa(unsigned int a_factor,std::vector<uchar>& a_buffer) {
    a_buffer.clear();
    if(!m_ww || !m_wh) return false;
    if(!a_factor) return false;
    size_t bpp = 4;
    a_buffer.resize(a_factor* m_ww * a_factor * m_wh * bpp);
    uchar* zbuffer = (uchar*)m_zb.zimage();
    uchar* abuffer = a_buffer.data();
    size_t zstride = m_ww*bpp;
    size_t astride = a_factor*m_ww*bpp;
    size_t i,j,ar,ac,ipix;
    uchar* zpos;uchar* apos;
    for(j=0;j<m_wh;j++) {
      for(i=0;i<m_ww;i++) {
        //position in the original image.
        zpos = zbuffer + (m_wh-1-j) * zstride + i*bpp;  //top_to_bottom.

        for(ar=0;ar<a_factor;ar++) {
          for(ac=0;ac<a_factor;ac++) {
            //position in the new image.
            apos = abuffer + (j*a_factor+ar) * astride + (i*a_factor+ac)*bpp;
            for(ipix=0;ipix<bpp;ipix++) {
              *(apos+ipix) = *(zpos+ipix);
            }
          }
        }

      }
    }

    return true;
  }

  bool get_bgras(bool a_top_to_bottom,std::vector<uchar>& a_buffer) {
    a_buffer.clear();
    if(!m_ww || !m_wh) return false;
    size_t sz = 4 * m_ww * m_wh;
    a_buffer.resize(sz);
    if(a_top_to_bottom) {
      uchar* pos = a_buffer.data();
      uchar* zpos = ((uchar*)m_zb.zimage());
      for(size_t count=0;count<sz;count+=4,pos+=4,zpos+=4) {
        *(pos+0) = *(zpos+2);
        *(pos+1) = *(zpos+1);
        *(pos+2) = *(zpos+0);
        *(pos+3) = *(zpos+3);
      }
    } else {
      size_t stride = m_ww*4;
      uchar* pos_line = a_buffer.data();
      uchar* zpos_line = ((uchar*)m_zb.zimage())+sz-stride;
      uchar* pos;
      uchar* zpos;
      size_t count;
      for(unsigned int row=0;row<m_wh;row++,pos_line+=stride,zpos_line-=stride) {
        pos = pos_line;
        zpos = zpos_line;
        for(count=0;count<m_ww;count++,pos+=4,zpos+=4) {
          *(pos+0) = *(zpos+2);
          *(pos+1) = *(zpos+1);
          *(pos+2) = *(zpos+0);
          *(pos+3) = *(zpos+3);
        }
      }
    }
    return true;
  }
public:
  static bool get_rgb(void* a_tag,unsigned int a_col,unsigned int a_row,float& a_r,float& a_g,float& a_b){
    //used with wps.
    zb_action* rzb = (zb_action*)a_tag;
    zb::buffer::ZPixel pix;
    if(!rzb->m_zb.get_clipped_pixel(a_col,rzb->wh()-1-a_row,pix)){
      rzb->out() << "tools::sg;:zb_action::get_rgb: can't get zbuffer pixel" << std::endl;
      a_r = 1;
      a_g = 0;
      a_b = 0;
      return false;
    }
    float a;
    zb::buffer::pix2rgba(pix,a_r,a_g,a_b,a);
    return true;
  }
protected:
  void set_normal_matrix() {
    mat4f tmp(m_model);
    tmp.no_translate();
    if(!tmp.invert(m_normal_matrix)) {
      m_out << "tools::sg::zb_action::set_normal_matrix : can't invert model matrix." << std::endl;
    }
    m_normal_matrix.transpose();
  }
  
  bool project_point(float& a_x,float& a_y,float& a_z,float& a_w) {
    a_w = 1;
    m_model.mul_4f_opt(a_x,a_y,a_z,a_w,m_tmp);
    m_proj.mul_4f_opt(a_x,a_y,a_z,a_w,m_tmp);
    if(a_w==0) return false;
    a_x /= a_w;
    a_y /= a_w;
    a_z /= a_w;
    return true;
  }

  class primvis : public primitive_visitor {
  protected:
    virtual bool project(float& a_x,float& a_y,float& a_z,float& a_w) {
      return m_this.project_point(a_x,a_y,a_z,a_w);
    }
    virtual bool add_point(float a_x,float a_y,float a_z,float) {
      return _add_point(a_x,a_y,a_z,m_this.m_rgba.r(),m_this.m_rgba.g(),m_this.m_rgba.b(),m_this.m_rgba.a());
    }

    virtual bool add_point(float a_x,float a_y,float a_z,float,
                           float a_r,float a_g,float a_b,float a_a) {
      return _add_point(a_x,a_y,a_z,a_r,a_g,a_b,a_a);
    }

    virtual bool add_line(float a_bx,float a_by,float a_bz,float,
                          float a_ex,float a_ey,float a_ez,float) {
      m_this.m_vp_mtx.mul_3f_opt(a_bx,a_by,a_bz,m_tmp);
      m_this.m_vp_mtx.mul_3f_opt(a_ex,a_ey,a_ez,m_tmp);
      a_bz *= -1;
      a_ez *= -1;

      zb::point beg;
      zinit(beg,a_bx,a_by,a_bz);

      zb::point end;
      zinit(end,a_ex,a_ey,a_ez);

      m_this.m_zb.set_depth_test(m_this.m_DEPTH_TEST);
      m_this.m_zb.set_blend(m_this.m_blend);

      zb::buffer::ZPixel pix;
      color2pix(m_this.m_rgba,pix);
      m_this.m_zb.draw_line(beg,end,pix,npix(m_this.m_line_width));

      return true;
    }

    virtual bool add_line(float a_bx,float a_by,float a_bz,float,
                          float a_br,float a_bg,float a_bb,float a_ba,
                          float a_ex,float a_ey,float a_ez,float,
                          float,float,float,float) {
      m_this.m_vp_mtx.mul_3f_opt(a_bx,a_by,a_bz,m_tmp);
      m_this.m_vp_mtx.mul_3f_opt(a_ex,a_ey,a_ez,m_tmp);
      a_bz *= -1;
      a_ez *= -1;

      zb::point beg;
      zinit(beg,a_bx,a_by,a_bz);

      zb::point end;
      zinit(end,a_ex,a_ey,a_ez);

      m_this.m_zb.set_depth_test(m_this.m_DEPTH_TEST);
      m_this.m_zb.set_blend(m_this.m_blend);

      // interpolate color with beg,end ?
      zb::buffer::ZPixel pix;
      zb::buffer::rgba2pix(a_br,a_bg,a_bb,a_ba,pix);
      m_this.m_zb.draw_line(beg,end,pix,npix(m_this.m_line_width));

      return true;
    }

    virtual bool add_triangle(float a_p1x,float a_p1y,float a_p1z,float a_p1w,
                              float a_p2x,float a_p2y,float a_p2z,float a_p2w,
                              float a_p3x,float a_p3y,float a_p3z,float a_p3w){
      return _add_triangle(a_p1x,a_p1y,a_p1z,a_p1w,
                           m_this.m_normal.x(),
                           m_this.m_normal.y(),
                           m_this.m_normal.z(),
                           a_p2x,a_p2y,a_p2z,a_p2w,
                           m_this.m_normal.x(),
                           m_this.m_normal.y(),
                           m_this.m_normal.z(),
                           a_p3x,a_p3y,a_p3z,a_p3w,
                           m_this.m_normal.x(),
                           m_this.m_normal.y(),
                           m_this.m_normal.z(),
                           m_this.m_rgba);
    }

    virtual bool add_triangle(
      float a_p1x,float a_p1y,float a_p1z,float a_p1w,
      float a_r1,float a_g1,float a_b1,float a_a1,
      float a_p2x,float a_p2y,float a_p2z,float a_p2w,
      float a_r2,float a_g2,float a_b2,float a_a2,
      float a_p3x,float a_p3y,float a_p3z,float a_p3w,
      float a_r3,float a_g3,float a_b3,float a_a3){

      float r = (a_r1+a_r2+a_r3)/3.0f;
      float g = (a_g1+a_g2+a_g3)/3.0f;
      float b = (a_b1+a_b2+a_b3)/3.0f;
      float a = (a_a1+a_a2+a_a3)/3.0f;
      colorf col(r,g,b,a);

      return _add_triangle(a_p1x,a_p1y,a_p1z,a_p1w,
                           m_this.m_normal.x(),
                           m_this.m_normal.y(),
                           m_this.m_normal.z(),
                           a_p2x,a_p2y,a_p2z,a_p2w,
                           m_this.m_normal.x(),
                           m_this.m_normal.y(),
                           m_this.m_normal.z(),
                           a_p3x,a_p3y,a_p3z,a_p3w,
                           m_this.m_normal.x(),
                           m_this.m_normal.y(),
                           m_this.m_normal.z(),
                           col);
    }

    virtual bool project_normal(float&,float&,float&) {
      return true;
    }
    virtual bool add_point_normal(float a_x,float a_y,float a_z,float a_w,
                                  float /*a_nx*/,float /*a_ny*/,float /*a_nz*/) {
      add_point(a_x,a_y,a_z,a_w);
      return true;
    }
    virtual bool add_point_normal(float a_x,float a_y,float a_z,float a_w,
                                  float /*a_nx*/,float /*a_ny*/,float /*a_nz*/,
                                  float a_r,float a_g,float a_b,float a_a) {
      add_point(a_x,a_y,a_z,a_w,a_r,a_g,a_b,a_a);
      return true;
    }
    virtual bool add_line_normal(float a_bx,float a_by,float a_bz,float a_bw,
                                 float /*a_bnx*/,float /*a_bny*/,float /*a_bnz*/,
                                 float a_ex,float a_ey,float a_ez,float a_ew,
                                 float /*a_enx*/,float /*a_eny*/,float /*a_enz*/) {
      add_line(a_bx,a_by,a_bz,a_bw, a_ex,a_ey,a_ez,a_ew);
      return true;
    }
    virtual bool add_line_normal(float a_bx,float a_by,float a_bz,float a_bw,
                                 float /*a_bnx*/,float /*a_bny*/,float /*a_bnz*/,
                                 float a_br,float a_bg,float a_bb,float a_ba,
                                 float a_ex,float a_ey,float a_ez,float a_ew,
                                 float /*a_enx*/,float /*a_eny*/,float /*a_enz*/,
                                 float a_er,float a_eg,float a_eb,float a_ea){
      add_line(a_bx,a_by,a_bz,a_bw, a_br,a_bg,a_bb,a_ba, a_ex,a_ey,a_ez,a_ew, a_er,a_eg,a_eb,a_ea);
      return true;
    }
    virtual bool add_triangle_normal(
      float a_p1x,float a_p1y,float a_p1z,float a_p1w,
      float a_n1x,float a_n1y,float a_n1z,
      float a_p2x,float a_p2y,float a_p2z,float a_p2w,
      float a_n2x,float a_n2y,float a_n2z,
      float a_p3x,float a_p3y,float a_p3z,float a_p3w,
      float a_n3x,float a_n3y,float a_n3z) {
      
      return _add_triangle(a_p1x,a_p1y,a_p1z,a_p1w,
                           a_n1x,a_n1y,a_n1z,
                           a_p2x,a_p2y,a_p2z,a_p2w,
                           a_n2x,a_n2y,a_n2z,
                           a_p3x,a_p3y,a_p3z,a_p3w,
                           a_n3x,a_n3y,a_n3z,
			   m_this.m_rgba);
      return true;
    }
    virtual bool add_triangle_normal(
      float a_p1x,float a_p1y,float a_p1z,float a_p1w,
      float a_n1x,float a_n1y,float a_n1z,
      float a_r1,float a_g1,float a_b1,float a_a1,
      float a_p2x,float a_p2y,float a_p2z,float a_p2w,
      float a_n2x,float a_n2y,float a_n2z,
      float a_r2,float a_g2,float a_b2,float a_a2,
      float a_p3x,float a_p3y,float a_p3z,float a_p3w,
      float a_n3x,float a_n3y,float a_n3z,
      float a_r3,float a_g3,float a_b3,float a_a3){
      
      float r = (a_r1+a_r2+a_r3)/3.0f;
      float g = (a_g1+a_g2+a_g3)/3.0f;
      float b = (a_b1+a_b2+a_b3)/3.0f;
      float a = (a_a1+a_a2+a_a3)/3.0f;
      colorf col(r,g,b,a);

      return _add_triangle(a_p1x,a_p1y,a_p1z,a_p1w,
                           a_n1x,a_n1y,a_n1z,
                           a_p2x,a_p2y,a_p2z,a_p2w,
                           a_n2x,a_n2y,a_n2z,
                           a_p3x,a_p3y,a_p3z,a_p3w,
                           a_n3x,a_n3y,a_n3z,
			   col);
      return true;
    }
  public:
    primvis(zb_action& a_zb):m_this(a_zb){}
    virtual ~primvis(){}
  public:
    primvis(const primvis& a_from)
    :primitive_visitor(a_from)
    ,m_this(a_from.m_this)
    {}
    primvis& operator=(const primvis& a_from){
      primitive_visitor::operator=(a_from);
      return *this;
    }
  protected:
    static void zinit(zb::point& a_p,float a_x,float a_y,float a_z) {
      a_p.x = fround(a_x); //float -> int
      a_p.y = fround(a_y); //float -> int
      a_p.z = (zb::ZZ)a_z; //float -> double
    }

    unsigned int npix(float a_size) {
      // 0 -> 0
      // 1 -> 0
      // 2 -> 1    3x3
      // 3 -> 1    3x3
      // 4 -> 2    5x5
      // 5 -> 2    5x5
      // 6 -> 3    7x7
      unsigned int num = (unsigned int)a_size;
      unsigned int num_2 = num/2;
      if(2*num_2==num) {num++;num_2 = num/2;}
      return num_2;
    }

    bool _add_point(float a_x,float a_y,float a_z,float a_r,float a_g,float a_b,float a_a){
      m_this.m_zb.set_depth_test(m_this.m_DEPTH_TEST);
      m_this.m_zb.set_blend(m_this.m_blend);

      m_this.m_vp_mtx.mul_3f_opt(a_x,a_y,a_z,m_tmp);
      a_z *= -1;

      zb::point p;
      zinit(p,a_x,a_y,a_z);

      zb::buffer::ZPixel pix;
      zb::buffer::rgba2pix(a_r,a_g,a_b,a_a,pix);
      m_this.m_zb.draw_point(p,pix,npix(m_this.m_point_size));

      return true;
    }

    bool _add_triangle(float a_p1x,float a_p1y,float a_p1z,float /*a_p1w*/,
                       float a_n1x,float a_n1y,float a_n1z,
                       float a_p2x,float a_p2y,float a_p2z,float /*a_p2w*/,
                       float a_n2x,float a_n2y,float a_n2z,
                       float a_p3x,float a_p3y,float a_p3z,float /*a_p3w*/,
                       float a_n3x,float a_n3y,float a_n3z,
                       const colorf& a_color){

      float p1x = a_p1x;float p1y = a_p1y;float p1z = a_p1z;//float p1w = a_p1w;
      float p2x = a_p2x;float p2y = a_p2y;float p2z = a_p2z;//float p2w = a_p2w;
      float p3x = a_p3x;float p3y = a_p3y;float p3z = a_p3z;//float p3w = a_p3w;

      m_this.m_vp_mtx.mul_3f_opt(p1x,p1y,p1z,m_tmp);
      m_this.m_vp_mtx.mul_3f_opt(p2x,p2y,p2z,m_tmp);
      m_this.m_vp_mtx.mul_3f_opt(p3x,p3y,p3z,m_tmp);
      p1z *= -1;
      p2z *= -1;
      p3z *= -1;

      if(m_this.m_POLYGON_OFFSET_FILL){
        //note : gopaw pawex9,14,15,21 with "lego" (drawing cubes) are sensitive to the below epsil.
        // zs are in [-1,1]
        float epsil = 1e-4f;
        p1z -= epsil;
        p2z -= epsil;
        p3z -= epsil;
      }

      typedef zb::ZZ ZZ; //double

      plane<vec3d> pn(
        vec3<ZZ>(p1x,p1y,p1z),
        vec3<ZZ>(p2x,p2y,p2z),
        vec3<ZZ>(p3x,p3y,p3z)
      );
      if(!pn.is_valid()) return true;

      ZZ C = pn.normal()[2];

      if(m_this.m_CULL_FACE){
        if(m_this.m_ccw) {
          if(C<=0) return true;
        } else {
          if(C>=0) return true;
        }
      }

      ZZ A = pn.normal()[0];
      ZZ B = pn.normal()[1];
      ZZ D = -pn.distance_from_origin();

      zb::point list[3];
      zinit(list[0],p1x,p1y,p1z);
      zinit(list[1],p2x,p2y,p2z);
      zinit(list[2],p3x,p3y,p3z);

      m_this.m_zb.set_depth_test(m_this.m_DEPTH_TEST);
      m_this.m_zb.set_blend(m_this.m_blend);

      colorf frag_color = a_color;

      if(m_this.m_light_on) {  // same logic as toolx/wasm/webgl.js:

        float nx = (a_n1x+a_n2x+a_n3x)/3.0f;
        float ny = (a_n1y+a_n2y+a_n3y)/3.0f;
        float nz = (a_n1z+a_n2z+a_n3z)/3.0f;

        m_this.m_normal_matrix.mul_dir_3f_opt(nx,ny,nz,m_tmp);

        vec3f _normal(nx,ny,nz);_normal.normalize();

        float _dot = _normal.dot(m_this.m_light_direction);

        if(_dot<0.0f) {
          _dot *= -1.0f;

          colorf _tmp = m_this.m_light_color;
          _tmp *= _dot;
          _tmp += m_this.m_light_ambient;

          frag_color *= _tmp;

        } else {
          frag_color *= m_this.m_light_ambient;
	}

        frag_color.clamp();
        frag_color.set_a(a_color.a());

      }

      zb::buffer::ZPixel pix;
      color2pix(frag_color,pix);
      m_this.m_zb.draw_polygon(3,list,A,B,C,D,pix);

      return true;
    }
  protected:
    zb_action& m_this;
  private:  //optimize:
    float m_tmp[3];
  };

protected:
  zb_manager& m_mgr;
  mat4f m_vp_mtx;
  zb::buffer m_zb;
  primvis m_pv;
  colorf m_light_color;
  colorf m_light_ambient;
  vec3f m_light_direction;
  vec3f m_normal;

  // to be restored in restore_state() :
  mat4f m_proj;
  mat4f m_model;
  mat4f m_normal_matrix;
  colorf m_rgba;
  bool m_ccw;
  bool m_POLYGON_OFFSET_FILL;
  bool m_CULL_FACE;
  bool m_POINT_SMOOTH;
  bool m_LINE_SMOOTH;
  float m_line_width;
  float m_point_size;
  bool m_light_on;
  bool m_DEPTH_TEST;
  bool m_blend;
private:  //optimize:
  float m_tmp[4];
};

}}

#endif
