# 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 _Material _PARTICLE_DEFAULT_MATERIAL
def _set_particle_default_material(_Material material):
  global _PARTICLE_DEFAULT_MATERIAL
  _PARTICLE_DEFAULT_MATERIAL = material


cdef class _Particles(CoordSyst):
  #cdef _Material _material
  #cdef CoordSyst _particle_coordsyst
  #cdef int       _nb_particles, _nb_max_particles, _particle_size # range from 11 to 20 float
  #cdef float*    _particles # life, max_life, position, speed, acceleration, [color], [size], [direction]
                            # life, max_life, x/y/z, u/v/w, a/b/c, [r/g/b/a], [w/h], [m/n/o]
  #cdef float     _delta_time
  #cdef int       _nb_colors, _nb_sizes
  #cdef float*    _fading_colors, *_sizes # fading colors and size gain
  #cdef int       _nb_creatable_particles, _max_particles_per_round
  
  cdef __getcstate__(self):
    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_int_endian_safe   (chunk, self._nb_particles)
    chunk_add_int_endian_safe   (chunk, self._nb_max_particles)
    chunk_add_int_endian_safe   (chunk, self._particle_size)
    chunk_add_int_endian_safe   (chunk, self._nb_colors)
    chunk_add_int_endian_safe   (chunk, self._nb_sizes)
    chunk_add_int_endian_safe   (chunk, self._max_particles_per_round)
    chunk_add_floats_endian_safe(chunk, self._particles, self._nb_particles * self._particle_size)
    if self._nb_colors:
      chunk_add_floats_endian_safe(chunk, self._fading_colors, 4 * self._nb_colors)
    if self._nb_sizes:
      chunk_add_floats_endian_safe(chunk, self._sizes, 2 * self._nb_sizes)
      
    return drop_chunk_to_string(chunk), self._material, self._particle_coordsyst
  
  cdef void __setcstate__(self, cstate):
    cdef Chunk* chunk
    cstate2, self._material, self._particle_coordsyst = 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_int_endian_safe(chunk, &self._nb_particles)
    chunk_get_int_endian_safe(chunk, &self._nb_max_particles)
    chunk_get_int_endian_safe(chunk, &self._particle_size)
    chunk_get_int_endian_safe(chunk, &self._nb_colors)
    chunk_get_int_endian_safe(chunk, &self._nb_sizes)
    chunk_get_int_endian_safe(chunk, &self._max_particles_per_round)
    
    self._particles = <float*> malloc(self._nb_max_particles * self._particle_size)
    chunk_get_floats_endian_safe(chunk, self._particles, self._nb_particles * self._particle_size)
    if self._nb_colors:
      self._fading_colors = <float*> malloc(4 * self._nb_colors * sizeof(float))
      chunk_get_floats_endian_safe(chunk, self._fading_colors, 4 * self._nb_colors)
    if self._nb_sizes:
      self._sizes = <float*> malloc(2 * self._nb_colors * sizeof(float))
      chunk_get_floats_endian_safe(chunk, self._sizes, 2 * self._nb_sizes)
    
    drop_chunk(chunk)
    self._validity = COORDSYS_INVALID
    
  property material:
    def __get__(self):
      return self._material
    def __set__(self, _Material x not None):
      self._material = x
      self._compute_alpha()
      
  property particle_coordsyst:
    def __get__(self):
      return self._particle_coordsyst
    def __set__(self, CoordSyst x):
      self._particle_coordsyst = x
  
  property nb_particles:
    def __get__(self):
      return self._nb_particles
    def __set__(self, int x):
      self._nb_particles = x
  
  property nb_max_particles:
    def __get__(self):
      return self._nb_max_particles
    def __set__(self, int x):
      self._nb_max_particles = x
      self._reinit()
      
  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

  property auto_generate_particle:
    def __get__(self):
      return self._option & PARTICLES_AUTO_GENERATE
    def __set__(self, int x):
      if x:
        self._option = self._option |  PARTICLES_AUTO_GENERATE
        self._nb_creatable_particles = self._max_particles_per_round
      else:
        self._option = self._option & ~PARTICLES_AUTO_GENERATE
        self._nb_creatable_particles = 0
        
  property removable:
    def __get__(self):
      return self._option & REMOVABLE
    def __set__(self, int x):
      if x: self._option = self._option |  PARTICLES_REMOVABLE
      else: self._option = self._option & ~PARTICLES_REMOVABLE
      
  property max_particles_per_round:
    def __get__(self):
      return self._max_particles_per_round
    def __set__(self, int x):
      self._max_particles_per_round = x
      if self._option & PARTICLES_AUTO_GENERATE: self._nb_creatable_particles = x
      
  def __init__(self, _World parent = None, _Material material = None, int nb_max_particles = 50, int removable = 0):
    CoordSyst.__init__(self, parent)
    self._material                = material or _PARTICLE_DEFAULT_MATERIAL
    self._particle_size           = 11
    self._nb_sizes                = 1
    self._sizes                   = <float*> malloc(2 * sizeof(float))
    self._sizes[0]                = self._sizes[1] = 1.0
    self._max_particles_per_round = 1000000
    self._nb_creatable_particles  = nb_max_particles
    self._nb_max_particles        = nb_max_particles
    self._fading_colors           = NULL
    self._particles               = <float*> malloc(self._nb_max_particles * self._particle_size * sizeof(float))
    
    if removable != 0: self._option = self._option | PARTICLES_REMOVABLE
    
  cdef void _reinit(self):
    self._particle_size = 11
    if self._option & PARTICLES_MULTI_COLOR: self._particle_size = self._particle_size + 4
    if self._option & PARTICLES_MULTI_SIZE:  self._particle_size = self._particle_size + 2
    if self._option & PARTICLES_CYLINDER:    self._particle_size = self._particle_size + 3
    self._particles = <float*> realloc(self._particles, self._nb_max_particles * self._particle_size * sizeof(float))
    
  def __dealloc__(self):
    free(self._particles)
    free(self._sizes)
    if self._fading_colors != NULL: free(self._fading_colors)
    
  def regenerate(self, int nb_particle = -1):
    cdef int i, max, nb
    if nb_particle == -1: nb_particle = self._max_particles_per_round
    nb  = 0
    max = self._nb_particles + nb_particle
    if max > self._nb_max_particles: max = self._nb_max_particles
    for i from self._nb_particles <= i < max: self.generate(i)
    
  def begin_round(self):
    if self._option & PARTICLES_AUTO_GENERATE and (self._nb_creatable_particles < self._max_particles_per_round):
      self._nb_creatable_particles = self._max_particles_per_round
      
  def advance_time(self, float proportion):
    self._delta_time = self._delta_time + proportion
    
  cdef void _advance(self, float proportion):
    cdef float* particle
    cdef float  decay
    cdef int    i
    decay = - 0.05 * proportion
    particle = self._particles
    i = 0
    while i < self._nb_particles:
      particle[0] = particle[0] + decay
      if particle[0] < 0.0: # particle is dead
        #if (not(self._option & PARTICLES_AUTO_GENERATE)) or (self.generator(i) == 1): # remove the particle
        if (self._nb_creatable_particles != 0) and (self.generate(i) != 1): # Replace the dead particle by a new one
          self._nb_creatable_particles = self._nb_creatable_particles - 1
        else: # Delete particle
          self._nb_particles = self._nb_particles - 1
          
          memcpy(self._particles + i                  * self._particle_size, 
                 self._particles + self._nb_particles * self._particle_size, 
                 self._particle_size * sizeof(float))
          continue
        
      else: # advance position
        particle[5] = particle[5] + (particle[ 8] * proportion) # speed += acceleration * proportion
        particle[6] = particle[6] + (particle[ 9] * proportion)
        particle[7] = particle[7] + (particle[10] * proportion)
        particle[2] = particle[2] + (particle[ 5] * proportion) # position = speed * proportion
        particle[3] = particle[3] + (particle[ 6] * proportion)
        particle[4] = particle[4] + (particle[ 7] * proportion)
        # advance color
        if (self._nb_colors > 0) and (self._option & PARTICLES_MULTI_COLOR):
          self._get_fading_color(particle[0], particle[1], particle + 11)
          
        # advance size
        if self._option & PARTICLES_MULTI_SIZE:
          if self._option & PARTICLES_MULTI_COLOR: self._get_size(particle[0], particle[1], particle + 15)
          else:                                    self._get_size(particle[0], particle[1], particle + 11)
          
        # advance direction
        if self._option & PARTICLES_CYLINDER:
          pass # XXX TO DO
        
      particle = particle + self._particle_size
      i = i + 1
      
    if (self._nb_particles < self._nb_max_particles) and (self._nb_creatable_particles > 0):
      self.regenerate(self._nb_creatable_particles)
      self._nb_creatable_particles = 0
      
    if (self._option & PARTICLES_REMOVABLE) and (self._nb_particles == 0):
      #self.remove()
      from soya import IDLER
      IDLER.next_round_tasks.append(self.remove)
      
  def remove(self):
    if self._parent: self._parent.remove(self)
    
  cdef void _batch(self, CoordSyst coordsyst):
    if self._option & HIDDEN: return
    # XXX TO DO usefull ?
    # P3_multiply_matrix(p->render_matrix, renderer->c_camera->render_matrix, P3_coordsys_get_root_matrix ((P3_coordsys*) p))
    
    self._advance(self._delta_time)
    if not self._parent is None: # Else, _advance has removed the Particles system.
      renderer._batch(renderer.alpha,    self, coordsyst, -1)
    self._delta_time = 0.0
    
    #if self._option & SPRITE_RECEIVE_SHADOW:
    #  if self._option & SPRITE_ALPHA: renderer._batch(renderer.alpha,    self, coordsyst, -1)
    #  else:                           renderer._batch(renderer.opaque,   self, coordsyst, -1)
    #else:                             renderer._batch(renderer.specials, self, coordsyst, -1)
    
  cdef void _render(self, CoordSyst coordsyst):
    cdef float* particle, *m
    cdef float  posi[3], w, h
    cdef int    i
    self._material._activate()
    
    glDisable(GL_CULL_FACE)
    if self._option & SPRITE_NEVER_LIT: glDisable(GL_LIGHTING)
    if self._option & SPRITE_COLORED: glColor4fv(self._fading_colors)
    if self._option & PARTICLES_MULTI_SIZE: w = h = 1.0
    else:
      w = self._sizes[0]
      h = self._sizes[1]
      
    glLoadIdentity()
    #if self._particle_coordsyst is None: m = renderer.current_camera._render_matrix
    if self._particle_coordsyst is None: m = self._get_root()._render_matrix
    else:                                m = self._particle_coordsyst._render_matrix
    particle = self._particles
    
    glBegin(GL_QUADS)
    for i from 0 <= i < self._nb_particles:
      if self._option & PARTICLES_MULTI_COLOR:
        glColor4fv(particle + 11)
        
        if self._option & PARTICLES_MULTI_SIZE:
          w = particle[15]
          h = particle[16]
      elif self._option & PARTICLES_MULTI_SIZE:
        w = particle[11]
        h = particle[12]
        
      posi[0] = particle[2] * m[0] + particle[3] * m[4] + particle[4] * m[ 8] + m[12]
      posi[1] = particle[2] * m[1] + particle[3] * m[5] + particle[4] * m[ 9] + m[13]
      posi[2] = particle[2] * m[2] + particle[3] * m[6] + particle[4] * m[10] + m[14]
      glTexCoord2f(0.0, 0.0); glVertex3f(posi[0] - w, posi[1] - h, posi[2])
      glTexCoord2f(1.0, 0.0); glVertex3f(posi[0] + w, posi[1] - h, posi[2])
      glTexCoord2f(1.0, 1.0); glVertex3f(posi[0] + w, posi[1] + h, posi[2])
      glTexCoord2f(0.0, 1.0); glVertex3f(posi[0] - w, posi[1] + h, posi[2])
      particle = particle + self._particle_size
      
    glEnd()
    if self._option & SPRITE_NEVER_LIT: glEnable(GL_LIGHTING)
    glEnable(GL_CULL_FACE)
    

  cdef void _compute_alpha(self):
    if (self._material._option & (MATERIAL_ALPHA | MATERIAL_MASK)) or ((self._option & SPRITE_COLORED) and (self._fading_colors[3] < 1.0)) or (self._option & PARTICLES_MULTI_COLOR):
      self._option = self._option |  SPRITE_ALPHA
    else:
      self._option = self._option & ~SPRITE_ALPHA
      
  cdef void _get_fading_color(self, float life, float max_life, float* returned_color):
    cdef int    i
    cdef float  f1, f2
    cdef float* c1, *c2
    if   life <= 0.0:      memcpy(returned_color, self._fading_colors + 4 * (self._nb_colors - 1), 4 * sizeof (float))
    elif life >= max_life: memcpy(returned_color, self._fading_colors, 4 * sizeof(float))
    else:
      life = (1.0 - (life / max_life)) * (self._nb_colors - 1)
      i    = <int> life
      c1   = self._fading_colors + 4 *  i
      c2   = self._fading_colors + 4 * (i + 1)
      f2   = life - i
      f1   = 1.0 - f2
      returned_color[0] = c1[0] * f1 + c2[0] * f2
      returned_color[1] = c1[1] * f1 + c2[1] * f2
      returned_color[2] = c1[2] * f1 + c2[2] * f2
      returned_color[3] = c1[3] * f1 + c2[3] * f2
      
  cdef void _get_size(self, float life, float max_life, float* returned_size):
    cdef int    i
    cdef float  f1, f2
    cdef float* c1, *c2
    if   life <= 0.0:      memcpy(returned_size, self._sizes + 2 * (self._nb_sizes - 1), 2 * sizeof(float))
    elif life >= max_life: memcpy(returned_size, self._sizes, 2 * sizeof(float))
    else:
      life = (1.0 - (life / max_life)) * (self._nb_sizes - 1)
      i    = <int> life
      c1   = self._sizes + 2 *  i
      c2   = self._sizes + 2 * (i + 1)
      f2   = life - i
      f1   = 1.0 - f2
      returned_size[0] = c1[0] * f1 + c2[0] * f2
      returned_size[1] = c1[1] * f1 + c2[1] * f2
  
  cdef float* _generate(self, int index, float life):
    cdef float* particle
    particle = self._particles + index * self._particle_size
    particle[0] = particle[1] = life
    if self._parent is None: memcpy (particle + 2, self._matrix + 12, 3 * sizeof(float))
    else: point_by_matrix_copy(particle + 2, self._matrix + 12, self._parent._root_matrix())
    if not self._particle_coordsyst is None: point_by_matrix(particle + 2, self._particle_coordsyst._inverted_root_matrix())
    
    if self._option & PARTICLES_MULTI_COLOR:
      memcpy(particle + 11, self._fading_colors, 4 * sizeof(float))
      if self._option & PARTICLES_MULTI_SIZE: memcpy(particle + 15, self._sizes, 2 * sizeof(float))
    elif self._option & PARTICLES_MULTI_SIZE: memcpy(particle + 11, self._sizes, 2 * sizeof(float))
    
    if self._nb_particles <= index: self._nb_particles = index + 1
    return self._particles + self._particle_size * index
  
  def generate(self, int i):
    pass
  
  def set_particle(self, int index, float life, float speed_x, float speed_y, float speed_z, float accel_x, float accel_y, float accel_z, float dir_x = 0.0, float dir_y = 0.0, float dir_z = 0.0):
    # args are: (index, life, speed x, y, z, acceleration x, y, z, [direction x, y, z])
    cdef int    i
    cdef float* particle
    particle = self._generate(index, life)
    particle[ 5] = speed_x
    particle[ 6] = speed_y
    particle[ 7] = speed_z
    particle[ 8] = accel_x
    particle[ 9] = accel_y
    particle[10] = accel_z
    if self._option & PARTICLES_CYLINDER:
      i = 11
      if self._option & PARTICLES_MULTI_COLOR: i = i + 4
      if self._option & PARTICLES_MULTI_SIZE:  i = i + 2
      particle[i    ] = dir_x
      particle[i + 1] = dir_y
      particle[i + 2] = dir_z
    
  def set_particle2(self, int index, float life, float x, float y, float z, float speed_x, float speed_y, float speed_z, float accel_x, float accel_y, float accel_z, float dir_x = 0.0, float dir_y = 0.0, float dir_z = 0.0):
    # args are: (index, life, position x, y, z, speed x, y, z, acceleration x, y, z, [direction x, y, z])
    cdef int    i
    cdef float* particle
    particle = self._particles + index * self._particle_size
    particle[0] = life
    particle[1] = particle[0]
    if self._option & PARTICLES_MULTI_COLOR:
      memcpy(particle + 11, self._fading_colors, 4 * sizeof(float))
      if self._option & PARTICLES_MULTI_SIZE: memcpy(particle + 15, self._sizes, 2 * sizeof(float))
    if self._option & PARTICLES_MULTI_SIZE: memcpy(particle + 11, self._sizes, 2 * sizeof(float))
    if self._nb_particles <= index: self._nb_particles = index + 1
    
    particle[ 2] = x
    particle[ 3] = y
    particle[ 4] = z
    particle[ 5] = speed_x
    particle[ 6] = speed_y
    particle[ 7] = speed_z
    particle[ 8] = accel_x
    particle[ 9] = accel_y
    particle[10] = accel_z
    if self._option & PARTICLES_CYLINDER:
      i = 11
      if self._option & PARTICLES_MULTI_COLOR: i = i + 4
      if self._option & PARTICLES_MULTI_SIZE:  i = i + 2
      particle[i    ] = dir_x
      particle[i + 1] = dir_y
      particle[i + 2] = dir_z
      
  def get_particle_position(self, int index):
    cdef float* particle
    particle = self._particles + index * self._particle_size
    return particle[2], particle[3], particle[4]
  
  def set_particle_position(self, int index, float x, float y, float z):
    cdef float* particle
    particle = self._particles + index * self._particle_size
    particle[2] = x
    particle[3] = y
    particle[4] = z
    
  def set_colors(self, *colors):
    cdef int i, j
    
    self._nb_colors = len(colors)
    if self._nb_colors != 0:
      self._fading_colors = <float*> realloc(self._fading_colors, self._nb_colors * 4 * sizeof(float))
      for i from 0 <= i < self._nb_colors:
        color = colors[i]
        self._fading_colors[i * 4    ] = color[0]
        self._fading_colors[i * 4 + 1] = color[1]
        self._fading_colors[i * 4 + 2] = color[2]
        self._fading_colors[i * 4 + 3] = color[3]
      self._option = self._option | SPRITE_COLORED
      if self._nb_colors == 1: self._option = self._option & ~PARTICLES_MULTI_COLOR
      else:                    self._option = self._option |  PARTICLES_MULTI_COLOR
    else:
      if self._fading_colors != NULL:
        free(self._fading_colors)
        self._fading_colors = NULL
      self._option = self._option & ((~SPRITE_COLORED) | (~PARTICLES_MULTI_COLOR))
    self._compute_alpha()
    self._reinit()
    
  def set_sizes(self, *sizes):
    cdef int i
    if sizes:
      self._nb_sizes = len(sizes)
      self._sizes = <float*> realloc(self._sizes, self._nb_sizes * 2 * sizeof(float))
      for i from 0 <= i < self._nb_sizes:
        size = sizes[i]
        self._sizes[i * 2    ] = size[0]
        self._sizes[i * 2 + 1] = size[1]
      if self._nb_sizes == 1: self._option = self._option & ~PARTICLES_MULTI_SIZE
      else:                   self._option = self._option |  PARTICLES_MULTI_SIZE
    else:
      self._nb_sizes = 1
      self._sizes = <float*> realloc(self._sizes, 2 * sizeof(float))
      self._sizes[0] = self._sizes[1] = 1.0
      self._option = self._option & ~PARTICLES_MULTI_SIZE
    self._reinit()


import random

# Hint :
# The free Linux driver for ati radeon 7500 has problem for rendering transparency
# => as particles system usually use additive blending, you should avoid using alpha
# for fading colors.
# E.g. :
#   Particles.set_colors((1.0, 1.0, 1.0, 0.5), (1.0, 1.0, 1.0, 1.0), (1.0, 1.0, 1.0, 0.0))
# and
#   Particles.set_colors((0.5, 0.5, 0.5, 1.0), (1.0, 1.0, 1.0, 1.0), (0.0, 0.0, 0.0, 1.0))
# are equivalent with additive blending, but the last one doesn't bug on radeon 7500.

cdef class Fountain(_Particles):
  def __init__(self, _World parent = None, _Material material = None, int nb_particles = 12, int removable = 0):
    _Particles.__init__(self, parent, material, nb_particles, removable)
    #self.set_colors((0.3, 0.3, 0.3, 1.0), (0.3, 0.3, 0.3, 0.0))
    #self.set_colors((0.3, 0.3, 0.3, 1.0), (0.1, 0.1, 0.1, 0.0))
    self.set_colors((0.1, 0.1, 0.1, 1.0), (0.3, 0.3, 0.3, 1.0), (0.3, 0.3, 0.3, 1.0), (0.1, 0.1, 0.1, 1.0))
    self.set_sizes ((0.25, 0.25), (1.0, 1.0))
    
  def generate(self, int index):
    cdef float sx, sy, sz, l
    sx = random.random() - 0.5
    sy = random.random() + 1.0
    sz = random.random() - 0.5
    l  = (0.2 * (1.0 + random.random())) / sqrt(sx * sx + sy * sy + sz * sz) * 0.4 * self._matrix[16]
    self.set_particle(index, 1.0 + random.random(), sx * l, sy * l, sz * l, 0.0, 0.0, 0.0)

cdef class Smoke(_Particles):
  def __init__(self, _World parent = None, _Material material = None, int nb_particles = 12, int removable = 0):
    _Particles.__init__(self, parent, material, nb_particles, removable)
    #self.set_colors((0.3, 0.3, 0.3, 1.0), (0.3, 0.3, 0.3, 0.0))
    #self.set_colors((0.3, 0.3, 0.3, 1.0), (0.1, 0.1, 0.1, 0.0))
    self.set_colors((0.1, 0.1, 0.1, 1.0), (0.3, 0.3, 0.3, 1.0), (0.3, 0.3, 0.3, 1.0), (0.1, 0.1, 0.1, 1.0))
    self.set_sizes ((0.25, 0.25), (1.0, 1.0))
    
  def generate(self, int index):
    cdef float sx, sy, sz, l
    sx = random.random() - 0.5
    sy = random.random() - 0.5
    sz = random.random() - 0.5
    l  = (0.2 * (1.0 + random.random())) / sqrt(sx * sx + sy * sy + sz * sz) * 0.4 * self._matrix[16]
    self.set_particle(index, 1.0 + random.random(), sx * l, sy * l, sz * l, 0.0, 0.0, 0.0)
    
    
cdef class FlagSubFire(_Particles):
  def __init__(self, _World parent = None, _Material material = None, int nb_particles = 12, int removable = 0):
    _Particles.__init__(self, parent, material, nb_particles, removable)
    self.set_colors((1.0, 1.0, 1.0, 1.0), (0.2, 0.7, 0.8, 0.8), (0.2, 0.4, 0.8, 0.2))
    self.set_sizes ((0.25, 0.25), (1.0, 1.0))
    
  def generate(self, int index, float x = 0.0, float y = 0.0, float z = 0.0):
    #angle = random.random() * 3.1417
    #sx = math.cos(angle)
    #sy = 2.0 + math.sqrt(random.random() * 50.0)
    #sz = math.sin(angle)
    cdef float sx, sy, sz, l
    sx = random.random() - 0.5
    sy = random.random() + 1.0
    sz = random.random() - 0.5
    l  = (0.2 * (1.0 + random.random())) / sqrt(sx * sx + sy * sy + sz * sz) * self._matrix[16]
    self.set_particle2(index, 0.5 + random.random(), x, y, z, sx * l, sy * l, sz * l, 0.0, -0.03, 0.0)
    #if position:
    #  self.set_particle_all(index, 0.5 + random.random(), x, y, z, sx * l, sy * l, sz * l, 0.0, -0.03, 0.0)
    #else:
    #  self.set_particle(index, 0.5 + random.random(), sx * l, sy * l, sz * l, 0.0, -0.03, 0.0)



cdef class FlagFirework(_Particles):
  #cdef _Particles _subgenerator
  #cdef int        _nb_sub_particles
  
  def __init__(self, _World parent = None, _Material material = None, int nb_particles = 6, int removable = 0, subgenerator = None, int nb_sub_particles = 8):
    _Particles.__init__(self, parent, material, nb_particles, removable)
    self._subgenerator = subgenerator or FlagSubFire(parent, nb_particles = nb_particles * nb_sub_particles)
    self._nb_sub_particles = nb_sub_particles
    self.set_colors((0.9, 0.7, 0.0, 0.4), (1.0, 1.0, 0.0, 1.0), (1.0, 1.0, 1.0, 1.0))
    self.set_sizes ((0.0, 0.0), (0.25, 0.25))
    self._option = self._option | PARTICLES_AUTO_GENERATE

  def remove(self):
    _Particles.remove(self)
    self._subgenerator.removable = 1
    
  def regenerate(self, int nb_particle = -1):
    cdef int i, nb
    if nb_particle == -1: nb_particle = self._max_particles_per_round
    nb = 0
    i  = self._nb_particles
    while i < self._nb_max_particles:
      self.mygenerate(i)
      i  = nb + 1
      nb = i  + 1
      if nb >= nb_particle: break
    #self.advance_time(1.0)
    
  def subgenerate(self, int index):
    cdef int        i
    cdef _Particles subg
    subg = self._subgenerator
    if not subg is None:
      if subg._nb_particles + self._nb_sub_particles > subg._nb_max_particles:
        # nb_max_particles and NOT _nb_max_particles (note the starting _) :
        # Changing the Python attribute nb_max_particles will realloc the memory as needed,
        # though a raw assignment to _nb_max_particles won't.
        subg.nb_max_particles = subg._nb_particles + self._nb_sub_particles
      p = self.get_particle_position(index)
      
      for i from subg._nb_particles <= i < subg._nb_particles + self._nb_sub_particles:
        subg.generate(i, p[0], p[1], p[2])
        #subg.set_particle_position(i, *p)
        
  def mygenerate(self, int index):
    cdef float sx, sy, sz, l
    sx = random.random() - 0.5
    sy = random.random() + 1.0
    sz = random.random() - 0.5
    l  = (0.2 * (1.8 + random.random())) / sqrt(sx * sx + sy * sy + sz * sz)
    self.set_particle(index, 0.5 + random.random(), sx * l, sy * l, sz * l, 0.0, -0.03, 0.0)
    
  def generate(self, int index):
    self.subgenerate(index)
    self.mygenerate(index)





# def draw_sprites(CoordSyst coordsyst, sprites):
#   # args : (coordsyst, [particules])
#   cdef CoordSyst root, parent
#   cdef float position[3], color[4], w, h
#   int i, nb
  
#   # TO DO coordsyst is useless...
#   sprites = PySequence_Fast_GET_ITEM (args, 1);
#   nb      = len(sprites)
#   root    = renderer.current_camera
#   glLoadIdentity()
#   glDisable(GL_CULL_FACE)
#   # use the current activated material :)
#   glBegin(GL_QUADS)
#   for i from 0 <= i < nb:
#     sprite = sprites[i]
#     parent = sprite.parent
#     position[0] = sprite.x
#     position[1] = sprite.y
#     position[2] = sprite.z
#     if not parent is root:
#       if not parent is None: point_by_matrix(position, parent._root_matrix())
#       point_by_matrix(position, root._inverted_root_matrix())
    
#     o = PyObject_GetAttrString (sprite, "color");
#     color[0] = (GLfloat) PyFloat_AS_DOUBLE (PySequence_Fast_GET_ITEM (o, 0));
#     color[1] = (GLfloat) PyFloat_AS_DOUBLE (PySequence_Fast_GET_ITEM (o, 1));
#     color[2] = (GLfloat) PyFloat_AS_DOUBLE (PySequence_Fast_GET_ITEM (o, 2));
#     color[3] = (GLfloat) PyFloat_AS_DOUBLE (PySequence_Fast_GET_ITEM (o, 3));
#     Py_DECREF (o);
#     o = PyObject_GetAttrString (sprite, "width");
#     w = (GLfloat) PyFloat_AS_DOUBLE (o);
#     Py_DECREF (o);
#     o = PyObject_GetAttrString (sprite, "height");
#     h = (GLfloat) PyFloat_AS_DOUBLE (o);
#     Py_DECREF (o);
#     /* draw */
#     glColor4fv (color);
#     glTexCoord2f (0.0, 0.0); 
#     glVertex3f (position[0] - w, position[1] - h, position[2]);
#     glTexCoord2f (1.0, 0.0); 
#     glVertex3f (position[0] + w, position[1] - h, position[2]);
#     glTexCoord2f (1.0, 1.0); 
#     glVertex3f (position[0] + w, position[1] + h, position[2]);
#     glTexCoord2f (0.0, 1.0); 
#     glVertex3f (position[0] - w, position[1] + h, position[2]);
#   }
#   glEnd ();
#   glEnable (GL_CULL_FACE);
#   Py_INCREF (Py_None);
#   return Py_None;
# }

# PyObject* PyP3SpritesSphere_RenderOld (PyObject* module, PyObject* args) {
# //  P3_parent* coordsys;
#   P3_coordsys* root;
# //  P3_parent* root;
#   PyObject* sprites;
#   PyObject* sprite;
#   PyObject* old;
#   PyObject* o;
#   GLfloat position[3];
#   GLfloat color[4];
#   GLfloat w; GLfloat h; /* size of the sprite */
#   int i; int j;
#   int nb; int nb2;
#   GLfloat a_incr;
# // TO DO
#   /* args : (coordsys, [particules], alpha cste) */
# //  coordsys = (P3_parent*) PySequence_Fast_GET_ITEM (args, 0);
#   sprites = PySequence_Fast_GET_ITEM (args, 1);
#   nb = PySequence_Size (sprites);
# //  root = (P3_parent*) P3_coordsys_get_root ((P3_coordsys*) coordsys);
#   root = (P3_coordsys*) renderer->c_camera;
#   a_incr = (GLfloat) PyFloat_AS_DOUBLE (PySequence_Fast_GET_ITEM (args, 2));
#   glLoadIdentity ();
# //  glLoadMatrixf (root->render_matrix);
#   glDisable (GL_CULL_FACE);
#   /* use default activated material :) */
#   glBegin (GL_QUADS);
#   for (i = 0; i < nb; i++) {
#     sprite = PySequence_Fast_GET_ITEM (sprites, i);
#     o = PyObject_GetAttrString (sprite, "color");
#     color[0] = (GLfloat) PyFloat_AS_DOUBLE (PySequence_Fast_GET_ITEM (o, 0));
#     color[1] = (GLfloat) PyFloat_AS_DOUBLE (PySequence_Fast_GET_ITEM (o, 1));
#     color[2] = (GLfloat) PyFloat_AS_DOUBLE (PySequence_Fast_GET_ITEM (o, 2));
#     color[3] = (GLfloat) PyFloat_AS_DOUBLE (PySequence_Fast_GET_ITEM (o, 3));
#     Py_DECREF (o);
#     o = PyObject_GetAttrString (sprite, "width");
#     w = (GLfloat) PyFloat_AS_DOUBLE (o);
#     Py_DECREF (o);
#     o = PyObject_GetAttrString (sprite, "height");
#     h = (GLfloat) PyFloat_AS_DOUBLE (o);
#     Py_DECREF (o);
#     old = PyObject_GetAttrString (sprite, "old_pos");
#     nb2 = PySequence_Size (old);
#     for (j = 0; j < nb2; j++) {
#       sprite = PySequence_Fast_GET_ITEM (old, j);
#       o = PyObject_GetAttrString (sprite, "x");
#       position[0] = (GLfloat) PyFloat_AS_DOUBLE (o);
#       Py_DECREF (o);
#       o = PyObject_GetAttrString (sprite, "y");
#       position[1] = (GLfloat) PyFloat_AS_DOUBLE (o);
#       Py_DECREF (o);
#       o = PyObject_GetAttrString (sprite, "z");
#       position[2] = (GLfloat) PyFloat_AS_DOUBLE (o);
#       Py_DECREF (o);
#       o = PyObject_GetAttrString (sprite, "parent");
#       if (o != (PyObject*) root) {
#         if (o != Py_None) {
#           P3_point_by_matrix (position, P3_coordsys_get_root_matrix ((P3_coordsys*) o));
#         }
#         P3_point_by_matrix (position, P3_coordsys_get_inverted_root_matrix ((P3_coordsys*) root));
#       }
#       Py_DECREF (o);
#       glColor4fv (color);
#       glTexCoord2f (0.0, 0.0); 
#       glVertex3f (position[0] - w, position[1] - h, position[2]);
#       glTexCoord2f (1.0, 0.0); 
#       glVertex3f (position[0] + w, position[1] - h, position[2]);
#       glTexCoord2f (1.0, 1.0); 
#       glVertex3f (position[0] + w, position[1] + h, position[2]);
#       glTexCoord2f (0.0, 1.0); 
#       glVertex3f (position[0] - w, position[1] + h, position[2]);
#       color[3] += a_incr;
#     }
#     Py_DECREF (old);
#   }
#   glEnd ();
#   glEnable (GL_CULL_FACE);
#   Py_INCREF (Py_None);
#   return Py_None;
# }

# /*=========================*
#  * FADING COLORS FUNCTIONS *
#  *=========================*/

# PyObject* PyP3FadingColor_Advance (PyObject* module, PyObject* self) {
#   GLfloat life;
#   GLfloat max_life;
#   PyObject* colors;
#   PyObject* color;
#   PyObject* o;
#   colors = PyObject_GetAttrString (self, "colors");
#   color = PyObject_GetAttrString (self, "color");
#   o = PyObject_GetAttrString (self, "life");
#   life = (GLfloat) PyFloat_AS_DOUBLE (o);
#   Py_DECREF (o);
#   o = PyObject_GetAttrString (self, "max_life");
#   max_life = (GLfloat) PyFloat_AS_DOUBLE (o);
#   Py_DECREF (o);
#   /* maximum life of a particle is assumed to be 1.0 */
#   if (life <= 0.0) {
#     int i;
#     o = PySequence_Fast_GET_ITEM (colors, PySequence_Size (colors) - 1);
#     for (i = 0; i < 4; i++) {
#       PySequence_SetItem (color, i, PySequence_Fast_GET_ITEM (o, i));
#     }
#   } else if (life >= max_life) {
#     int i;
#     o = PySequence_Fast_GET_ITEM (colors, 0);
#     for (i = 0; i < 4; i++) {
#       PySequence_SetItem (color, i, PySequence_Fast_GET_ITEM (o, i));
#     }
#   } else {
#     int nb = PySequence_Size (colors);
#     int index;
#     int i;
#     GLfloat f1; GLfloat f2;
#     GLfloat c1[4]; GLfloat c2[4];
#     life = (1.0 - (life / max_life)) * (nb - 1);
#     index = (int) life;
#     o = PySequence_Fast_GET_ITEM (colors, index);
#     for (i = 0; i < 4; i++) {
#       c1[i] = (GLfloat) PyFloat_AS_DOUBLE (PySequence_Fast_GET_ITEM (o, i));
#     }
#     o = PySequence_Fast_GET_ITEM (colors, index + 1);
#     for (i = 0; i < 4; i++) {
#       c2[i] = (GLfloat) PyFloat_AS_DOUBLE (PySequence_Fast_GET_ITEM (o, i));
#     }
#     f2 = life - index;
#     f1 = 1.0 - f2;
#     o = PyFloat_FromDouble ((double) c1[0] * f1 + c2[0] * f2);
#     PySequence_SetItem (color, 0, o);
#     Py_DECREF (o);
#     o = PyFloat_FromDouble ((double) c1[1] * f1 + c2[1] * f2);
#     PySequence_SetItem (color, 1, o);
#     Py_DECREF (o);
#     o = PyFloat_FromDouble ((double) c1[2] * f1 + c2[2] * f2);
#     PySequence_SetItem (color, 2, o);
#     Py_DECREF (o);
#     o = PyFloat_FromDouble ((double) c1[3] * f1 + c2[3] * f2);
#     PySequence_SetItem (color, 3, o);
#     Py_DECREF (o);
#   }
#   Py_DECREF (colors);
#   Py_DECREF (color);
#   Py_INCREF (Py_None);
#   return Py_None;
# }

# /*========================*
#  * ACCELERATION FUNCTIONS *
#  *========================*/

# PyObject* PyP3Acceleration_Advance (PyObject* module, PyObject* args) {
#   PyObject* self;
#   PyObject* acc;
#   PyObject* speed;
#   PyObject* o;
#   double d;
#   double sx; double sy; double sz;
#   double proportion;
#   /* args are: (self, acceleration, proportion) */
#   self = PySequence_Fast_GET_ITEM (args, 0);
#   acc  = PySequence_Fast_GET_ITEM (args, 1);
#   proportion = PyFloat_AS_DOUBLE (PySequence_Fast_GET_ITEM (args, 2));
#   speed = PyObject_GetAttrString (self, "speed");
#   o = PyObject_GetAttrString (speed, "x");
#   d = PyFloat_AS_DOUBLE (o);
#   Py_DECREF (o);
#   sx = d + PyFloat_AS_DOUBLE (PySequence_Fast_GET_ITEM (acc, 0));
#   o = PyFloat_FromDouble (sx);
#   PyObject_SetAttrString (speed, "x", o);
#   Py_DECREF (o);
#   o = PyObject_GetAttrString (speed, "y");
#   d = PyFloat_AS_DOUBLE (o);
#   Py_DECREF (o);
#   sy = d + PyFloat_AS_DOUBLE (PySequence_Fast_GET_ITEM (acc, 1));
#   o = PyFloat_FromDouble (sy);
#   PyObject_SetAttrString (speed, "y", o);
#   Py_DECREF (o);
#   o = PyObject_GetAttrString (speed, "z");
#   d = PyFloat_AS_DOUBLE (o);
#   Py_DECREF (o);
#   sz = d + PyFloat_AS_DOUBLE (PySequence_Fast_GET_ITEM (acc, 2));
#   o = PyFloat_FromDouble (sz);
#   PyObject_SetAttrString (speed, "z", o);
#   Py_DECREF (o);
#   Py_DECREF (speed);
#   o = PyObject_GetAttrString (self, "x");
#   d = PyFloat_AS_DOUBLE (o);
#   Py_DECREF (o);
#   o = PyFloat_FromDouble (d + sx * proportion);
#   PyObject_SetAttrString (self, "x", o);
#   Py_DECREF (o);
#   o = PyObject_GetAttrString (self, "y");
#   d = PyFloat_AS_DOUBLE (o);
#   Py_DECREF (o);
#   o = PyFloat_FromDouble (d + sy * proportion);
#   PyObject_SetAttrString (self, "y", o);
#   Py_DECREF (o);
#   o = PyObject_GetAttrString (self, "z");
#   d = PyFloat_AS_DOUBLE (o);
#   Py_DECREF (o);
#   o = PyFloat_FromDouble (d + sz * proportion);
#   PyObject_SetAttrString (self, "z", o);
#   Py_DECREF (o);
#   Py_INCREF (Py_None);
#   return Py_None;
# }
