# Soya 3D
# Copyright (C) 2004 Jean-Baptiste LAMY -- jiba@tuxfamily.org
#
# 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; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA


cdef class _CellShadingShape(_SimpleShape):
  #cdef _Material _shader
  #cdef float     _outline_color[4]
  #cdef float     _outline_width, _outline_attenuation
  
  cdef __getcstate__(self):
    cdef Chunk* chunk
    chunk = get_chunk()
    chunk_add_float_endian_safe (chunk, self._outline_width)
    chunk_add_float_endian_safe (chunk, self._outline_attenuation)
    chunk_add_floats_endian_safe(chunk, self._outline_color, 4)
    return _SimpleShape.__getcstate__(self), drop_chunk_to_string(chunk), self._shader
    
  cdef void __setcstate__(self, cstate):
    _SimpleShape.__setcstate_data__(self, cstate[0])
    
    cdef Chunk* chunk
    chunk = string_to_chunk(cstate[1])
    chunk_get_float_endian_safe (chunk, &self._outline_width)
    chunk_get_float_endian_safe (chunk, &self._outline_attenuation)
    chunk_get_floats_endian_safe(chunk,  self._outline_color, 4)
    drop_chunk(chunk)
    self._shader = cstate[2]
    
    # Build the display list data, but don't create the corresponding OpenGL display list
    self._build_display_list()
    
  cdef void _build_cellshading(self, _Material shader, outline_color, float outline_width, float outline_attenuation):
    cdef int i
    self._shader              = shader
    self._outline_width       = outline_width
    self._outline_attenuation = outline_attenuation
    for i from 0 <= i < 4: self._outline_color[i] = outline_color[i]
    
  cdef void _batch(self, CoordSyst coordsyst):
    if quality == QUALITY_LOW:
      _SimpleShape._batch(self, coordsyst)
      return
    
    
    cdef int      i
    cdef Frustum* frustum
    frustum = renderer._frustum(coordsyst)
    if (self._option & SHAPE_HAS_SPHERE) and (sphere_in_frustum(frustum, self._sphere) == 0): return
    
    if self._display_lists.nb_opaque_list != 0: renderer._batch(renderer.opaque, self, coordsyst, -1)
    if self._display_lists.nb_alpha_list  != 0: renderer._batch(renderer.alpha , self, coordsyst, -1)
    
#    # batch each face
#    for i from 0 <= i < self._nb_faces: self._batch_face(self._faces + i)
#    pack_batch_end(self, coordsyst)

    # For outline
    if self._outline_width > 0.0: renderer._batch(renderer.secondpass, self, coordsyst, 0)

        
#   cdef void _render(self, CoordSyst coordsyst):
#     cdef int      i, start, end
#     cdef Frustum* frustum
#     cdef Chunk*   chunk
#     cdef float*   shades
    
#     if renderer.state == RENDERER_STATE_SECONDPASS:
#       frustum = renderer._frustum(coordsyst)
#       self._render_outline(frustum)
#     else:
#       shape_option_activate(self._option)
      
#       chunk = get_chunk()
#       chunk_register(chunk, self._nb_vnormals * sizeof(float))
#       shades = <float*> chunk.content
#       self._prepare_cellshading(coordsyst, shades)
      
#       self._pack_render_cellshading(shades)
#       drop_chunk(chunk)
#       shape_option_inactivate(self._option)
      
      
#   cdef void _pack_render_cellshading(self, float* shades):
#     cdef Pack*      pack
#     cdef ShapeFace* face
#     cdef _Material  material

#     # Activate shader texture
#     glActiveTexture(GL_TEXTURE1)
#     if self._shader._id == 0: self._shader._init_texture()
#     glEnable       (GL_TEXTURE_2D)
#     glTexEnvi      (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE)
#     glBindTexture  (GL_TEXTURE_2D, self._shader._id)
#     glActiveTexture(GL_TEXTURE0)
    
#     pack = <Pack*> chunk_get_ptr(renderer.data)
#     while pack:
#       material = <_Material> (pack.material_id)
#       material._activate()
      
#       face_option_activate(pack.option)
      
#       face = <ShapeFace*> chunk_get_ptr(renderer.data)
#       if   pack.option & FACE_TRIANGLE:
#         glBegin(GL_TRIANGLES)
#         while face:
#           self._render_triangle_cellshading(face, shades)
#           face = <ShapeFace*> chunk_get_ptr(renderer.data)
          
#       elif pack.option & FACE_QUAD:
#         glBegin(GL_QUADS)
#         while face:
#           self._render_quad_cellshading(face, shades)
#           face = <ShapeFace*> chunk_get_ptr(renderer.data)
          
#       glEnd()
      
#       face_option_inactivate(pack.option)
#       pack = <Pack*> chunk_get_ptr(renderer.data)
    
#     # Unactivate shader texture
#     glActiveTexture(GL_TEXTURE1)
#     glDisable(GL_TEXTURE_2D)
#     glActiveTexture(GL_TEXTURE0)
    
    
  cdef void _render(self, CoordSyst coordsyst):
    if quality == QUALITY_LOW:
      _SimpleShape._render(self, coordsyst)
      return
    
    cdef int          i, start, end
    cdef int*         face_id
    cdef Frustum*     frustum
    cdef Chunk*       chunk
    cdef float*       shades
    cdef _Material    material
    cdef DisplayList* display_list
    
    if renderer.state == RENDERER_STATE_SECONDPASS:
      frustum = renderer._frustum(coordsyst)
      self._render_outline(frustum)
    else:
      shape_option_activate(self._option)
      
      chunk = get_chunk()
      chunk_register(chunk, self._nb_vnormals * sizeof(float))
      shades = <float*> chunk.content
      self._prepare_cellshading(coordsyst, shades)
      
      if renderer.state == RENDERER_STATE_OPAQUE:
        start = 0
        end   = self._display_lists.nb_opaque_list
      else: # Alpha
        start = self._display_lists.nb_opaque_list
        end   = start + self._display_lists.nb_alpha_list
        
      # Activate shader texture
      glActiveTexture(GL_TEXTURE1)
      if self._shader._id == 0: self._shader._init_texture()
      glEnable       (GL_TEXTURE_2D)
      glTexEnvi      (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL)
      glBindTexture  (GL_TEXTURE_2D, self._shader._id)
      glActiveTexture(GL_TEXTURE0)
      
      for i from start <= i < end:
        display_list = self._display_lists.display_lists + i
        
        material = <_Material> (display_list.material_id)
        material._activate()
        
        face_option_activate(display_list.option)
        
        face_id = display_list.faces_id
        if   display_list.option & FACE_TRIANGLE:
          glBegin(GL_TRIANGLES)
          while face_id[0] != -1:
            self._render_triangle_cellshading(self._faces + face_id[0], shades)
            face_id = face_id + 1
            
        elif display_list.option & FACE_QUAD:
          glBegin(GL_QUADS)
          while face_id[0] != -1:
            self._render_quad_cellshading(self._faces + face_id[0], shades)
            face_id = face_id + 1
            
        glEnd()
        
        face_option_inactivate(display_list.option)
      
      # Unactivate shader texture
      glActiveTexture(GL_TEXTURE1)
      glDisable      (GL_TEXTURE_2D)
      glActiveTexture(GL_TEXTURE0)
        
        
      drop_chunk(chunk)
      shape_option_inactivate(self._option)
      
      
  cdef void _render_outline(self, Frustum* frustum):
    cdef int        i, j, k, ns, nb
    cdef float      d
    cdef float*     plane
    cdef ShapeFace* face, neighbor_face
    
    # Compute outline width, which depends on distance to camera
    d = sphere_distance_point(self._sphere, frustum.position) * self._outline_attenuation
    if d < 1.0: d = self._outline_width
    else:
      d = self._outline_width / d
      if d < 2.0: d = 2.0
      
    _DEFAULT_MATERIAL._activate()
    glLineWidth(d)
    glColor4fv (self._outline_color)
    glDisable  (GL_LIGHTING)
    glDepthFunc(GL_LEQUAL)
    
    # mark faces as either front or back
    for i from 0 <= i < self._nb_faces:
      face = self._faces + i
      plane = self._values + face.normal
      if plane[0] * frustum.position[0] + plane[1] * frustum.position[1] + plane[2] * frustum.position[2] + plane[3] > 0.0:
        face.option = (face.option & ~FACE_BACK ) | FACE_FRONT
      else:
        face.option = (face.option & ~FACE_FRONT) | FACE_BACK
        
        
#     # find and draw edges   
#     glBegin(GL_LINES)
#     for i from 0 <= i < self._nb_faces:
#       face = self._faces + i
#       if face.option & FACE_FRONT:
#         # test if neighbors are back
#         for j from 0 <= j < 4:
#           k = self._neighbors[4 * i + j]
#           if k != -1:
#             neighbor_face = self._faces[k]
#             if neighbor_face.option & FACE_BACK:
#               # draw edge between vertices k and k + 1
#               #print "an edge", (face.option & FACE_QUAD), 
#               #print "  ", i, j, k
#               #print "  ", face.v[j]
#               #print "  ", self._vertex_coords[face.v[j]]
#               glVertex3fv(self._coords + self._vertex_coords[face.v[j]])
#               #print "  ..."
              
#               if ((face.option & FACE_QUAD) and (j < 3)) or (j < 2):
#                 glVertex3fv(self._coords + self._vertex_coords[face.v[j + 1]])
#               else:
#                 glVertex3fv(self._coords + self._vertex_coords[face.v[    0]])
                
#     glEnd()


#     cdef Chunk* chunk
#     chunk = get_chunk()
#     chunk_register(chunk, self._nb_coords * sizeof(int))
#     cdef int* vertex2next
#     vertex2next = <int*> (chunk.content)
#     for i from 0 <= i < self._nb_coords: vertex2next[i] = -1

#     # find edges   
#     #print "draw edges", self._nb_coords
#     for i from 0 <= i < self._nb_faces:
#       face = self._faces + i
#       if face.option & FACE_FRONT:
#         # test if neighbors are back
#         for j from 0 <= j < 4:
#           k = self._neighbors[4 * i + j]
#           if k != -1:
#             neighbor_face = self._faces[k]
#             if neighbor_face.option & FACE_BACK:
#               # draw edge between vertices k and k + 1
#               if ((face.option & FACE_QUAD) and (j < 3)) or (j < 2):
#                 vertex2next[self._vertex_coords[face.v[j]] / 3] = self._vertex_coords[face.v[j + 1]] / 3
#               else:
#                 vertex2next[self._vertex_coords[face.v[j]] / 3] = self._vertex_coords[face.v[0]] / 3
                
#     # draw edges
#     for i from 0 <= i < self._nb_coords:
#       if vertex2next[i] >= 0:
#         j = i
#         glBegin(GL_LINE_STRIP)
#         while vertex2next[j] >= 0:
#           glVertex3fv(self._coords + j * 3)
#           k = vertex2next[j] # Next vertex
#           vertex2next[j] = -2
#           j = k
          
#         glVertex3fv(self._coords + j * 3)
#         glEnd()
        




    cdef Chunk* chunk
    chunk = get_chunk()
    chunk_register(chunk, self._nb_coords * sizeof(int))
    cdef int* vertex_used
    vertex_used = <int*> (chunk.content)
    for i from 0 <= i < self._nb_coords: vertex_used[i] = -1
    
    
    # find and draw edges   
    glBegin(GL_LINES)
    for i from 0 <= i < self._nb_faces:
      face = self._faces + i
      if face.option & FACE_ALPHA: continue
      
      if face.option & FACE_QUAD: nb = 4
      else:                       nb = 3
      
      if face.option & FACE_SMOOTH_LIT:
        if face.option & FACE_DOUBLE_SIDED:
          for j from 0 <= j < nb:
            k = self._neighbors[4 * i + j]
            if k == -1: # No neighbor, but double-sided face => the face is its own neighbor
              vertex_used[self._vertex_coords[face.v[j]] / 3] = 1

              # draw edge between vertices j and j + 1
              glVertex3fv(self._coords + self._vertex_coords[face.v[j]])
              if j < nb - 1: glVertex3fv(self._coords + self._vertex_coords[face.v[j + 1]])
              else:          glVertex3fv(self._coords + self._vertex_coords[face.v[    0]])
                
            else:
              ns = self._neighbors_side[4 * i + j]
              neighbor_face = self._faces[k]
              if (
                (ns == -1) and (((face.option & FACE_FRONT) and (neighbor_face.option & FACE_BACK )) or ((face.option & FACE_BACK) and (neighbor_face.option & FACE_FRONT)))
                  ) or (
                (ns ==  1) and (((face.option & FACE_FRONT) and (neighbor_face.option & FACE_FRONT)) or ((face.option & FACE_BACK) and (neighbor_face.option & FACE_BACK)))
                ):
                vertex_used[self._vertex_coords[face.v[j]] / 3] = 1
                
                # draw edge between vertices j and j + 1
                glVertex3fv(self._coords + self._vertex_coords[face.v[j]])
                if j < nb - 1: glVertex3fv(self._coords + self._vertex_coords[face.v[j + 1]])
                else:          glVertex3fv(self._coords + self._vertex_coords[face.v[    0]])
                
        else:
          if face.option & FACE_FRONT:
            # test if neighbors are back
            for j from 0 <= j < nb:
              k = self._neighbors[4 * i + j]
              if (k == -1) or (self._faces[k].option & FACE_BACK):
                vertex_used[self._vertex_coords[face.v[j]] / 3] = 1
                
                # draw edge between vertices j and j + 1
                glVertex3fv(self._coords + self._vertex_coords[face.v[j]])
                if j < nb - 1: glVertex3fv(self._coords + self._vertex_coords[face.v[j + 1]])
                else:          glVertex3fv(self._coords + self._vertex_coords[face.v[    0]])
                
      else: # Not smoothlit
        if (face.option & FACE_FRONT) or (face.option & FACE_DOUBLE_SIDED):
          for j from 0 <= j < nb:
            # draw edge between vertices j and j + 1
            glVertex3fv(self._coords + self._vertex_coords[face.v[j]])
            if j < nb - 1: glVertex3fv(self._coords + self._vertex_coords[face.v[j + 1]])
            else:          glVertex3fv(self._coords + self._vertex_coords[face.v[    0]])
            
    glEnd()
    
    glPointSize(d / 2)
        
    glBegin(GL_POINTS)
    for i from 0 <= i < self._nb_coords:
      if vertex_used[i] == 1: glVertex3fv(self._coords + i * 3)
    glEnd()
    
    
    drop_chunk(chunk)
    
    glLineWidth(1.0) # Reset to default
    glPointSize(1.0) # Reset to default
    glEnable   (GL_LIGHTING)
    glDepthFunc(GL_LESS)
    glColor4fv (white)


  cdef float _vertex_compute_cellshading(self, float* coord, float* normal, lights, float shade):
    cdef _Light light
    cdef float  ptr[3]
    cdef float  tmp
    cdef int    i
    
    for light in lights:
      if light._w == 0.0: # directional light
        tmp = -vector_dot_product(normal, light._data)
      else: # positional light
        ptr[0] = light._data[0] - coord[0]
        ptr[1] = light._data[1] - coord[1]
        ptr[2] = light._data[2] - coord[2]
        vector_normalize(ptr)
        tmp = vector_dot_product(normal, ptr)
        
      shade = shade + tmp
      
    return shade

  cdef void _prepare_cellshading_shades(self, float* shades, lights):
    cdef _Light light
    cdef float* ptr1, *ptr2
    cdef float  v[3]
    cdef float  tmp
    cdef int    i, j, k
    
    for light in lights:
      ptr1 = self._vnormals
      if light._w == 0.0: # directional light
        for j from 0 <= j < self._nb_vnormals:
          tmp = -vector_dot_product(ptr1, light._data)
          shades[j] = shades[j] + tmp
          ptr1 = ptr1 + 3
          
      else: # positional light
        ptr2 = self._coords
        for j from 0 <= j < self._nb_vnormals:
          v[0] = light._data[0] - ptr2[0]
          v[1] = light._data[1] - ptr2[1]
          v[2] = light._data[2] - ptr2[2]
          vector_normalize(v)
          tmp = vector_dot_product(ptr1, v)
          shades[j] = shades[j] + tmp
          ptr1 = ptr1 + 3
          ptr2 = ptr2 + 3
          
  cdef void _prepare_cellshading(self, CoordSyst coordsyst, float* shades):
    cdef int    n
    cdef _Light light
    for light in renderer.top_lights:             light._cast_into(coordsyst)
    for light in renderer.current_context.lights: light._cast_into(coordsyst)
    
    if self._nb_vnormals > 0: # Else the shades are computed at the vertex rendering time, since the shades are not shared (all (coord,normal) couples are different)
      for n from 0 <= n < self._nb_vnormals: shades[n] = 0.5
      self._prepare_cellshading_shades(shades, renderer.top_lights)
      self._prepare_cellshading_shades(shades, renderer.current_context.lights)
      
      # clip shade texcoord values
      for n from 0 <= n < self._nb_vnormals:
        # do not clip with interval [0, 1] because smooth magnification of texture
        # causes visual bugs
        if   shades[n] > 0.95: shades[n] = 0.95
        elif shades[n] < 0.05: shades[n] = 0.05


  cdef void _render_triangle_cellshading(self, ShapeFace* face, float* shades):
    if face.option & FACE_SMOOTH_LIT:
      self._render_vertex_cellshading_smoothlit(face.v[0], face.option, shades)
      self._render_vertex_cellshading_smoothlit(face.v[1], face.option, shades)
      self._render_vertex_cellshading_smoothlit(face.v[2], face.option, shades)
    else:
      glNormal3fv(self._values + face.normal)
      self._render_vertex_cellshading(face.v[0], face.option, self._values + face.normal)
      self._render_vertex_cellshading(face.v[1], face.option, self._values + face.normal)
      self._render_vertex_cellshading(face.v[2], face.option, self._values + face.normal)

  cdef void _render_quad_cellshading(self, ShapeFace* face, float* shades):
    if face.option & FACE_SMOOTH_LIT:
      self._render_vertex_cellshading_smoothlit(face.v[0], face.option, shades)
      self._render_vertex_cellshading_smoothlit(face.v[1], face.option, shades)
      self._render_vertex_cellshading_smoothlit(face.v[2], face.option, shades)
      self._render_vertex_cellshading_smoothlit(face.v[3], face.option, shades)
    else:
      glNormal3fv(self._values + face.normal)
      self._render_vertex_cellshading(face.v[0], face.option, self._values + face.normal)
      self._render_vertex_cellshading(face.v[1], face.option, self._values + face.normal)
      self._render_vertex_cellshading(face.v[2], face.option, self._values + face.normal)
      self._render_vertex_cellshading(face.v[3], face.option, self._values + face.normal)


  # XXX face_option arg is useless
  cdef void _render_vertex_cellshading_smoothlit (self, int index, int face_option, float* shades):
    cdef int    n
    cdef float* coord
    cdef float  shade
    n     = self._vertex_coords[index]
    coord = self._coords + n
    
    if face_option & FACE_NON_LIT:
      shade = 0.5 # Medium shade
    else:
      shade = shades[n / 3]
    
    if self._option & SHAPE_DIFFUSES : glColor4fv   (self._colors   + self._vertex_diffuses [index])
    if self._option & SHAPE_EMISSIVES: glMaterialfv (GL_FRONT_AND_BACK, GL_EMISSION, self._colors + self._vertex_emissives[index]) # XXX use glColorMaterial when emissive color but no diffuse ?
    if self._option & SHAPE_TEXCOORDS:
      glMultiTexCoord2fv(GL_TEXTURE0, self._values + self._vertex_texcoords[index])
      glMultiTexCoord2f (GL_TEXTURE1, shade, shade)
    else: glTexCoord2f(shade, shade)
    
    glNormal3fv(self._vnormals + n)
    glVertex3fv(coord)
    
  # XXX face_option arg is useless
  cdef void _render_vertex_cellshading(self, int index, int face_option, float* fnormal):
    cdef float* coord
    cdef float  shade
    coord = self._coords + self._vertex_coords[index]
    
    if face_option & FACE_NON_LIT:
      shade = 0.5 # Medium value
    else:
      shade = self._vertex_compute_cellshading(coord, fnormal, renderer.top_lights, 0.5)
      shade = self._vertex_compute_cellshading(coord, fnormal, renderer.current_context.lights, shade)
      if   shade < 0.05: shade = 0.05
      elif shade > 0.95: shade = 0.95
      
    if self._option & SHAPE_DIFFUSES : glColor4fv  (self._colors   + self._vertex_diffuses [index])
    if self._option & SHAPE_EMISSIVES: glMaterialfv(GL_FRONT_AND_BACK, GL_EMISSION, self._colors + self._vertex_emissives[index]) # XXX use glColorMaterial when emissive color but no diffuse ?
    if self._option & SHAPE_TEXCOORDS:
      glMultiTexCoord2fv(GL_TEXTURE0, self._values + self._vertex_texcoords[index])
      glMultiTexCoord2f (GL_TEXTURE1, shade, shade)
    else: glTexCoord2f(shade, shade)
    
    glVertex3fv(coord)
    
  

