# 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 float _SPRITE_MATRIX[16]
_SPRITE_MATRIX[0] = _SPRITE_MATRIX[5] = _SPRITE_MATRIX[10] = _SPRITE_MATRIX[15] = 1.0
_SPRITE_MATRIX[1] = _SPRITE_MATRIX[2] = _SPRITE_MATRIX[3] = _SPRITE_MATRIX[4] = _SPRITE_MATRIX[6] = _SPRITE_MATRIX[7] = _SPRITE_MATRIX[8] = _SPRITE_MATRIX[9] = _SPRITE_MATRIX[11] = _SPRITE_MATRIX[12] = _SPRITE_MATRIX[13] = _SPRITE_MATRIX[14] = 0.0
cdef float _CYLINDER_SPRITE_MATRIX[16]
_CYLINDER_SPRITE_MATRIX[0] = _CYLINDER_SPRITE_MATRIX[5] = _CYLINDER_SPRITE_MATRIX[10] = _CYLINDER_SPRITE_MATRIX[15] = 1.0
_CYLINDER_SPRITE_MATRIX[1] = _CYLINDER_SPRITE_MATRIX[2] = _CYLINDER_SPRITE_MATRIX[3] = _CYLINDER_SPRITE_MATRIX[4] = _CYLINDER_SPRITE_MATRIX[6] = _CYLINDER_SPRITE_MATRIX[7] = _CYLINDER_SPRITE_MATRIX[8] = _CYLINDER_SPRITE_MATRIX[9] = _CYLINDER_SPRITE_MATRIX[11] = _CYLINDER_SPRITE_MATRIX[12] = _CYLINDER_SPRITE_MATRIX[13] = _CYLINDER_SPRITE_MATRIX[14] = 0.0

cdef class _Sprite(CoordSyst):
  #cdef float _width, _height
  #cdef float _color[4]
  #cdef _Material _material
  
  property width:
    def __get__(self):
      return self._width
    def __set__(self, float x):
      self._width = x
  
  property height:
    def __get__(self):
      return self._height
    def __set__(self, float x):
      self._height = x
  
  property color:
    def __get__(self):
      return self._color[0], self._color[1], self._color[2], self._color[3]
    def __set__(self, x):
      self._color[0], self._color[1], self._color[2], self._color[3] = x
  
  property material:
    def __get__(self):
      return self._material
    def __set__(self, _Material x not None):
      self._material = x
      
  property lit:
    def __get__(self):
      return not(self._option & SPRITE_NEVER_LIT)
    def __set__(self, int x):
      if x: self._option = self._option & ~SPRITE_NEVER_LIT
      else: self._option = self._option |  SPRITE_NEVER_LIT
      
  def __init__(self, _World parent = None, _Material material = None):
    CoordSyst.__init__(self, parent)
    self._material = material or _DEFAULT_MATERIAL
    self._color[0] = self._color[1] = self._color[2] = self._color[3] = 1.0
    self._width = self._height = 0.5
    
  cdef __getcstate__(self):
    #return struct.pack("<iifffffffffffffffffffiiffff", self._option, self._matrix[12], self._matrix[13], self._matrix[14], self._material, self._width, self._height, self._color[0], self._color[1], self._color[2], self._color[3]), self._material
    
    cdef Chunk* chunk
    chunk = get_chunk()
    chunk_add_int_endian_safe   (chunk, self._option)
    chunk_add_floats_endian_safe(chunk, self._matrix, 19)
    chunk_add_float_endian_safe   (chunk, self._width)
    chunk_add_float_endian_safe   (chunk, self._height)
    chunk_add_floats_endian_safe(chunk, self._color ,  4)
    return drop_chunk_to_string(chunk), self._material
  
  cdef void __setcstate__(self, cstate):
    self._validity = COORDSYS_INVALID
    #self._option, self._matrix[12], self._matrix[13], self._matrix[14], self._material, self._width, self._height, self._color[0], self._color[1], self._color[2], self._color[3] = struct.pack("<iifffffffffffffffffffiiffff")
    #self._material = cstate[1]
    
    cdef Chunk* chunk
    cstate2, self._material = cstate
    chunk = string_to_chunk(cstate2)
    chunk_get_int_endian_safe(chunk, &self._option)
    chunk_get_floats_endian_safe(chunk, self._matrix, 19)
    chunk_get_float_endian_safe(chunk, &self._width)
    chunk_get_float_endian_safe(chunk, &self._height)
    chunk_get_floats_endian_safe(chunk, self._color ,  4)
    drop_chunk(chunk)
    
  cdef void _batch(self, CoordSyst coordsyst):
    if self._option & HIDDEN: return
    if self._option & SPRITE_RECEIVE_SHADOW:
      if self.option & SPRITE_ALPHA: renderer._batch(renderer.alpha,    self, None, -1)
      else:                          renderer._batch(renderer.opaque,   self, None, -1)
    else:                            renderer._batch(renderer.specials, self, None, -1)
    
  cdef void _render(self, CoordSyst coordsyst):
    cdef float* a, *b
    # compute render matrix
    b = self._parent._render_matrix
    a = &self._matrix[12]
    _SPRITE_MATRIX[12] = a[0] * b[0] + a[1] * b[4] + a[2] * b[ 8] + b[12]
    _SPRITE_MATRIX[13] = a[0] * b[1] + a[1] * b[5] + a[2] * b[ 9] + b[13]
    _SPRITE_MATRIX[14] = a[0] * b[2] + a[1] * b[6] + a[2] * b[10] + b[14]
    self._material._activate()
    glLoadMatrixf(_SPRITE_MATRIX)
    glDisable(GL_CULL_FACE)
    if self._option & SPRITE_NEVER_LIT: glDisable(GL_LIGHTING)
    else:
      glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_TRUE)
      glNormal3f(0.0, 0.0, -1.0)
    glColor4fv(self._color)
    
    glBegin(GL_QUADS)
    glTexCoord2f(0.0, 0.0); glVertex3f(- self._width, - self._height, 0.0)
    glTexCoord2f(1.0, 0.0); glVertex3f(  self._width, - self._height, 0.0)
    glTexCoord2f(1.0, 1.0); glVertex3f(  self._width,   self._height, 0.0)
    glTexCoord2f(0.0, 1.0); glVertex3f(- self._width,   self._height, 0.0)
    glEnd()
    glEnable(GL_CULL_FACE)
    if self._option & SPRITE_NEVER_LIT: glEnable(GL_LIGHTING)
    else: glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_FALSE)
    
  cdef void _compute_alpha(self):
    if self._material._option & (MATERIAL_ALPHA | MATERIAL_MASK):
      self._option = self._option | SPRITE_ALPHA
      return
    if self._color[3] < 1.0:
      self._option = self._option | SPRITE_ALPHA
      return
    self._option = self._option & ~SPRITE_ALPHA
    
    
cdef class _CylinderSprite(_Sprite):
  cdef __getcstate__(self):
    #return struct.pack("<iifffffffffffffffffffiiffff", self._option, self._matrix[12], self._matrix[13], self._matrix[14], self._material, self._width, self._height, self._color[0], self._color[1], self._color[2], self._color[3]), self._material
    
    cdef Chunk* chunk
    chunk = get_chunk()
    chunk_add_int_endian_safe   (chunk, self._option)
    chunk_add_floats_endian_safe(chunk, self._matrix, 19)
    chunk_add_float_endian_safe   (chunk, self._width)
    chunk_add_float_endian_safe   (chunk, self._height)
    chunk_add_floats_endian_safe(chunk, self._color ,  4)
    return drop_chunk_to_string(chunk), self._material
  
  cdef void __setcstate__(self, cstate):
    self._validity = COORDSYS_INVALID
    #self._option, self._matrix[12], self._matrix[13], self._matrix[14], self._material, self._width, self._height, self._color[0], self._color[1], self._color[2], self._color[3] = struct.pack("<iifffffffffffffffffffiiffff")
    #self._material = cstate[1]
    
    cdef Chunk* chunk
    cstate2, self._material = cstate
    chunk = string_to_chunk(cstate2)
    chunk_get_int_endian_safe(chunk, &self._option)
    chunk_get_floats_endian_safe(chunk, self._matrix, 19)
    chunk_get_float_endian_safe(chunk, &self._width)
    chunk_get_float_endian_safe(chunk, &self._height)
    chunk_get_floats_endian_safe(chunk, self._color ,  4)
    drop_chunk(chunk)
    
  cdef void _render(self, CoordSyst coordsyst):
    cdef float  x, y, f
    cdef float* a, *m
    # compute render matrix
    m = self._parent._render_matrix
    a = &self._matrix[8]
    _CYLINDER_SPRITE_MATRIX[ 8] = a[0] * m[0] + a[1] * m[4] + a[2] * m[ 8]
    _CYLINDER_SPRITE_MATRIX[ 9] = a[0] * m[1] + a[1] * m[5] + a[2] * m[ 9]
    _CYLINDER_SPRITE_MATRIX[10] = a[0] * m[2] + a[1] * m[6] + a[2] * m[10]
    a = &self._matrix[12]
    _CYLINDER_SPRITE_MATRIX[12] = a[0] * m[0] + a[1] * m[4] + a[2] * m[ 8] + m[12]
    _CYLINDER_SPRITE_MATRIX[13] = a[0] * m[1] + a[1] * m[5] + a[2] * m[ 9] + m[13]
    _CYLINDER_SPRITE_MATRIX[14] = a[0] * m[2] + a[1] * m[6] + a[2] * m[10] + m[14]
    if _CYLINDER_SPRITE_MATRIX[10] == 0.0:
      x = _CYLINDER_SPRITE_MATRIX[8]
      y = _CYLINDER_SPRITE_MATRIX[9]
    else:
      f = _CYLINDER_SPRITE_MATRIX[14] / _CYLINDER_SPRITE_MATRIX[10]
      x = _CYLINDER_SPRITE_MATRIX[12] - f * _CYLINDER_SPRITE_MATRIX[8]
      y = _CYLINDER_SPRITE_MATRIX[13] - f * _CYLINDER_SPRITE_MATRIX[9]
      # sign of x, y ?
    if (x == 0.0) and (y == 0.0):
      _CYLINDER_SPRITE_MATRIX[4] = 0.0
      _CYLINDER_SPRITE_MATRIX[5] = 1.0
    else:
      f = <float> (1.0 / sqrt(x * x + y * y))
      _CYLINDER_SPRITE_MATRIX[5] = -x * f
      _CYLINDER_SPRITE_MATRIX[4] =  y * f
    _CYLINDER_SPRITE_MATRIX[0] =  _CYLINDER_SPRITE_MATRIX[5] * _CYLINDER_SPRITE_MATRIX[10] - _CYLINDER_SPRITE_MATRIX[6] * _CYLINDER_SPRITE_MATRIX[9]
    _CYLINDER_SPRITE_MATRIX[1] = -_CYLINDER_SPRITE_MATRIX[4] * _CYLINDER_SPRITE_MATRIX[10] + _CYLINDER_SPRITE_MATRIX[6] * _CYLINDER_SPRITE_MATRIX[8]
    _CYLINDER_SPRITE_MATRIX[2] =  _CYLINDER_SPRITE_MATRIX[4] * _CYLINDER_SPRITE_MATRIX[ 9] - _CYLINDER_SPRITE_MATRIX[5] * _CYLINDER_SPRITE_MATRIX[8]
    # render
    self._material._activate()
    glLoadMatrixf(_CYLINDER_SPRITE_MATRIX)
    glDisable(GL_CULL_FACE)
    if self._option & SPRITE_NEVER_LIT: glDisable(GL_LIGHTING)
    else:
      glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_TRUE)
      glNormal3f(1.0, 0.0, 0.0)
    glColor4fv(self._color)
    glBegin(GL_QUADS)
    glTexCoord2f(0.0, 0.0); glVertex3f(0.0, - self._height, - self._width)
    glTexCoord2f(1.0, 0.0); glVertex3f(0.0,   self._height, - self._width)
    glTexCoord2f(1.0, 1.0); glVertex3f(0.0,   self._height,   self._width)
    glTexCoord2f(0.0, 1.0); glVertex3f(0.0, - self._height,   self._width)
    glEnd()
    glEnable(GL_CULL_FACE)
    if self._option & SPRITE_NEVER_LIT: glEnable(GL_LIGHTING)
    else: glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_FALSE)


cdef class _Bonus(CoordSyst):
  #cdef float _angle
  #cdef float _color[4]
  #cdef _Material _material, _halo
  
  cdef __getcstate__(self):
    #return struct.pack("<ifffffffffffffffffffffff", self._option, self._matrix[0], self._matrix[1], self._matrix[2], self._matrix[3], self._matrix[4], self._matrix[5], self._matrix[6], self._matrix[7], self._matrix[8], self._matrix[9], self._matrix[10], self._matrix[11], self._matrix[12], self._matrix[13], self._matrix[14], self._matrix[15], self._matrix[16], self._matrix[17], self._matrix[18], self._color[0], self._color[1], self._color[2], self._color[3]), self._material
  
    cdef Chunk* chunk
    chunk = get_chunk()
    chunk_add_int_endian_safe   (chunk, self._option)
    chunk_add_floats_endian_safe(chunk, self._matrix, 19)
    chunk_add_floats_endian_safe(chunk, self._color ,  4)
    return drop_chunk_to_string(chunk), self._material, self._halo
  
  cdef void __setcstate__(self, cstate):
    self._validity = COORDSYS_INVALID
    #self._option, self._matrix[0], self._matrix[1], self._matrix[2], self._matrix[3], self._matrix[4], self._matrix[5], self._matrix[6], self._matrix[7], self._matrix[8], self._matrix[9], self._matrix[10], self._matrix[11], self._matrix[12], self._matrix[13], self._matrix[14], self._matrix[15], self._matrix[16], self._matrix[17], self._matrix[18], self._color[0], self._color[1], self._color[2], self._color[3] = struct.unpack("<ifffffffffffffffffffffff", cstate[0])
    #self._material = cstate[1]
  
    cdef Chunk* chunk
    cstate2, self._material, self._halo = cstate
    chunk = string_to_chunk(cstate2)
    chunk_get_int_endian_safe(chunk, &self._option)
    chunk_get_floats_endian_safe(chunk, self._matrix, 19)
    chunk_get_floats_endian_safe(chunk, self._color ,  4)
    drop_chunk(chunk)
    
  property color:
    def __get__(self):
      return self._color[0], self._color[1], self._color[2], self._color[3]
    def __set__(self, x):
      self._color[0], self._color[1], self._color[2], self._color[3] = x
  
  property material:
    def __get__(self):
      return self._material
    def __set__(self, _Material x not None):
      self._material = x
   
  property halo:
    def __get__(self):
      return self._halo
    def __set__(self, _Material x not None):
      self._halo = x
   
  property lit:
    def __get__(self):
      return not(self._option & SPRITE_NEVER_LIT)
    def __set__(self, int x):
      if x: self._option = self._option & ~SPRITE_NEVER_LIT
      else: self._option = self._option |  SPRITE_NEVER_LIT
  
  def __init__(self, _World parent = None, _Material material = None, _Material halo = None):
    CoordSyst.__init__(self, parent)
    self._material = material or _DEFAULT_MATERIAL
    self._halo     = halo     or _DEFAULT_MATERIAL
    self._color[0] = self._color[1] = self._color[2] = self._color[3] = 1.0
    
  cdef void _batch(self, CoordSyst coordsyst):
    if self._option & HIDDEN: return
    if not(self._option & BONUS_BATCHED):
      # make the bonus rotate
      self._option = self._option | BONUS_BATCHED
      self._angle = self._angle + 4.0
      if self._angle >= 360.0: self._angle = self._angle - 360.0
    multiply_matrix(self._render_matrix, coordsyst._render_matrix, self._matrix)
    self._frustum_id = -1
    renderer._batch(renderer.alpha, self, self, -1)
    
  cdef void _render(self, CoordSyst coordsyst):
    cdef float* m
    self._option = self._option & ~BONUS_BATCHED
    glDisable(GL_CULL_FACE)
    if self._option & SPRITE_NEVER_LIT: glDisable(GL_LIGHTING)
    else: glNormal3f(0.0, 0.0, -1.0)
    # draw halo
    #m = self._parent._render_matrix
    #_SPRITE_MATRIX[12] = m[12] + self._matrix[12]
    #_SPRITE_MATRIX[13] = m[13] + self._matrix[13] + 1.0
    #_SPRITE_MATRIX[14] = m[14] + self._matrix[14]
    #glLoadMatrixf(_SPRITE_MATRIX)
    m = self._render_matrix
    _SPRITE_MATRIX[12] = m[12]
    _SPRITE_MATRIX[13] = m[13] + 1.0
    _SPRITE_MATRIX[14] = m[14]
    glLoadMatrixf(_SPRITE_MATRIX)
    
    self._halo._activate()
    glColor4fv(self._color)
    glDisable(GL_LIGHTING)
    glBegin(GL_QUADS)
    glTexCoord2f(0.0, 1.0); glVertex3f(- 1.0, - 1.0, 0.0)
    glTexCoord2f(1.0, 1.0); glVertex3f(- 1.0,   1.0, 0.0)
    glTexCoord2f(1.0, 0.0); glVertex3f(  1.0,   1.0, 0.0)
    glTexCoord2f(0.0, 0.0); glVertex3f(  1.0, - 1.0, 0.0)
    glEnd()
    glEnable(GL_LIGHTING)
    # draw 2D square
    glRotatef(self._angle, 0.0, 1.0, 0.0)
    glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_TRUE)
    self._material._activate()
    glBegin(GL_QUADS)
    glTexCoord2f(0.0, 1.0); glVertex3f(- 0.5, -0.5, 0.0)
    glTexCoord2f(1.0, 1.0); glVertex3f(  0.5, -0.5, 0.0)
    glTexCoord2f(1.0, 0.0); glVertex3f(  0.5,  0.5, 0.0)
    glTexCoord2f(0.0, 0.0); glVertex3f(- 0.5,  0.5, 0.0)
    glEnd()
    glEnable(GL_CULL_FACE)
    glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_FALSE)
    if self._option & SPRITE_NEVER_LIT: glEnable(GL_LIGHTING)
