# 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

#ctypedef struct LandVertex:
#  float texcoord[2]
#  float normal  [3]
#  float coord   [3]
#  Pack* pack

cdef struct _LandTri

#cdef struct _LandPatch:
#  float     sphere[4]
#  char      level # precision level for rendering
#  _LandTri* tri_top
#  _LandTri* tri_left
#  _LandTri* tri_right
#  _LandTri* tri_bottom
#  int       visible
#ctypedef _LandPatch LandPatch

#cdef struct _LandTri:
#  char        level
#  float       normal[3]
#  float       sphere[4]
#  # 3 vertices that determine the triangle. turn right to left (CCW)
#  LandVertex* v1 # apex vertex
#  LandVertex* v2 # right vertex for the apex
#  LandVertex* v3 # left vertex for the apex
#  _LandTri*   parent
#  _LandTri*   left_child
#  _LandTri*   right_child
#  _LandTri*   left_neighbor
#  _LandTri*   right_neighbor
#  _LandTri*   base_neighbor
#  LandPatch*  patch
#ctypedef _LandTri LandTri

cdef void land_tri_dealloc(LandTri* tri):
  if land_tri_has_child(tri):
    land_tri_dealloc(tri.left_child)
    land_tri_dealloc(tri.right_child)
  free(tri)

cdef int land_tri_has_child(LandTri* tri):
  return tri.left_child != NULL

cdef int land_tri_diamond(LandTri* tri):
  return (tri.base_neighbor == NULL) or ((tri.v2 == tri.base_neighbor.v3) and (tri.v3 == tri.base_neighbor.v2))

cdef void land_tri_sphere(float r[4], float* p1, float* p2, float* p3):
  cdef float x, y, z, f, d
  # fast function to compute a bounding sphere for 3 points 
  # (faster but rougher than sphere_from_points())
  f = 1.0 / 3.0
  r[0] = (p1[0] + p2[0] + p3[0]) * f
  r[1] = (p1[1] + p2[1] + p3[1]) * f
  r[2] = (p1[2] + p2[2] + p3[2]) * f
  x = p1[0] - r[0]
  y = p1[1] - r[1]
  z = p1[2] - r[2]
  d = x * x + y * y + z * z
  x = p2[0] - r[0]
  y = p2[1] - r[1]
  z = p2[2] - r[2]
  f = x * x + y * y + z * z
  if f > d: d = f
  x = p3[0] - r[0]
  y = p3[1] - r[1]
  z = p3[2] - r[2]
  f = x * x + y * y + z * z
  if f > d: d = f
  r[3] = sqrt(d)


cdef LandTri* land_get_tri():
  # XXX try to re-use LandTri structures
  return <LandTri*> malloc(sizeof(LandTri))

cdef void land_tri_drop(LandTri* tri):
  # XXX try to re-use LandTri structures
  free(tri)
  
cdef LandTri* land_create_tri(LandVertex* v1, LandVertex* v2, LandVertex* v3, LandPatch* patch):
  cdef LandTri* tri
  tri               = land_get_tri()
  tri.v1            = v1
  tri.v2            = v2
  tri.v3            = v3
  tri.patch         = patch
  tri.level         = 0
  tri.parent        = NULL
  tri.left_child    = NULL
  tri.right_child   = NULL
  tri.base_neighbor = NULL
  land_tri_sphere (tri.sphere, v1.coord, v2.coord, v3.coord)
  face_normal     (tri.normal, v1.coord, v2.coord, v3.coord)
  vector_normalize(tri.normal)
  return tri

cdef LandTri* land_create_child_tri(LandVertex* v1, LandVertex* v2, LandVertex* v3, LandTri* parent):
  cdef LandTri* tri
  tri             = land_get_tri()
  tri.v1          = v1
  tri.v2          = v2
  tri.v3          = v3
  tri.parent      = parent
  tri.level       = parent.level + 1
  tri.patch       = parent.patch
  tri.left_child  = NULL
  tri.right_child = NULL
  land_tri_sphere (tri.sphere, v1.coord, v2.coord, v3.coord)
  face_normal     (tri.normal, v1.coord, v2.coord, v3.coord)
  vector_normalize(tri.normal)
  return tri

cdef void land_tri_update_neighbor_after_split (LandTri* tri):
  tri.left_child.left_neighbor    = tri.right_child
  if (tri.left_neighbor != NULL) and land_tri_has_child(tri.left_neighbor):
    tri.left_child.base_neighbor  = tri.left_neighbor.right_child
  else:
    tri.left_child.base_neighbor  = tri.left_neighbor
    
  tri.right_child.right_neighbor  = tri.left_child
  if (tri.right_neighbor != NULL) and land_tri_has_child(tri.right_neighbor):
    tri.right_child.base_neighbor = tri.right_neighbor.left_child
  else:
    tri.right_child.base_neighbor = tri.right_neighbor
  
  if tri.base_neighbor == NULL:
    tri.left_child.right_neighbor = NULL
    tri.right_child.left_neighbor = NULL
  else:
    tri.left_child.right_neighbor = tri.base_neighbor.right_child
    tri.right_child.left_neighbor = tri.base_neighbor.left_child
    
  if tri.left_neighbor != NULL:
    if tri.v1 == tri.left_neighbor.v1:
      tri.left_neighbor.right_neighbor = tri.left_child
    else:
      tri.left_neighbor.base_neighbor = tri.left_child
      if tri.left_neighbor.parent != NULL:
        tri.left_neighbor.parent.right_neighbor = tri.left_child
        
  if tri.right_neighbor != NULL:
    if tri.v1 == tri.right_neighbor.v1:
      tri.right_neighbor.left_neighbor = tri.right_child
    else:
      tri.right_neighbor.base_neighbor = tri.right_child
      if tri.right_neighbor.parent != NULL:
        tri.right_neighbor.parent.left_neighbor = tri.right_child
        
cdef void land_tri_force_split(LandTri* tri, LandVertex* apex):
  tri.left_child  = land_create_child_tri(apex, tri.v1, tri.v2, tri)
  tri.right_child = land_create_child_tri(apex, tri.v3, tri.v1, tri)
  land_tri_update_neighbor_after_split(tri)
  
cdef void land_tri_update_neighbor_after_merge (LandTri* tri):
  if tri.left_neighbor != NULL:
    if tri.left_neighbor.v1 == tri.v1:
      tri.left_neighbor.right_neighbor = tri
      if land_tri_has_child(tri.left_neighbor): tri.left_neighbor.right_child.base_neighbor = tri
    else:
      tri.left_neighbor.base_neighbor = tri
      if tri.left_neighbor.parent != NULL:
        tri.left_neighbor.parent.right_neighbor = tri
  if tri.right_neighbor != NULL:
    if tri.right_neighbor.v1 == tri.v1:
      tri.right_neighbor.left_neighbor = tri
      if land_tri_has_child(tri.right_neighbor):
        tri.right_neighbor.left_child.base_neighbor = tri
    else:
      tri.right_neighbor.base_neighbor = tri
      if tri.right_neighbor.parent != NULL:
        tri.right_neighbor.parent.left_neighbor = tri

cdef void land_get_height_at_factors(LandVertex* v1, LandVertex* v2, LandVertex* v3, float x, float z, float* k, float* t):
  cdef float  u[2], w[2], q
  cdef float* ptr
  ptr  = v1.coord
  u[0] = v2.coord[0] - ptr[0]
  u[1] = v2.coord[2] - ptr[2]
  w[0] = v3.coord[0] - ptr[0]
  w[1] = v3.coord[2] - ptr[2]
  q    = 1.0 / (w[0] * u[1] - w[1] * u[0])
  t[0] = (  (x - ptr[0]) * u[1] - (z - ptr[2]) * u[0]) * q
  k[0] = (- (x - ptr[0]) * w[1] + (z - ptr[2]) * w[0]) * q


cdef void land_drawColor_radeon(float* vect):
  glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, vect)
  
cdef void noop():
  pass

cdef void land_disableColor_radeon():
  #glColor4fv(white) # XXX really needed ?
  glDisable(GL_COLOR_MATERIAL)
  glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, white)
  
cdef void land_enableColor_radeon():
  glEnable(GL_COLOR_MATERIAL)

#ctypedef void (*land_drawColor_FUNC   )(float*)
#ctypedef void (*land_disableColor_FUNC)()
#ctypedef void (*land_enableColor_FUNC )()

cdef land_drawColor_FUNC    land_drawColor
cdef land_disableColor_FUNC land_disableColor
cdef land_enableColor_FUNC  land_enableColor

land_drawColor    = glColor4fv
land_disableColor = noop
land_enableColor  = noop


# XXX inline this
cdef void SET_COLOR_OPAQUE(_Land self, int index):
  if self._colors == NULL: land_drawColor(renderer.current_material._diffuse)
  else:                    land_drawColor(self._colors + index)

# XXX inline this
cdef void SET_COLOR_ALPHA(_Land self, int index):
  cdef float  ccc[4]
  cdef float* color
  if self._colors == NULL: color = renderer.current_material._diffuse
  else:                    color = self._colors + index
  ccc[0] = color[0]
  ccc[1] = color[1]
  ccc[2] = color[2]
  ccc[3] = 0.0
  land_drawColor(ccc)


cdef class _Land(CoordSyst):
  #cdef             _materials
  #cdef LandVertex* _vertices
  #cdef char*       _vertex_options
  #cdef int*        _vertex_colors
  #cdef float*      _normals # full LOD triangles normals
  #cdef int         _nb_colors
  #cdef float*      _colors # vertices colors
  #cdef int         _nb_vertex_width # size_width and size_depth must be (2^n) + 1
  #cdef int         _nb_vertex_depth # or I don't know what happen (crash?)
  #cdef int         _patch_size
  #cdef int         _max_level
  #cdef float       _texture_factor # a factor that multiply the texture coordinates
  #cdef float       _scale_factor # a factor to decide when the triangle must split (higher value means more accuracy)
  #cdef float       _split_factor
  #cdef int         _nb_patchs
  #cdef int         _nb_patch_width
  #cdef int         _nb_patch_depth
  #cdef LandPatch*  _patchs

  property width:
    def __get__(self):
      return self._nb_vertex_width
  property depth:
    def __get__(self):
      return self._nb_vertex_depth
    
  property patch_size:
    def __get__(self):
      return self._patch_size
    def __set__(self, x):
      self._patch_size = x
      if self._option & LAND_INITED: self._option = self._option - LAND_INITED
      
  property has_vertex_options:
    def __get__(self):
      return self._option & LAND_VERTEX_OPTIONS
    
  property split_factor:
    def __get__(self):
      return self._split_factor
    def __set__(self, x):
      self._split_factor = x
    
  property scale_factor:
    def __get__(self):
      return self._scale_factor
    def __set__(self, x):
      self._scale_factor = x
      self._option = self._option & ~LAND_INITED
      self._compute_coords()
      
  property texture_factor:
    def __get__(self):
      return self._texture_factor
    def __set__(self, x):
      self._texture_factor = x
      self._compute_coords()
      
  property raypick_with_LOD:
    def __get__(self):
      return self._option & LAND_REAL_LOD_RAYPICK
    def __set__(self, x):
      if x: self._option = self._option |  LAND_REAL_LOD_RAYPICK
      else: self._option = self._option & ~LAND_REAL_LOD_RAYPICK
      
  cdef LandVertex* _get_vertex(self, int x, int z):
    # XXX inline / macroize (how ?) this ?
    return self._vertices + (x + z * self._nb_vertex_width)
  
  cdef void _check_size(self):
    cdef int i, d
    i = 0
    while 1:
      d = (1 << i) + 1
      if self._nb_vertex_width <= d:
        if self._nb_vertex_width < d: print "WARNING Land size width must be (2^n) + 1"
        break
      i = i + 1
      
    i = 0
    while 1:
      d = (1 << i) + 1
      if self._nb_vertex_depth <= d:
        if self._nb_vertex_depth < d: print "WARNING Land size depth must be (2^n) + 1"
        break
      i = i + 1
      
  #def __new__(self, int width, int depth):
    
  def __init__(self, _World parent = None, int width = 0, int depth = 0, float scale_factor = 1.0):
    cdef LandVertex* v
    cdef int         i
    cdef int         nb
    
    CoordSyst.__init__(self, parent)
    if depth == 0: depth = width
    
    self._nb_vertex_width = width
    self._nb_vertex_depth = depth
    self._patch_size      = 8
    self._texture_factor  = 1.0
    self._scale_factor    = scale_factor
    self._split_factor    = 2.0
    self._materials       = [_DEFAULT_MATERIAL]
    if (width != 0) and (depth != 0):
      nb = self._nb_vertex_width * self._nb_vertex_depth
      self._check_size()
      self._vertices = <LandVertex*> malloc(nb * sizeof(LandVertex))
      for i from 0 <= i < nb:
        v = self._vertices + i
        v.coord[1] = 0
        v.pack = _DEFAULT_MATERIAL._pack(FACE_TRIANGLE)
      self._normals = <float*> malloc((self._nb_vertex_width - 1) * (self._nb_vertex_depth - 1) * 6 * sizeof(float))
    self._compute_coords()
    
    
  cdef void _free_patchs(self):
    cdef LandPatch* patch
    cdef int        i
    for i from 0 <= i < self._nb_patchs:
      patch = self._patchs + i
      land_tri_dealloc(patch.tri_top)
      land_tri_dealloc(patch.tri_left)
      land_tri_dealloc(patch.tri_right)
      land_tri_dealloc(patch.tri_bottom)
    free(self._patchs)
    self._patchs = NULL
    
    
  cdef void __del__(self):
    self._free_patchs()
    self._materials.__imul__(0)
    free(self._vertices)
    free(self._colors)
    free(self._normals)
    free(self._vertex_options)
    free(self._vertex_colors)
    
  def from_image(self, _Image image):
    cdef GLubyte* ptr
    cdef int nb, i
    self._nb_vertex_width = image.width
    self._nb_vertex_depth = image.height
    self._check_size()
    if self._vertices != NULL: free(self._vertices)
    nb = self._nb_vertex_width * self._nb_vertex_depth
    self._vertices = <LandVertex*> malloc(nb * sizeof(LandVertex))
    for i from 0 <= i < nb: (self._vertices + i).pack = _DEFAULT_MATERIAL._pack(FACE_TRIANGLE)
    self._normals = <float*> malloc((self._nb_vertex_width - 1) * (self._nb_vertex_depth - 1) * 6 * sizeof(float))
    if   image.nb_color == 3:
      for i from 0 <= i < nb:
        ptr = image._pixels + i * 3
        (self._vertices + i).coord[1] = (ptr[0] + ptr[1] + ptr[2]) / (3.0 * 255.0)
    elif image.nb_color == 4:
      for i from 0 <= i < nb:
        ptr = image._pixels + i * 4
        (self._vertices + i).coord[1] = (ptr[0] + ptr[1] + ptr[2] + ptr[3]) / (4.0 * 255.0)
    elif image.nb_color == 1:
      for i from 0 <= i < nb:
        (self._vertices + i).coord[1] = image._pixels[i] / 255.0
        
    self._option = self._option & ~LAND_INITED
    self._compute_coords()
    
  cdef void _add_material(self, _Material material):
    if material in self._materials: return
    self._materials.append(material)
    self._option = self._option & ~LAND_INITED
    
  def set_material_layer(self, _Material material, float start, float end):
    cdef int         i, j
    cdef LandVertex* vertex
    cdef Pack*       pack
    self._add_material(material)
    pack = material._pack(FACE_TRIANGLE)
    for j from 0 <= j < self._nb_vertex_depth:
      for i from 0 <= i < self._nb_vertex_width:
        vertex = self._get_vertex(i, j)
        if (vertex.coord[1] >= start) and (vertex.coord[1] <= end): vertex.pack = pack
    self._option = self._option & ~LAND_INITED
    
  def set_material_layer_angle(self, _Material material, float start, float end, float angle_min, float angle_max):
    cdef int         i, j
    cdef LandVertex* vertex
    cdef Pack*       pack
    cdef float       f, v[3]
    v[0] = 0.0; v[1] = 1.0; v[2] = 0.0 # { 0.0, 1.0, 0.0 }
    # ensure the normals are computed
    if not(self._option & LAND_INITED): self._init()
    # angle 0 is horizontal
    self._add_material(material)
    pack = material._pack(FACE_TRIANGLE)
    for j from 0 <= j < self._nb_vertex_depth:
      for i from 0 <= i < self._nb_vertex_width:
        vertex = self._get_vertex(i, j)
        f = fabs(to_degrees(vector_angle(vertex.normal, v)))
        if (vertex.coord[1] >= start) and (vertex.coord[1] <= end) and (angle_min <= f <= angle_max): vertex.pack = pack
    self._option = self._option & ~LAND_INITED
    
    
  def get_normal(self, int x, int z):
    cdef LandVertex* vertex
    vertex = self._get_vertex(x, z)
    return vertex.normal[0], vertex.normal[1], vertex.normal[2]
  
  def get_height(self, int x, int z):
    return self._get_height(x, z)
  
  cdef float _get_height(self, int x, int z):
    if (x < 0) or (z < 0) or (x >= self._nb_vertex_width) or (z >= self._nb_vertex_depth): return 0.0
    return self._get_vertex(x, z).coord[1]
  
  def get_material(self, int x, int z):
    if (x < 0) or (z < 0) or (x >= self._nb_vertex_width) or (z >= self._nb_vertex_depth): return None
    return <_Material> (self._get_vertex(x, z).pack.material_id)
  
  cdef void _set_height (self, int x, int z, float height):
    if (x < 0) or (z < 0) or (x >= self._nb_vertex_width) or (z >= self._nb_vertex_depth): return
    self._get_vertex(x, z).coord[1] = height

  def set_height(self, int x, int z, float height):
    if (x < 0) or (z < 0) or (x >= self._nb_vertex_width) or (z >= self._nb_vertex_depth): return
    self._get_vertex(x, z).coord[1] = height
    
  def multiply_height(self, float factor):
    cdef int         i, j
    cdef LandVertex* vertex
    for j from 0 <= j < self._nb_vertex_depth:
      for i from 0 <= i < self._nb_vertex_width:
        vertex = self._get_vertex(i, j)
        vertex.coord[1] = vertex.coord[1] * factor
        
  def add_height(self, float height):
    cdef LandVertex* vertex
    cdef int         i, j
    for j from 0 <= j < self._nb_vertex_depth:
      for i from 0 <= i < self._nb_vertex_width:
        vertex = self._get_vertex(i, j)
        vertex.coord[1] = vertex.coord[1] + height

  def set_material(self, int x, int z, _Material material):
    cdef Pack* pack
    self._add_material(material)
    pack = material._pack(FACE_TRIANGLE)
    if (0 <= x < self._nb_vertex_width) and (0 <= z < self._nb_vertex_depth):
      self._get_vertex(x, z).pack = pack
      
  def remove_colors(self):
    """Land.remove_colors()

Removes all colors, from all vertices that have colors."""
    free(self._colors)
    free(self._vertex_colors)
    self._nb_colors        = 0
    self._colors           = NULL
    self._vertex_colors    = NULL
    self._option = self._option & (~LAND_COLORED)
    
  def get_vertex_option(self, int x, int z):
    """Land.get_vertex_option(X, Z) => (HIDDEN, NON_SOLID, FORCE_PRESENCE)

Returns the vertex options associated with the vertex of coordinates X, Z, as a 4-int tuple.
See Land.set_vertex_option for the available options."""
    cdef int option
    self._check_vertex_options()
    option = self._vertex_options[x + z * self._nb_vertex_width]
    return option & LAND_VERTEX_HIDDEN, option & LAND_VERTEX_NON_SOLID, option & LAND_VERTEX_FORCE_PRESENCE
  
  def set_vertex_option(self, int x, int z, int hidden = 0, int non_solid = 0, int force_presence = 0):
    """Land.set_vertex_option(X, Z, HIDDEN, NON_SOLID, FORCE_PRESENCE)

Sets the vertex options associated with the vertex of coordinates X, Z.
These options are usefull to create hole in the landscape, or non-rectangular landscape.

Options are (all option are boolean, and default is 0 for all):
  - Hidden : hide the vertex (a triangle whose 3 vertices hidden is not drawn)
  - Non-solid : the vertex is non-solid (raypicking is disabled on a triangle whose 3 vertices are non-solid).
  - ForcePresence : the vertex is ALWAYS present (else, the LOD system may remove some vertex temporarily).
"""
    cdef int index
    index = x + z * self._nb_vertex_width
    self._check_vertex_options()
    self._vertex_options[index] = (self._vertex_options[index] & ~(LAND_VERTEX_HIDDEN | LAND_VERTEX_NON_SOLID | LAND_VERTEX_FORCE_PRESENCE)) | (hidden * LAND_VERTEX_HIDDEN) | (non_solid * LAND_VERTEX_NON_SOLID) | (force_presence * LAND_VERTEX_FORCE_PRESENCE)
    
  def set_vertex_color(self, int x, int z, color):
    cdef float  ccolor[4]
    cdef int    n
    n = x + z * self._nb_vertex_width
    ccolor[0] = color[0]
    ccolor[1] = color[1]
    ccolor[2] = color[2]
    ccolor[3] = color[3]
    self._vertex_colors[n] = self._check_color(ccolor)
    if   1.0 - ccolor[3] > EPSILON:          self._vertex_options[n] = self._vertex_options[n] |  LAND_VERTEX_ALPHA
    elif self._option & LAND_VERTEX_OPTIONS: self._vertex_options[n] = self._vertex_options[n] & ~LAND_VERTEX_ALPHA
    if   ccolor[3] < EPSILON:                self._vertex_options[n] = self._vertex_options[n] |  LAND_VERTEX_HIDDEN
    elif self._option & LAND_VERTEX_OPTIONS: self._vertex_options[n] = self._vertex_options[n] & ~LAND_VERTEX_HIDDEN
    
  def reinit(self):
    """Land.reinit()

Re-inits the landscape.
You MUST call this method after the landscape have been modified manually
(e.g. with Land.set_height)."""
    if self._option & LAND_INITED: self._option = self._option - LAND_INITED
    
    
  cdef void _init(self):
    if self._patchs != NULL: self._free_patchs()
    self._compute_normals()
    self._create_patchs()
    if self._option & LAND_VERTEX_OPTIONS: self._force_presence()
    self._option = self._option | LAND_INITED
    
  cdef void _compute_normal(self, int x, int y): # compute vertex normal
    cdef float y00, y01, y10, y11, y12, y21, y22, y02, y20, a, b, c
    cdef LandVertex* vertex
    vertex = self._get_vertex(x, y)
    y11 = self._get_height(x, y)
    
    y00 = (self._get_height(x - 1, y - 1) - y11)
    y01 = (self._get_height(x - 1, y    ) - y11)
    y10 = (self._get_height(x    , y - 1) - y11)
    y12 = (self._get_height(x    , y + 1) - y11)
    y21 = (self._get_height(x + 1, y    ) - y11)
    y22 = (self._get_height(x + 1, y + 1) - y11)
    y02 = (self._get_height(x - 1, y + 1) - y11)
    y20 = (self._get_height(x + 1, y - 1) - y11)
    
    a = (-y00 - 2.0 * y01 - y02 + y20 + 2.0 * y21 + y22) / self._scale_factor
    b = (-y00 - 2.0 * y10 - y20 + y02 + 2.0 * y12 + y22) / self._scale_factor
    c = 8.0
    vertex.normal[0] = - a
    vertex.normal[1] =   c
    vertex.normal[2] = - b
    vector_normalize(vertex.normal)
    
  cdef void _compute_normals(self):
    cdef int         i, j
    cdef float*      ptr
    cdef LandVertex* v1, *v2, *v3, *v4
    # compute points normals
    for j from 0 <= j < self._nb_vertex_depth:
      for i from 0 <= i < self._nb_vertex_width:
        self._compute_normal(i, j)
    # compute triangles normals
    ptr = self._normals
    for j from 0 <= j < self._nb_vertex_depth - 1:
      for i from 0 <= i < self._nb_vertex_width - 1:
        v1 = self._get_vertex(i    , j    )
        v2 = self._get_vertex(i + 1, j    )
        v3 = self._get_vertex(i + 1, j + 1)
        v4 = self._get_vertex(i    , j + 1)
        if ((i & 1) and (j & 1)) or ((not(i & 1)) and (not(j & 1))):
          face_normal(ptr,     v4.coord, v3.coord, v1.coord); vector_normalize(ptr)
          face_normal(ptr + 3, v2.coord, v1.coord, v3.coord); vector_normalize(ptr + 3)
        else:
          face_normal(ptr,     v1.coord, v4.coord, v2.coord); vector_normalize(ptr)
          face_normal(ptr + 3, v3.coord, v2.coord, v4.coord); vector_normalize(ptr + 3)
        ptr = ptr + 6
        
  cdef void _compute_coords(self):
    cdef int         i, j, k
    cdef LandVertex* vertex
    k = 0
    for i from 0 <= i < self._nb_vertex_depth:
      for j from 0 <= j < self._nb_vertex_width:
        vertex = self._vertices + k
        vertex.coord[0]    = (<float> j) * self._scale_factor
        vertex.coord[2]    = (<float> i) * self._scale_factor
        vertex.texcoord[0] = (<float> j) * self._texture_factor
        vertex.texcoord[1] = (<float> i) * self._texture_factor
        k = k + 1
        
  cdef void _create_patch(self, LandPatch* patch, int x, int z, int patch_size):
    cdef int         i, j, m, nb, x1, z1, x2, z2
    cdef float*      coords
    cdef LandVertex* vertex, *center, *v0, *v1, *v2, *v3
    patch.level = 0
    x1 =  x      * patch_size
    x2 = (x + 1) * patch_size
    z1 =  z      * patch_size
    z2 = (z + 1) * patch_size
    
    # compute bounding sphere
    nb     = (patch_size + 1) * (patch_size + 1)
    coords = <float*> malloc(nb * 3 * sizeof(float))
    m      = 0
    for i from x1 <= i <= x2:
      for j from z1 <= j <= z2:
        vertex = self._get_vertex(i, j)
        memcpy(coords + m, vertex.coord, 3 * sizeof(float))
        m = m + 3
    sphere_from_points(patch.sphere, coords, nb)
    free(coords)
    
    # create tris
    v0 = self._get_vertex(x1, z1)
    v1 = v0 + self._patch_size
    v2 = v0 + self._patch_size * self._nb_vertex_width
    v3 = v2 + self._patch_size
    center = v0 + ((v3 - v0) >> 1)
    patch.tri_top    = land_create_tri(center, v1, v0, patch)
    patch.tri_left   = land_create_tri(center, v0, v2, patch)
    patch.tri_right  = land_create_tri(center, v3, v1, patch)
    patch.tri_bottom = land_create_tri(center, v2, v3, patch)
    patch.tri_top.left_neighbor     = patch.tri_right
    patch.tri_top.right_neighbor    = patch.tri_left
    patch.tri_top.base_neighbor     = NULL
    patch.tri_left.left_neighbor    = patch.tri_top
    patch.tri_left.right_neighbor   = patch.tri_bottom
    patch.tri_left.base_neighbor    = NULL
    patch.tri_right.left_neighbor   = patch.tri_bottom
    patch.tri_right.right_neighbor  = patch.tri_top
    patch.tri_right.base_neighbor   = NULL
    patch.tri_bottom.left_neighbor  = patch.tri_left
    patch.tri_bottom.right_neighbor = patch.tri_right
    patch.tri_bottom.base_neighbor  = NULL
    
  cdef void _create_patchs(self):
    cdef int        i, j, k
    cdef LandPatch* m
    self._max_level = exp_of_2(self._patch_size) * 2 - 1
    # create patchs
    self._nb_patch_width = <int> ((self._nb_vertex_width - 1) / self._patch_size)
    self._nb_patch_depth = <int> ((self._nb_vertex_depth - 1) / self._patch_size)
    self._nb_patchs = self._nb_patch_width * self._nb_patch_depth
    if self._patchs != NULL: land_free_patchs(land)
    self._patchs = <LandPatch*> malloc(self._nb_patchs * sizeof(LandPatch))
    k = 0
    for j from 0 <= j < self._nb_patch_depth:
      for i from 0 <= i < self._nb_patch_width:
        self._create_patch(self._patchs + k, i, j, self._patch_size)
        k = k + 1
    # set neighbors
    for j from 0 <= j < self._nb_patch_depth:
      for i from 0 <= i < self._nb_patch_width:
        m = self._patchs + (i + j * self._nb_patch_width)
        if (i > 0                       ): m.tri_left  .base_neighbor = (self._patchs + (i - 1) +  j      * self._nb_patch_width).tri_right
        if (j > 0                       ): m.tri_top   .base_neighbor = (self._patchs +  i      + (j - 1) * self._nb_patch_width).tri_bottom
        if (i < self._nb_patch_width - 1): m.tri_right .base_neighbor = (self._patchs + (i + 1) +  j      * self._nb_patch_width).tri_left
        if (j < self._nb_patch_depth - 1): m.tri_bottom.base_neighbor = (self._patchs +  i      + (j + 1) * self._nb_patch_width).tri_top
        
  cdef int _register_color(self, float color[4]):
    cdef float* c
    cdef int    i, nb
    c = self._colors
    for i from 0 <= i < self._nb_colors:
      if ((fabs(color[0] - c[0]) < EPSILON) and
          (fabs(color[1] - c[1]) < EPSILON) and
          (fabs(color[2] - c[2]) < EPSILON) and
          (fabs(color[3] - c[3]) < EPSILON)): return i
      c = c + 4
    
    i = self._nb_colors * 4
    self._nb_colors = self._nb_colors + 1
    self._colors = <float*> realloc(self._colors, self._nb_colors * 4 * sizeof(float))
    memcpy(self._colors + i, color, 4 * sizeof(float))
    return i
  
  def compute_shadow_color(self, _Light light, shadow_color):
    cdef int    i, nb
    cdef float  color[4]
    cdef float  cshadow_color[4]
    cdef float* ocolor, *old_colors
    cdef int    scolor, wcolor
    nb = self._nb_vertex_width * self._nb_vertex_depth
    if not(self._option & LAND_INITED): self._init()
    
    cshadow_color[0] = shadow_color[0]
    cshadow_color[1] = shadow_color[1]
    cshadow_color[2] = shadow_color[2]
    cshadow_color[3] = shadow_color[3]
    
    # initialize vertex colors if needed
    old_colors = self._colors
    self._colors = NULL
    self._nb_colors = 0
    if not(self._option & LAND_COLORED):
      self._option = self._option | LAND_COLORED
      self._vertex_colors = <int*> malloc(nb * sizeof(int))
      for i from 0 <= i < nb: self._vertex_colors[i] = -1
      self._nb_colors = 2
      self._colors = <float*> malloc(8 * sizeof(float))
      wcolor = 0
      scolor = 4
      memcpy(self._colors + wcolor, white        , 4 * sizeof(float))
      memcpy(self._colors + scolor, cshadow_color, 4 * sizeof(float))
      
    # compute shadows
    for i from 0 <= i < nb:
      if light._shadow_at((self._vertices + i).coord):
        if self._vertex_colors[i] == -1: self._vertex_colors[i] = scolor
        else:
          ocolor = self._colors + self._vertex_colors[i]
          color[0] = ocolor[0] * cshadow_color[0]
          color[1] = ocolor[1] * cshadow_color[1]
          color[2] = ocolor[2] * cshadow_color[2]
          color[3] = ocolor[3] * cshadow_color[3]
          self._vertex_colors[i] = self._register_color(color)
      else:
        if self._vertex_colors[i] == -1: self._vertex_colors[i] = wcolor
        else:                            self._vertex_colors[i] = self._register_color(self._colors + self._vertex_colors[i])
    free(old_colors)


  cdef void _tri_split(self, LandTri* tri):
    cdef LandVertex* v
    if not land_tri_diamond(tri): self._tri_split(tri.base_neighbor)
    
    # hum that is a beautiful code to get the vertex that is the middle of v2-v3
    if tri.v2 < tri.v3: v = tri.v2 + ((tri.v3 - tri.v2) >> 1)
    else:               v = tri.v3 + ((tri.v2 - tri.v3) >> 1)
    tri.left_child  = land_create_child_tri(v, tri.v1, tri.v2, tri)
    tri.right_child = land_create_child_tri(v, tri.v3, tri.v1, tri)
    if tri.base_neighbor != NULL: land_tri_force_split(tri.base_neighbor, v)
    land_tri_update_neighbor_after_split(tri)
    
  cdef int _tri_merge_child(self, LandTri* tri):
    cdef LandTri* base
    # return FALSE if merge can't be done, else TRUE if the merge has been done
    base = tri.base_neighbor
    if ((tri.left_child.level <= tri.left_child.patch.level) or 
       ((base != NULL) and (base.left_child.level <= base.left_child.patch.level))): return 0

    if land_tri_has_child(tri.left_child ) and not self._tri_merge_child(tri.left_child ): return 0
    if land_tri_has_child(tri.right_child) and not self._tri_merge_child(tri.right_child): return 0
    if base != NULL:
      if land_tri_has_child(base.left_child ) and not self._tri_merge_child(base.left_child ): return 0
      if land_tri_has_child(base.right_child) and not self._tri_merge_child(base.right_child): return 0
      
    if (self._option & LAND_VERTEX_OPTIONS) and (self._vertex_options[tri.left_child.v1 - self._vertices] & LAND_VERTEX_FORCE_PRESENCE):
      return 0
    
    land_tri_update_neighbor_after_merge(tri)
    if base != NULL:
      land_tri_update_neighbor_after_merge(base)
      land_tri_drop(base.left_child)
      land_tri_drop(base.right_child)
      base.left_child = base.right_child = NULL
      
    land_tri_drop(tri.left_child )
    land_tri_drop(tri.right_child)
    tri.left_child = tri.right_child = NULL
    return 1
  
  cdef void _tri_set_level(self, LandTri* tri, char level):
    if land_tri_has_child(tri):
      self._tri_set_level(tri.left_child, level)
      if tri.left_child == NULL: self._tri_set_level(tri,             level) # this means we have merged the children
      else:                      self._tri_set_level(tri.right_child, level)
    else:
      if  (tri.level > level) and (tri.parent != NULL): self._tri_merge_child(tri.parent)
      elif tri.level < level:
        self._tri_split    (tri)
        self._tri_set_level(tri.left_child , level)
        self._tri_set_level(tri.right_child, level)
        
  cdef void _patch_set_level(self, LandPatch* patch, char level):
    if patch.level != level:
      patch.level = level
      self._tri_set_level(patch.tri_top   , level)
      self._tri_set_level(patch.tri_left  , level)
      self._tri_set_level(patch.tri_right , level)
      self._tri_set_level(patch.tri_bottom, level)
      
  cdef int _patch_update(self, LandPatch* patch, Frustum* frustum, float* frustum_box):
    cdef char   level
    cdef float  d, r
    cdef float* v1, *v2
    # update patch LOD
    v1 = patch.tri_top   .v3.coord
    v2 = patch.tri_bottom.v3.coord
    if (frustum_box[0] > v2[0]) or (frustum_box[2] < v1[0]) or (frustum_box[1] > v2[2]) or (frustum_box[3] < v1[2]) or (not sphere_in_frustum(frustum, patch.sphere)):
      self._patch_set_level(patch, 0)
      return 0
    else:
      d = point_distance_to(patch.sphere, frustum.position)
      r = patch.sphere[3] * self._split_factor
      if d <= r: self._patch_set_level(patch, self._max_level)
      else:
        level = <char> (self._max_level + 1 - <int> (d / r))
        if level < 0: level = 0
        self._patch_set_level(patch, level)
      return 1
    
  cdef void _tri_force_presence(self, LandTri* tri, LandVertex* v):
    cdef float u[2], w[2]
    cdef float k, m, f, x, z
    if (v == tri.v1) or (v == tri.v2) or (v == tri.v3): return
    if land_tri_has_child(tri):
      self._tri_force_presence(tri.left_child,  v)
      self._tri_force_presence(tri.right_child, v)
    else:
      u[0] = tri.v2.coord[0] - tri.v1.coord[0]
      u[1] = tri.v2.coord[2] - tri.v1.coord[2]
      w[0] = tri.v3.coord[0] - tri.v1.coord[0]
      w[1] = tri.v3.coord[2] - tri.v1.coord[2]
      x    = v.coord[0] - tri.v1.coord[0]
      z    = v.coord[2] - tri.v1.coord[2]
      f    = 1.0 / (u[0] * w[1] - u[1] * w[0])
      m    = (u[0] * z - u[1] * x) * f
      k    = (w[1] * x - w[0] * z) * f
      if (m >= 0.0) and (m <= 1.0) and (k >= 0.0) and (k <= 1.0) and (k + m <= 1.0):
        self._tri_split(tri)
        self._tri_force_presence(tri.left_child,  v)
        self._tri_force_presence(tri.right_child, v)
        
  cdef void _force_presence(self):
    cdef LandVertex* v
    cdef LandPatch*  patch
    cdef int         a, b, i, j
    for j from 0 <= j < self._nb_vertex_depth:
      for i from 0 <= i < self._nb_vertex_width:
        if self._vertex_options[i + j * self._nb_vertex_width] & LAND_VERTEX_FORCE_PRESENCE:
          a = <int> ((<float> i) / self._patch_size)
          b = <int> ((<float> j) / self._patch_size)
          if a >= self._nb_patch_width: a = self._nb_patch_width - 1
          if b >= self._nb_patch_depth: b = self._nb_patch_depth - 1
          patch = self._patchs + a + b * self._nb_patch_width
          v = self._get_vertex(i, j)
          self._tri_force_presence(patch.tri_top,    v)
          self._tri_force_presence(patch.tri_left,   v)
          self._tri_force_presence(patch.tri_right,  v)
          self._tri_force_presence(patch.tri_bottom, v)



  cdef void _tri_batch(self, LandTri* tri, Frustum* frustum):
    cdef Pack* p, *p1, *p2, *p3, *p4
    cdef int   o1, o2, o3
    if sphere_in_frustum(frustum, tri.sphere):
      if land_tri_has_child(tri): # recurse in children
        self._tri_batch(tri.left_child,  frustum)
        self._tri_batch(tri.right_child, frustum)
      else:
        if self._option & LAND_VERTEX_OPTIONS:
          o1 = self._vertex_options[tri.v1 - self._vertices]
          o2 = self._vertex_options[tri.v2 - self._vertices]
          o3 = self._vertex_options[tri.v3 - self._vertices]
          if ((o1 & LAND_VERTEX_HIDDEN) and
              (o2 & LAND_VERTEX_HIDDEN) and
              (o3 & LAND_VERTEX_HIDDEN)): return
          if (o1 & LAND_VERTEX_ALPHA) or (o2 & LAND_VERTEX_ALPHA) or (o3 & LAND_VERTEX_ALPHA):
            # draw everything in second pass
            p1 = pack_get_secondpass(tri.v1.pack)
            p2 = pack_get_secondpass(tri.v2.pack)
            p3 = pack_get_secondpass(tri.v3.pack)
            if p1 < p2: p = p1
            else:       p = p2
            if p3 < p : p = p3
            pack_batch_face(pack_get_secondpass(p), tri)
            if  p1 != p                               : pack_batch_face(p1, tri)
            if (p2 != p) and (p2 != p1)               : pack_batch_face(p2, tri)
            if (p3 != p) and (p3 != p1) and (p3 != p2): pack_batch_face(p3, tri)
            return
          
        p1 = tri.v1.pack
        p2 = tri.v2.pack
        p3 = tri.v3.pack
        # we must render without alpha the pack that is the most represented 
        # this constraint is needed for special texturing smoothing
        if   p1 == p2:
          pack_batch_face(p1, tri)
          if p3 != p1: pack_batch_face(pack_get_secondpass(p3), tri)
        elif p1 == p3:
          pack_batch_face(p1, tri)
          if p2 != p1: pack_batch_face(pack_get_secondpass(p2), tri)
        elif p2 == p3:
          pack_batch_face(p2, tri)
          if p1 != p2: pack_batch_face(pack_get_secondpass(p1), tri)
        else:
          # take in account the base neighbor if it's a diamond
          if (tri.base_neighbor != NULL) and (tri.v2 == tri.base_neighbor.v3) and (tri.v3 == tri.base_neighbor.v2):
            p4 = tri.base_neighbor.v1.pack
            if   p2 == p4:
              pack_batch_face(                    p2 , tri)
              pack_batch_face(pack_get_secondpass(p1), tri)
              pack_batch_face(pack_get_secondpass(p3), tri)
              return
            elif p3 == p4:
              pack_batch_face(                    p3 , tri)
              pack_batch_face(pack_get_secondpass(p1), tri)
              pack_batch_face(pack_get_secondpass(p2), tri)
              return
          pack_batch_face(                    p1 , tri)
          pack_batch_face(pack_get_secondpass(p2), tri)
          pack_batch_face(pack_get_secondpass(p3), tri)
          
  cdef void _patch_batch(self, LandPatch* patch, Frustum* frustum):
    if sphere_in_frustum(frustum, patch.sphere):
      self._tri_batch(patch.tri_top,    frustum)
      self._tri_batch(patch.tri_left,   frustum)
      self._tri_batch(patch.tri_right,  frustum)
      self._tri_batch(patch.tri_bottom, frustum)
      
  cdef void _batch(self, CoordSyst coordsyst):
    if self._option & HIDDEN: return
    
    if not (self._option & LAND_INITED): self._init()
    
    multiply_matrix(self._render_matrix, coordsyst._render_matrix, self._matrix)
    self._frustum_id = -1
    
    cdef Chunk*     chunk
    cdef LandPatch* patch
    cdef Frustum*   frustum
    cdef float*     ptr
    cdef float      frustum_box[4]
    cdef int        i
    
    # quadtree visibility computation
    frustum = renderer._frustum(self)
    ptr     = frustum.points
    frustum_box[0] = ptr[0]
    frustum_box[1] = ptr[2]
    frustum_box[2] = ptr[0]
    frustum_box[3] = ptr[2]
    i = 3
    while i < 24:
      if ptr[i] < frustum_box[0]: frustum_box[0] = ptr[i]
      if ptr[i] > frustum_box[2]: frustum_box[2] = ptr[i]
      i = i + 2
      if ptr[i] < frustum_box[1]: frustum_box[1] = ptr[i]
      if ptr[i] > frustum_box[3]: frustum_box[3] = ptr[i]
      i = i + 1
      
    patch = self._patchs
    for i from 0 <= i < self._nb_patchs:
      patch.visible = self._patch_update(patch, frustum, frustum_box)
      patch = patch + 1
      
    patch = self._patchs
    for i from 0 <= i < self._nb_patchs:
      if patch.visible: self._patch_batch(patch, frustum)
      patch = patch + 1
      
    pack_batch_end(self, self)
    
  # XXX We cannot use glColor with COLOR_MATERIAL
  # since some 3D drivers (namely, the free radeon 7500 driver) have a bugged
  # implementation of COLOR_MATERIAL.
  
  cdef void _vertex_render_special(self, LandVertex* vertex):
    cdef float  ccc[4]
    cdef float* color
    cdef int    index
    index = vertex - self._vertices
    color = self._colors + index
    
    if (self._option & LAND_VERTEX_OPTIONS) and (self._vertex_options[index] & LAND_VERTEX_ALPHA) and (not (<_Material> (vertex.pack.material_id)) is renderer.current_material):
      ccc[0] = color[0]
      ccc[1] = color[1]
      ccc[2] = color[2]
      ccc[3] = 0.0
      land_drawColor (ccc)
    else: land_drawColor(color)
    #glArrayElement(index)
    glTexCoord2fv(vertex.texcoord)
    glNormal3fv(vertex.normal)
    glVertex3fv(vertex.coord)
    
    
  cdef void _tri_render_middle(self, LandTri* tri):
    cdef float       ccc[4]
    cdef LandVertex* v
    cdef float*      color, *color2
    
    if self._colors == NULL:
      color = renderer.current_material._diffuse
      ccc[0] = color[0]
      ccc[1] = color[1]
      ccc[2] = color[2]
    else:
      color  = self._colors + (tri.v2 - self._vertices)
      color2 = self._colors + (tri.v3 - self._vertices)
      ccc[0] = (color[0] + color2[0]) * 0.5
      ccc[1] = (color[1] + color2[1]) * 0.5
      ccc[2] = (color[2] + color2[2]) * 0.5
    ccc[3] = 0.0
    land_drawColor(ccc)
    
    if tri.level == self._max_level:
      glTexCoord2f ((tri.v2.texcoord[0] + tri.v3.texcoord[0]) * 0.5,
                    (tri.v2.texcoord[1] + tri.v3.texcoord[1]) * 0.5)
      glNormal3f   ((tri.v2.normal  [0] + tri.v3.normal  [0]) * 0.5,
                    (tri.v2.normal  [1] + tri.v3.normal  [1]) * 0.5,
                    (tri.v2.normal  [2] + tri.v3.normal  [2]) * 0.5)
      glVertex3f   ((tri.v2.coord   [0] + tri.v3.coord   [0]) * 0.5,
                    (tri.v2.coord   [1] + tri.v3.coord   [1]) * 0.5,
                    (tri.v2.coord   [2] + tri.v3.coord   [2]) * 0.5)
    else:
      if tri.v2 < tri.v3: v = tri.v2 + ((tri.v3 - tri.v2) >> 1)
      else:               v = tri.v3 + ((tri.v2 - tri.v3) >> 1)
      #glArrayElement(v - self._vertices)
      glTexCoord2fv(v.texcoord)
      glNormal3fv(v.normal)
      glVertex3fv(v.coord)
      
  cdef void _tri_render_secondpass(self, LandTri* tri):
    cdef float  ccc[4]
    cdef float* color
    cdef int    index

    if ((not(self._option & LAND_VERTEX_OPTIONS) or
         not((self._vertex_options[tri.v1 - self._vertices] & LAND_VERTEX_ALPHA) or
             (self._vertex_options[tri.v2 - self._vertices] & LAND_VERTEX_ALPHA) or
             (self._vertex_options[tri.v3 - self._vertices] & LAND_VERTEX_ALPHA))) and
        (tri.base_neighbor != NULL) and (tri.v2 == tri.base_neighbor.v3) and (tri.v3 == tri.base_neighbor.v2)):
      # diamond base neighbor without alpha => special smooth texturing
      if  (((<_Material> (tri.v2.pack.material_id)) is renderer.current_material) and
           (tri.v2.pack != tri.v1.pack) and
           (tri.v2.pack != tri.v3.pack) and
           (tri.v2.pack != tri.base_neighbor.v1.pack)):
        index = tri.v1 - self._vertices; SET_COLOR_ALPHA (self, index); glTexCoord2fv(tri.v1.texcoord); glNormal3fv(tri.v1.normal); glVertex3fv(tri.v1.coord); #glArrayElement(index)
        index = tri.v2 - self._vertices; SET_COLOR_OPAQUE(self, index); glTexCoord2fv(tri.v2.texcoord); glNormal3fv(tri.v2.normal); glVertex3fv(tri.v2.coord); #glArrayElement(index)
        self._tri_render_middle(tri)
        return
      
      elif (((<_Material> (tri.v3.pack.material_id)) is renderer.current_material) and
           (tri.v3.pack != tri.v1.pack) and
           (tri.v3.pack != tri.v2.pack) and
           (tri.v3.pack != tri.base_neighbor.v1.pack)):
        index = tri.v1 - self._vertices; SET_COLOR_ALPHA (self, index); glTexCoord2fv(tri.v1.texcoord); glNormal3fv(tri.v1.normal); glVertex3fv(tri.v1.coord); #glArrayElement(index)
        self._tri_render_middle(tri)
        index = tri.v3 - self._vertices; SET_COLOR_OPAQUE(self, index); glTexCoord2fv(tri.v3.texcoord); glNormal3fv(tri.v3.normal); glVertex3fv(tri.v3.coord); #glArrayElement(index)
        return
        
    index = tri.v1 - self._vertices
    if (<_Material> (tri.v1.pack.material_id)) is renderer.current_material: SET_COLOR_OPAQUE(self, index)
    else:                                                                    SET_COLOR_ALPHA (self, index)
    glTexCoord2fv(tri.v1.texcoord)
    glNormal3fv(tri.v1.normal)
    glVertex3fv(tri.v1.coord)
    #glArrayElement(index)
    
    index = tri.v2 - self._vertices
    if (<_Material> (tri.v2.pack.material_id)) is renderer.current_material: SET_COLOR_OPAQUE(self, index)
    else:                                                                    SET_COLOR_ALPHA (self, index)
    glTexCoord2fv(tri.v2.texcoord)
    glNormal3fv(tri.v2.normal)
    glVertex3fv(tri.v2.coord)
    #gArrayElement(index)
    
    index = tri.v3 - self._vertices
    if (<_Material> (tri.v3.pack.material_id)) is renderer.current_material: SET_COLOR_OPAQUE(self, index)
    else:                                                                    SET_COLOR_ALPHA (self, index)
    glTexCoord2fv(tri.v3.texcoord)
    glNormal3fv(tri.v3.normal)
    glVertex3fv(tri.v3.coord)
    #glArrayElement(index)


  cdef void _render(self, CoordSyst coordsyst):
    cdef int      cur, index
    cdef Pack*    pack
    cdef LandTri* tri
    
    land_disableColor()
    # ArrayElement doesn't work well, at least for me
    #glInterleavedArrays(GL_T2F_N3F_V3F, sizeof(LandVertex), self._vertices)
    
    # Renderer.data is like :
    #   [pack1*, land_tri1*, land_tri2*,..., NULL, pack2*, land_tri3,..., NULL, NULL]
    
    cur = renderer.data.nb
    pack = <Pack*> chunk_get_ptr(renderer.data)
    
    if   renderer.state == RENDERER_STATE_OPAQUE:
      while pack:
        (<_Material> (pack.material_id))._activate()
        
        glBegin(GL_TRIANGLES)
        tri = <LandTri*> chunk_get_ptr(renderer.data)
        while tri:
          #index = tri.v1 - self._vertices
          if self._colors != NULL: land_drawColor(self._colors + index)
          #glArrayElement(index)
          glTexCoord2fv(tri.v1.texcoord)
          glNormal3fv(tri.v1.normal)
          glVertex3fv(tri.v1.coord)
          
          #index = tri.v2 - self._vertices
          if self._colors != NULL: land_drawColor(self._colors + index)
          #glArrayElement(index)
          glTexCoord2fv(tri.v2.texcoord)
          glNormal3fv(tri.v2.normal)
          glVertex3fv(tri.v2.coord)
          
          #index = tri.v3 - self._vertices
          if self._colors != NULL: land_drawColor(self._colors + index)
          #glArrayElement(index)
          glTexCoord2fv(tri.v3.texcoord)
          glNormal3fv(tri.v3.normal)
          glVertex3fv(tri.v3.coord)
          
          tri = <LandTri*> chunk_get_ptr(renderer.data)
          
        glEnd()
        pack = <Pack*> chunk_get_ptr(renderer.data)
        
        
    elif renderer.state == RENDERER_STATE_SECONDPASS:
      glEnable(GL_BLEND)
      
      # XXX SORT them BEFORE !!! avoid looping twice !
      
      # draw the special
      while pack:
        if pack.option & PACK_SPECIAL:
          (<_Material> (pack.material_id))._activate()
          glBegin(GL_TRIANGLES)
          tri = <LandTri*> chunk_get_ptr(renderer.data)
          while tri:
            self._vertex_render_special(tri.v1)
            self._vertex_render_special(tri.v2)
            self._vertex_render_special(tri.v3)
            tri = <LandTri*> chunk_get_ptr(renderer.data)
          glEnd()
        else:
          while chunk_get_ptr(renderer.data): pass
          
        pack = <Pack*> chunk_get_ptr(renderer.data)
        
      # draw the secondpass
      glDepthFunc(GL_LEQUAL)
      glPolygonOffset(-1.0, 0.0)
      renderer.data.nb = cur # Reset the chunk, in order to loop over the same data than above
      pack = <Pack*> chunk_get_ptr(renderer.data)
      while pack:
        if not(pack.option & PACK_SPECIAL):
          (<_Material> (pack.material_id))._activate()
          glEnable(GL_POLYGON_OFFSET_FILL) # Black magic : it doesn't work if GL_POLYGON_OFFSET_FILL is enabled once. we must enable/disable it for all glBegin-glEnd.
          glBegin(GL_TRIANGLES)
          tri = <LandTri*> chunk_get_ptr(renderer.data)
          while tri:
            self._tri_render_secondpass(tri)
            tri = <LandTri*> chunk_get_ptr(renderer.data)
          glEnd()
          glDisable(GL_POLYGON_OFFSET_FILL) # Black magic
        else:
          while chunk_get_ptr(renderer.data): pass
          
        pack = <Pack*> chunk_get_ptr(renderer.data)
        
      glDisable(GL_BLEND)
      glDepthFunc(GL_LESS)
      glDisable(GL_POLYGON_OFFSET_FILL) # Needed ? It should be already disabled
      
    land_drawColor(white)
    land_enableColor()
    glDisableClientState(GL_VERTEX_ARRAY)
    glDisableClientState(GL_NORMAL_ARRAY)
    glDisableClientState(GL_TEXTURE_COORD_ARRAY)
    
  cdef void _tri_raypick(self, LandTri* tri, float* raydata, RaypickData data):
    cdef float r, root_r
    if land_tri_has_child(tri):
      self._tri_raypick(tri.left_child,  raydata, data)
      self._tri_raypick(tri.right_child, raydata, data)
    else:
      if ((self._option & LAND_VERTEX_OPTIONS) and 
          (self._vertex_options[tri.v1 - self._vertices] & LAND_VERTEX_NON_SOLID) and
          (self._vertex_options[tri.v2 - self._vertices] & LAND_VERTEX_NON_SOLID) and
          (self._vertex_options[tri.v3 - self._vertices] & LAND_VERTEX_NON_SOLID)): return
         
      if triangle_raypick(raydata, tri.v1.coord, tri.v2.coord, tri.v3.coord, tri.normal, data.option, &r):
        root_r = self._distance_out(r)
        if (root_r < data.root_result) or (data.result_coordsyst is None):
          data.result           = r
          data.root_result      = root_r
          data.result_coordsyst = self
          memcpy(data.normal, tri.normal, 3 * sizeof(float))
          
  cdef void _full_raypick(self, LandVertex* v1, LandVertex* v2, LandVertex* v3, float* normal, float* raydata, RaypickData data):
    cdef float* coord1, *coord2, *coord3
    cdef float  a, b, c, root_a
    if ((not(self._option & LAND_VERTEX_OPTIONS)) or 
        (not(self._vertex_options[v1 - self._vertices] & LAND_VERTEX_NON_SOLID)) or
        (not(self._vertex_options[v2 - self._vertices] & LAND_VERTEX_NON_SOLID)) or
        (not(self._vertex_options[v3 - self._vertices] & LAND_VERTEX_NON_SOLID))):
      coord1 = v1.coord
      coord2 = v2.coord
      coord3 = v3.coord
      # compute distance sphere-line
      a = - raydata[5] * (coord1[0] - raydata[0]) + raydata[3] * (coord1[2] - raydata[2])
      if a < 2 * self._scale_factor:
        b = - raydata[5] * (coord2[0] - raydata[0]) + raydata[3] * (coord2[2] - raydata[2])
        c = - raydata[5] * (coord3[0] - raydata[0]) + raydata[3] * (coord3[2] - raydata[2])
        
        if not((a > 0.0) and (b > 0.0) and (c > 0.0)) and not((a < 0.0) and (b < 0.0) and (c < 0.0)):
          if triangle_raypick(raydata, coord1, coord2, coord3, normal, data.option, &a):
            root_a = self._distance_out(a)
            if (root_a < data.root_result) or (data.result_coordsyst is None): # XXX Strange : no 'fabs()' around root_a and data.root_result ???
              data.result           = a
              data.root_result      = root_a
              data.result_coordsyst = self
              memcpy(data.normal, normal, 3 * sizeof(float))

  cdef void _full_raypick_rect(self, int x1, int z1, int x2, int z2, float* raydata, RaypickData data):
    cdef int         i, j
    cdef LandVertex* p, *p0
    cdef float*      normal
    p0 = self._get_vertex(x1, z1)
    
    for j from z1 <= j < z2:
      p = p0
      normal = self._normals + 6 * (x1 + j * (self._nb_vertex_width - 1))
      for i from x1 <= i < x2:
        if not((i + j) & 1): # if both j and i are odd or even
          self._full_raypick(p + self._nb_vertex_width, p + self._nb_vertex_width + 1, p, normal, raydata, data)
          normal = normal + 3
          self._full_raypick(p + 1, p, p + self._nb_vertex_width + 1, normal, raydata, data)
          normal = normal + 3
        else:
          self._full_raypick(p, p + self._nb_vertex_width, p + 1, normal, raydata, data)
          normal = normal + 3
          self._full_raypick(p + self._nb_vertex_width + 1, p + 1, p + self._nb_vertex_width, normal, raydata, data)
          normal = normal + 3
        p = p + 1
      p0 = p0 + self._nb_vertex_width

  cdef void _raypick(self, RaypickData raypick_data, CoordSyst raypickable):
    if self._option & NON_SOLID: return
    
    cdef int        i, x, z
    cdef LandPatch* patch
    cdef float*     data
    cdef float      f, x1, z1, x2, z2
    data = self._raypick_data(raypick_data)
    if not(self._option & LAND_INITED): self._init()
    
    if self._option & LAND_REAL_LOD_RAYPICK:
      for i from 0 <= i < self._nb_patchs:
        patch = self._patchs + i
        if not sphere_raypick(data, patch.sphere): continue
        # raypick on tris
        self._tri_raypick(patch.tri_top,    data, raypick_data)
        self._tri_raypick(patch.tri_left,   data, raypick_data)
        self._tri_raypick(patch.tri_right,  data, raypick_data)
        self._tri_raypick(patch.tri_bottom, data, raypick_data)
      
    else:
      if (data[3] == 0.0) and (data[5] == 0.0):
        x = <int> (data[0] / self._scale_factor)
        z = <int> (data[2] / self._scale_factor)
        if (0.0 <= x < self._nb_vertex_width) and (0.0 <= z < self._nb_vertex_depth):
          self._full_raypick_rect(x, z, x + 1, z + 1, data, raypick_data)
      else:
        if data[6] < 0.0:
          for i from 0 <= i < self._nb_patchs:
            patch = self._patchs + i
            if not sphere_raypick(data, patch.sphere): continue
            # raypick on tris with full accuracy
            self._full_raypick_rect(<int> (patch.tri_top   .v3.coord[0] / self._scale_factor),
                                    <int> (patch.tri_top   .v3.coord[2] / self._scale_factor),
                                    <int> (patch.tri_bottom.v3.coord[0] / self._scale_factor),
                                    <int> (patch.tri_bottom.v3.coord[2] / self._scale_factor),
                                    data, raypick_data)
          
        else:
          if raypick_data.option & RAYPICK_HALF_LINE:
            x1 = data[0]
            z1 = data[2]
          else:
            x1 = data[0] - data[6] * data[3]
            z1 = data[2] - data[6] * data[5]
          x2 = data[0] + data[6] * data[3]
          z2 = data[2] + data[6] * data[5]
          if x1 > x2:
            f  = x1
            x1 = x2
            x2 = f
          if z1 > z2:
            f  = z1
            z1 = z2
            z2 = f
            
          x1 = x1 / self._scale_factor
          z1 = z1 / self._scale_factor
          x2 = x2 / self._scale_factor
          z2 = z2 / self._scale_factor
          if x2 < 0.0: return
          if z2 < 0.0: return
          if x1 >= <float> self._nb_vertex_width: return
          if z1 >= <float> self._nb_vertex_depth: return
          if x1 < 0.0: x1 = 0.0
          if z1 < 0.0: z1 = 0.0
          x2 = x2 + 1.0
          z2 = z2 + 1.0
          if x2 >= <float> self._nb_vertex_width: x2 = <float> self._nb_vertex_width - 1
          if z2 >= <float> self._nb_vertex_depth: z2 = <float> self._nb_vertex_depth - 1
          self._full_raypick_rect(<int> x1, <int> z1, <int> x2, <int> z2, data, raypick_data)

  cdef int _tri_raypick_b(self, LandTri* tri, float* raydata, int option):
    cdef float r
    if land_tri_has_child(tri):
      if self._tri_raypick_b(tri.left_child,  raydata, option): return 1
      if self._tri_raypick_b(tri.right_child, raydata, option): return 1
    else:
      if self._option & LAND_VERTEX_OPTIONS:
        if ((self._vertex_options[tri.v1 - self._vertices] & LAND_VERTEX_NON_SOLID) and
            (self._vertex_options[tri.v2 - self._vertices] & LAND_VERTEX_NON_SOLID) and
            (self._vertex_options[tri.v3 - self._vertices] & LAND_VERTEX_NON_SOLID)): return 0
      if triangle_raypick(raydata, tri.v1.coord, tri.v2.coord, tri.v3.coord, tri.normal, option, &r):
        return 1
    return 0
  
  cdef int _full_raypick_b(self, LandVertex* v1, LandVertex* v2, LandVertex* v3, float* normal, float* raydata, int option):
    cdef float* coord1, *coord2, *coord3
    cdef float  a, b, c
    if ((not(self._option & LAND_VERTEX_OPTIONS)) or
        (not(self._vertex_options[v1 - self._vertices] & LAND_VERTEX_NON_SOLID)) or
        (not(self._vertex_options[v2 - self._vertices] & LAND_VERTEX_NON_SOLID)) or
        (not(self._vertex_options[v3 - self._vertices] & LAND_VERTEX_NON_SOLID))):
      coord1 = v1.coord
      coord2 = v2.coord
      coord3 = v3.coord
      # compute distance sphere-line
      a = - raydata[5] * (coord1[0] - raydata[0]) + raydata[3] * (coord1[2] - raydata[2])
      if a < 2 * self._scale_factor:
        b = - raydata[5] * (coord2[0] - raydata[0]) + raydata[3] * (coord2[2] - raydata[2])
        c = - raydata[5] * (coord3[0] - raydata[0]) + raydata[3] * (coord3[2] - raydata[2])
        if not((a > 0.0) and (b > 0.0) and (c > 0.0)) and not((a < 0.0) and (b < 0.0) and (c < 0.0)):
          if triangle_raypick(raydata, coord1, coord2, coord3, normal, option, &a): return 1
    return 0
  
  cdef int _full_raypick_rect_b(self, int x1, int z1, int x2, int z2, float* raydata, int option):
    cdef int         i, j
    cdef LandVertex* p, *p0
    cdef float*      normal
    p0 = self._get_vertex(x1, z1)
    
    for j from z1 <= j < z2:
      p = p0
      normal = self._normals + 6 * (x1 + j * (self._nb_vertex_width - 1))
      for i from x1 <= i < x2:
        if not((i + j) & 1): # if both j and i are odd or even
          if self._full_raypick_b(p + self._nb_vertex_width, p + self._nb_vertex_width + 1, p, normal, raydata, option): return 1
          normal = normal + 3
          if self._full_raypick_b(p + 1, p, p + self._nb_vertex_width + 1, normal, raydata, option): return 1
          normal = normal + 3
        else:
          if self._full_raypick_b(p, p + self._nb_vertex_width, p + 1, normal, raydata, option): return 1
          normal = normal + 3
          if self._full_raypick_b(p + self._nb_vertex_width + 1, p + 1, p + self._nb_vertex_width, normal, raydata, option): return 1
          normal = normal + 3
        p = p + 1
      p0 = p0 + self._nb_vertex_width
    return 0

  cdef int _raypick_b(self, RaypickData raypick_data, CoordSyst raypickable):
    if self._option & NON_SOLID: return 0
    
    cdef int        i, x, z
    cdef LandPatch* patch
    cdef float*     data
    cdef float      x1, z1, x2, z2, f
    if not(self._option & LAND_INITED): self._init()
    data = self._raypick_data(raypick_data)
    
    if self._option & LAND_REAL_LOD_RAYPICK:
      for i from 0 <= i < self._nb_patchs:
        patch = self._patchs + i
        if not sphere_raypick(data, patch.sphere): continue
        # raypick on tris
        if self._tri_raypick_b(patch.tri_top,    data, raypick_data.option): return 1
        if self._tri_raypick_b(patch.tri_left,   data, raypick_data.option): return 1
        if self._tri_raypick_b(patch.tri_right,  data, raypick_data.option): return 1
        if self._tri_raypick_b(patch.tri_bottom, data, raypick_data.option): return 1
        
    else:
      if (data[3] == 0.0) and (data[5] == 0.0):
        x = <int> (data[0] / self._scale_factor)
        z = <int> (data[2] / self._scale_factor)
        
        if (0.0 <= x < self._nb_vertex_width) and (0.0 <= z < self._nb_vertex_depth):
          return self._full_raypick_rect_b(x, z, x + 1, z + 1, data, raypick_data.option)
      else:
        if data[6] < 0.0:
          for i from 0 <= i < self._nb_patchs:
            patch = self._patchs + i
            if not sphere_raypick(data, patch.sphere): continue
            # raypick on tris with full accuracy
            if self._full_raypick_rect_b(<int> patch.tri_top   .v3.coord[0] / self._scale_factor,
                                         <int> patch.tri_top   .v3.coord[2] / self._scale_factor,
                                         <int> patch.tri_bottom.v3.coord[0] / self._scale_factor,
                                         <int> patch.tri_bottom.v3.coord[2] / self._scale_factor,
                                         data, raypick_data.option): return 1
        else:
          if raypick_data.option & RAYPICK_HALF_LINE:
            x1 = data[0]
            z1 = data[2]
          else:
            x1 = data[0] - data[6] * data[3]
            z1 = data[2] - data[6] * data[5]
          x2 = data[0] + data[6] * data[3]
          z2 = data[2] + data[6] * data[5]
          if x1 > x2:
            f  = x1
            x1 = x2
            x2 = f
          if z1 > z2:
            f  = z1
            z1 = z2
            z2 = f
          x1 = x1 / self._scale_factor
          z1 = z1 / self._scale_factor
          x2 = x2 / self._scale_factor
          z2 = z2 / self._scale_factor
          if x2 < 0.0: return 0
          if z2 < 0.0: return 0
          if x1 >= <float> self._nb_vertex_width: return 0
          if z1 >= <float> self._nb_vertex_depth: return 0
          if x1 < 0.0: x1 = 0.0
          if z1 < 0.0: z1 = 0.0
          x2 = x2 + 1.0
          z2 = z2 + 1.0
          if x2 >= <float> self._nb_vertex_width: x2 = <float> self._nb_vertex_width - 1
          if z2 >= <float> self._nb_vertex_depth: z2 = <float> self._nb_vertex_depth - 1
          return self._full_raypick_rect_b(<int> x1, <int> z1, <int> x2, <int> z2, data, raypick_data.option)
    return 0
  
  cdef void _collect_raypickables(self, Chunk* items, float* rsphere, float* sphere):
    if self._option & NON_SOLID: return
    
    # XXX not implemented -- no selection
    chunk_add_ptr(items, <void*> self)
    
  def get_true_height_and_normal(self, float x, float z):
    cdef int         i, j
    cdef float       k, t
    cdef float*      norm
    cdef LandVertex* v1, *v2
    cdef LandPatch*  patch
    cdef LandTri*    tri
    
    cdef int         ix, iz
    cdef float       nx, nz
    cdef LandVertex* v3
    
    if not(self._option & LAND_INITED): self._init()
    if self._option & LAND_REAL_LOD_RAYPICK:
      # return the true height, the one of the land_tri
      for i from 0 <= i < self._nb_patchs:
        patch = self._patchs + i
        v1 = patch.tri_top   .v3
        v2 = patch.tri_bottom.v3
        if (v1.coord[0] <= x <= v2.coord[0]) and (v1.coord[2] <= z <= v2.coord[2]):
          
          # XXX not sure this works... :-(
          
          tri = patch.tri_top
          for j from 0 <= j < 4:
            land_get_height_at_factors(tri.v1, tri.v2, tri.v3, x, z, &k, &t)
            if (-EPSILON <= k <= 1.0 + EPSILON) and (-EPSILON <= t <= 1.0 + EPSILON) and (k + t - 1.0 < EPSILON):
              while 1:
                land_get_height_at_factors(tri.v1, tri.v2, tri.v3, x, z, &k, &t)
                if land_tri_has_child(tri):
                  if k >= t: tri = tri.right_child
                  else:      tri = tri.left_child
                else:
                  norm = tri.normal
                  return tri.v1.coord[1] + k * (tri.v2.coord[1] - tri.v1.coord[1]) + t * (tri.v3.coord[1] - tri.v1.coord[1]), Vector(self, norm[0], norm[1], norm[2])
                
            tri = tri + 1 # HACK : patch.tri_top, patch.tri_left, patch.tri_right, patch.tri_bottom are in this order in the LandPatch structure.
            
          break
        
    else: # interpolates the height
      nx = x / self._scale_factor
      nz = z / self._scale_factor
      ix = <int> nx
      iz = <int> nz
      if (0 <= ix < self._nb_vertex_width) and (0 <= iz < self._nb_vertex_depth):
        if not((ix + iz) & 1): # if both ix and iz are odd or even
          if nz - iz > nx - ix:
            v1 = self._get_vertex(ix,     iz + 1)
            v2 = self._get_vertex(ix,     iz)
            v3 = self._get_vertex(ix + 1, iz + 1)
            land_get_height_at_factors(v1, v2, v3, x, z, &k, &t)
            norm = self._normals + 6 * (ix + iz * (self._nb_vertex_width - 1))
            return v1.coord[1] + k * (v2.coord[1] - v1.coord[1]) + t * (v3.coord[1] - v1.coord[1]), Vector(self, norm[0], norm[1], norm[2])
          else:
            v1 = self._get_vertex(ix + 1, iz)
            v2 = self._get_vertex(ix + 1, iz + 1)
            v3 = self._get_vertex(ix,     iz)
            land_get_height_at_factors(v1, v2, v3, x, z, &k, &t)
            norm = self._normals + 6 * (ix + iz * (self._nb_vertex_width - 1)) + 3
            return v1.coord[1] + k * (v2.coord[1] - v1.coord[1]) + t * (v3.coord[1] - v1.coord[1]), Vector(self, norm[0], norm[1], norm[2])
          
        else:
          if nz - iz + nx - ix < 1:
            v1 = self._get_vertex(ix,     iz)
            v2 = self._get_vertex(ix + 1, iz)
            v3 = self._get_vertex(ix,     iz + 1)
            land_get_height_at_factors(v1, v2, v3, x, z, &k, &t)
            norm = self._normals + 6 * (ix + iz * (self._nb_vertex_width - 1))
            return v1.coord[1] + k * (v2.coord[1] - v1.coord[1]) + t * (v3.coord[1] - v1.coord[1]), Vector(self, norm[0], norm[1], norm[2])
          else:
            v1 = self._get_vertex(ix + 1, iz + 1)
            v2 = self._get_vertex(ix,     iz + 1)
            v3 = self._get_vertex(ix + 1, iz)
            land_get_height_at_factors(v1, v2, v3, x, z, &k, &t)
            norm = self._normals + 6 * (ix + iz * (self._nb_vertex_width - 1)) + 3
            return v1.coord[1] + k * (v2.coord[1] - v1.coord[1]) + t * (v3.coord[1] - v1.coord[1]), Vector(self, norm[0], norm[1], norm[2])
          
    return -1.0, None
  
  
  cdef __getcstate__(self):
    cdef int         i, j, nb
    cdef Chunk*      chunk
    cdef LandVertex* v
    cdef             material_id2index
    material_id2index = {}
    for i from 0 <= i < len(self._materials): material_id2index[self._materials[i]] = i
    nb = self._nb_vertex_width * self._nb_vertex_depth
    
    chunk = get_chunk()
    chunk_add_int_endian_safe   (chunk, self._option & ~LAND_INITED)
    chunk_add_floats_endian_safe(chunk, self._matrix, 19)
    chunk_add_int_endian_safe   (chunk, self._nb_vertex_width)
    chunk_add_int_endian_safe   (chunk, self._nb_vertex_depth)
    chunk_add_int_endian_safe   (chunk, self._patch_size)
    chunk_add_float_endian_safe (chunk, self._texture_factor)
    chunk_add_float_endian_safe (chunk, self._scale_factor)
    chunk_add_float_endian_safe (chunk, self._split_factor)
    chunk_add_int_endian_safe   (chunk, self._nb_colors)
    
    if (self._option & LAND_COLORED) and (self._nb_colors > 0):
      chunk_add_floats_endian_safe(chunk, self._colors        , 4 * self._nb_colors)
      chunk_add_ints_endian_safe  (chunk, self._vertex_colors , nb)
      
    if self._option & LAND_VERTEX_OPTIONS: chunk_add_chars_endian_safe(chunk, self._vertex_options, nb)
    
    for i from 0 <= i < nb:
      v = self._vertices + i
      chunk_add_float_endian_safe(chunk, v.coord[1])
      chunk_add_int_endian_safe  (chunk, material_id2index[<_Material> (v.pack.material_id)])
      
    return drop_chunk_to_string(chunk), self._materials
  
  cdef void __setcstate__(self, cstate):
    cdef int         i, j, nb
    cdef int         temp
    cdef Chunk*      chunk
    cdef LandVertex* v
    self._materials = cstate[1]
    chunk = string_to_chunk(cstate[0])
    
    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_vertex_width)
    chunk_get_int_endian_safe   (chunk, &self._nb_vertex_depth)
    chunk_get_int_endian_safe   (chunk, &self._patch_size)
    chunk_get_float_endian_safe (chunk, &self._texture_factor)
    chunk_get_float_endian_safe (chunk, &self._scale_factor)
    chunk_get_float_endian_safe (chunk, &self._split_factor)
    chunk_get_int_endian_safe   (chunk, &self._nb_colors)
    self._validity = COORDSYS_INVALID
    
    nb = self._nb_vertex_width * self._nb_vertex_depth
    if (self._option & LAND_COLORED) and (self._nb_colors > 0):
      self._colors        = <float*> malloc(4 * self._nb_colors * sizeof(float))
      self._vertex_colors = <int*  > malloc(nb *                  sizeof(int))
      chunk_get_floats_endian_safe(chunk, self._colors        , 4 * self._nb_colors)
      chunk_get_ints_endian_safe  (chunk, self._vertex_colors , nb)
    else: self._colors = self._vertex_colors = NULL
    
    if self._option & LAND_VERTEX_OPTIONS:
      self._vertex_options = <char*> malloc(nb * sizeof(char))
      chunk_get_chars_endian_safe(chunk, self._vertex_options, nb)
    else: self._vertex_options = NULL
      
    if (self._nb_vertex_width != 0) and (self._nb_vertex_depth != 0):
      self._vertices = <LandVertex*> malloc(nb * sizeof(LandVertex))
      for i from 0 <= i < nb:
        v = self._vertices + i
        chunk_get_float_endian_safe(chunk, v.coord + 1)
        chunk_get_int_endian_safe(chunk, &temp)
        v.pack = (<_Material> (self._materials[temp]))._pack(FACE_TRIANGLE)
      self._normals = <float*> malloc((self._nb_vertex_width - 1) * (self._nb_vertex_depth - 1) * 6 * sizeof(float))
    else: self._vertices = self._normals = NULL
    
    drop_chunk(chunk)
    self._compute_coords()
    
    
  cdef void _check_vertex_options(self):
    if not(self._option & LAND_VERTEX_OPTIONS):
      self._option = self._option | LAND_VERTEX_OPTIONS
      #free(self._vertex_options)
      self._vertex_options = <char*> calloc(self._nb_vertex_width * self._nb_vertex_depth, sizeof(char))
      
  cdef int _check_color(self, float* color):
    cdef int i, nb, my_white
    if not(self._option & LAND_COLORED):
      nb = self._nb_vertex_width * self._nb_vertex_depth
      self._option = self._option | LAND_COLORED
      self._vertex_colors = <int*> malloc(nb * sizeof(int))
      my_white = self._register_color(white)
      for i from 0 <= i < nb: self._vertex_colors[i] = my_white
    if 1.0 - color[3] > EPSILON: self._check_vertex_options() # need vertex option to stock vertex alpha
    return self._register_color(color)

  
# XXX FX
      
#   cdef void _fx_all(self, fx* fx):
#     cdef i, nb
#     nb = self._nb_vertex_width * self._nb_vertex_depth
#     for i from 0 <= i < nb:: fx.apply(fx, i)
    

#   cdef void _fx_in_sphere(self, fx* fx, float sphere[4]):
#     cdef int x1, z1, x2, z2, i, j, k, t
#     # compute box from sphere
#     x1 = <int> ((sphere[0] - sphere[3]) / self._scale_factor)
#     x2 = <int> ((sphere[0] + sphere[3]) / self._scale_factor)
#     z1 = <int> ((sphere[2] - sphere[3]) / self._scale_factor + 1.0)
#     z2 = <int> ((sphere[2] + sphere[3]) / self._scale_factor + 1.0)
#     if (x1 >= self._nb_vertex_width) or (z1 >= self._nb_vertex_depth) or (x2 < 0) or (z2 < 0): return
#     if x1 < 0: x1 = 0
#     if z1 < 0: z1 = 0
#     if x2 > self._nb_vertex_width: x2 = self._nb_vertex_width
#     if z2 > self._nb_vertex_depth: z2 = self._nb_vertex_depth
#     # test each vertex in the box
#     k = x1 + z1 * self._nb_vertex_width
#     for j from z1 <= j < z2:
#       t = k
#       for i from x1 <= i < x2:
#         if point_is_in_sphere(sphere, (self._vertices + t).coord)):
#           fx.apply(fx, t)
#         t = t + 1
#       k = k + self._nb_vertex_width
      
#   cdef void _fx_in_cylinderY(self, fx* fx, float cylinder[3]):
#     cdef float* f
#     cdef float  x, z
#     cdef int    x1, z1, x2, z2, i, j, k, t
#     # make box from cylinder
#     x1 = <int> ((cylinder[0] - cylinder[2]) / self._scale_factor)
#     x2 = <int> ((cylinder[0] + cylinder[2]) / self._scale_factor)
#     z1 = <int> ((cylinder[1] - cylinder[2]) / self._scale_factor + 1.0)
#     z2 = <int> ((cylinder[1] + cylinder[2]) / self._scale_factor + 1.0)
#     if (x1 >= self._nb_vertex_width) or (z1 >= self._nb_vertex_depth) or (x2 < 0) or (z2 < 0): return
#     if x1 < 0: x1 = 0
#     if z1 < 0: z1 = 0
#     if x2 > self._nb_vertex_width: x2 = self._nb_vertex_width
#     if z2 > self._nb_vertex_depth: z2 = self._nb_vertex_depth
#     # test each vertex in the box
#     k = x1 + z1 * self._nb_vertex_width
#     for j from z1 <= j < z2:
#       t = k
#       for i from x1 <= i < x2:
#         f = (self._vertices + t).coord
#         x = f[0] - cylinder[0]
#         z = f[2] - cylinder[1]
#         if x * x + z * z <= cylinder[2] * cylinder[2]:
#           fx.apply(fx, t)
#         t = t + 1
#       k = k + self._nb_vertex_width


