# Soya 3D
# Copyright (C) 2003-2004 Jean-Baptiste LAMY -- jiba@tuxfamily.org
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

cdef class _TravelingCamera(_Camera):
  #cdef           _travelings
  #cdef Traveling _traveling
  #cdef float     _speed_value
  #cdef _Vector   _speed
  
  property traveling:
    def __get__(self): return self._traveling
  
  property travelings:
    def __get__(self): return self._travelings
  
  property speed:
    def __get__(self): return self._speed_value
    def __set__(self, float x): self._speed_value = x
    
  def __init__(self, parent = None):
    _Camera.__init__(self, parent)
    
    self._travelings  = []
    self._traveling   = None
    self._speed_value = 0.3
    self._speed       = Vector()
    
  def add_traveling(self, Traveling traveling):
    self._travelings.append(traveling)
    self._traveling = traveling
    self._traveling_changed()
    
  def pop_traveling(self):
    del self._travelings[-1]
    self._traveling = self._travelings[-1]
    self._traveling_changed()
    
  def remove_traveling(self, Traveling traveling):
    self._travelings.remove(traveling)
    if self._traveling is traveling:
      self._traveling = self._travelings[-1]
      self._traveling_changed()
      
  cdef void _traveling_changed(self):
    cdef float v[3], up[3]
    cdef float angle
    
    if self._traveling._incline_as is None:
      v[0] = 0.0
      v[1] = 1.0
      v[2] = 0.0
      vector_by_matrix(v, self._inverted_root_matrix())
      
      if (v[0] != 0.0) or (v[1] != 0.0):
        up[0] = 0.0
        up[1] = 1.0
        up[2] = 0.0
        v [2] = 0.0
        
        angle = vector_angle(up, v)
        if v[0] > 0: self.turn_incline(-angle)
        else:        self.turn_incline( angle)
        
  #def begin_round(self):
    #if self._traveling._smooth_move:
    #  self._speed.set_start_end(self, self._traveling.best_position(self))
    #  self._speed.__imul__(self._speed_value)
    
  def advance_time(self, float proportion):
    import soya
    if soya.IDLER.will_render == 0:
      self._proportion = self._proportion + proportion
      return
    
    proportion = proportion + self._proportion
    
    cdef _Vector direction, current
    cdef float   v[3], up[3]
    cdef float   angle
    
    if self._traveling._smooth_move:
      while proportion > 1.0:
        self._speed.set_start_end(self, self._traveling.best_position(self))
        self.add_mul_vector(self._speed_value, self._speed)
        proportion = proportion - 1.0
        
      self._speed.set_start_end(self, self._traveling.best_position(self))
      self.add_mul_vector(proportion * self._speed_value, self._speed)
      
    else:
      self.move(self._traveling.best_position(self))
    
    if self._traveling._smooth_rotation:
      r = self._traveling.best_direction(self)
      if isinstance(r, _Vector): direction = r
      else:                      direction = self >> r
      direction.convert_to(self)
      direction.normalize()
      if   direction._matrix[0] < -0.02: direction._matrix[0] = -0.02
      elif direction._matrix[0] >  0.02: direction._matrix[0] =  0.02
      if   direction._matrix[1] < -0.02: direction._matrix[1] = -0.02
      elif direction._matrix[1] >  0.02: direction._matrix[1] =  0.02
      direction._matrix[2] = -1.0
      self.look_at(direction)
      
    else:
      self.look_at(self._traveling.best_direction(self))
      
#       if self._traveling._incline_as is None:
#         self.look_at(self._traveling.best_direction(self))
#       else:
#         # XXX optimizable
#         r = self.traveling.best_direction(self)
#         if isinstance(r, _Vector): direction = r
#         else:                      direction = self >> r
#         current = Vector(self, 0.0, 0.0, -1.0)
#         axe = current.cross_product(direction)
#         axe.convert_to(self.parent)
#         self.rotate_axe_xyz(current.angle_to(direction), axe.x, axe.y, axe.z)
        
    if not self._traveling._incline_as is None:
      v[0] = 0.0
      v[1] = 1.0
      v[2] = 0.0
      vector_by_matrix(v, self._traveling._incline_as._root_matrix())
      vector_by_matrix(v, self._inverted_root_matrix())
      
      if (v[0] != 0.0) or (v[1] != 0.0):
        up[0] = 0.0
        up[1] = 1.0
        up[2] = 0.0
        v [2] = 0.0
        
        angle = vector_angle(up, v)
        angle = min(2.0 * self._speed_value, angle) * proportion
        if v[0] > 0: self.turn_incline(-angle)
        else:        self.turn_incline( angle)
        
    self._proportion = 0.0

        
  def zap(self):
    self.move   (self._traveling.best_position (self))
    self.look_at(self._traveling.best_direction(self))
    
    
cdef class Traveling(_CObj):
  #cdef CoordSyst _incline_as
  #cdef int       _smooth_move, _smooth_rotation
  
  property incline_as:
    def __get__(self):
      return self._incline_as
    def __set__(self, CoordSyst x):
      self._incline_as = x
    
  property smooth_move:
    def __get__(self):
      return self._smooth_move
    def __set__(self, int x):
      self._smooth_move = x
    
  property smooth_rotation:
    def __get__(self):
      return self._smooth_rotation
    def __set__(self, int x):
      self._smooth_rotation = x
    
  def best_position (self, _TravelingCamera camera):
    raise NotImplementedError
  
  def best_direction(self, _TravelingCamera camera):
    raise NotImplementedError


cdef class _FixTraveling(Traveling):
  """A fixed view traveling."""
  
  #cdef Position _target, _direction
  
  property target:
    def __get__(self):
      return self._target
    def __set__(self, Position x):
      self._target = x
    
  property direction:
    def __get__(self):
      return self._direction
    def __set__(self, Position x):
      self._direction = x
    
  def __init__(self, Position target, Position direction, int smooth_move = 1, int smooth_rotation = 0):
    """FixTraveling(target, direction, smooth_move = 1, smooth_rotation = 0)
TARGET is the position of the camera, and DIRECTION the direction to look at.
SMOOTH_MOVE and SMOOTH_ROTATION defines wether the camera moves and rotates smoothly or
not."""
    
    self._target          = target
    self._direction       = direction or Vector(None, 0.0, 0.0, -1.0)
    self._smooth_move     = smooth_move
    self._smooth_rotation = smooth_rotation
    
  def best_position(self, _TravelingCamera camera):
    return self._target or camera
  
  def best_direction(self, _TravelingCamera camera):
    return self._direction


cdef class _ThirdPersonTraveling(Traveling):
  """A Tomb-Raider-like traveling.
TARGET is a point in the character (or world) to follow."""
  
  #cdef Position _target
  #cdef _Point   __target, _best, _result, __direction
  #cdef _Vector  _direction, __normal
  #cdef float _distance, _offset_y, _offset_y2, _lateral_angle, _top_view
  #cdef float _speed
  
  property speed:
    def __get__(self):
      return self._speed
    def __set__(self, float x):
      self._speed = x
    
  property target:
    def __get__(self):
      return self._target
    def __set__(self, Position x):
      self._target = x
    
  property direction:
    def __get__(self):
      return self._direction
    def __set__(self, _Vector x):
      self._direction = x
      
  property distance:
    def __get__(self):
      return self._distance
    def __set__(self, float x):
      self._distance = x
      
  property offset_y:
    def __get__(self):
      return self._offset_y
    def __set__(self, float x):
      self._offset_y = x
      
  property offset_y2:
    def __get__(self):
      return self._offset_y2
    def __set__(self, float x):
      self._offset_y2 = x
      
  property lateral_angle:
    def __get__(self):
      return self._lateral_angle
    def __set__(self, float x):
      self._lateral_angle = x
      
  property top_view:
    def __get__(self):
      return self._top_view
    def __set__(self,  x):
      self._top_view = x
      
  def __init__(self, Position target = None, _Vector direction = None, int smooth_move = 1, int smooth_rotation = 0):
    self._incline_as      = target
    self._target          = target
    self.__target         = Point()
    self._best            = Point()
    self._result          = Point()
    self._direction       = direction or Vector(None, 0.0, 1.0, 2.0)
    self.__direction      = Point()
    self.__normal         = Vector()
    self._smooth_move     = smooth_move
    self._smooth_rotation = smooth_rotation
    self._distance        = 5.0
    self._offset_y        = 1.5
    self._offset_y2       = 0.0
    self._lateral_angle   = 0.0
    self._top_view        = 0.0
    self._speed           = 1.0
    self._context         = None
    
  def best_position(self, _TravelingCamera camera):
    cdef _World  root
    cdef _Point  target, result, best
    cdef _Vector direction
    cdef int     i, j
    cdef float   best_dist, dist, px, py, pz, a, old_dirx, old_diry, c, s
    cdef int     lat, vert
    
    root      = camera._get_root()
    target    = self.__target
    result    = self._result
    best      = self._best
    direction = self._direction
    
    target._parent = self._target
    target._matrix[0] = 0.0
    target._matrix[1] = self._offset_y
    target._matrix[2] = 0.0
    target.convert_to(root)
    
    best.clone(target)
    best.add_mul_vector(0.5, direction)
    self._context = root.RaypickContext(best, self._distance * 0.6, self._context)
    
    px, py, pz = self._target.transform(camera)
    
    if self._lateral_angle:
      c  = cos(self._lateral_angle)
      s  = sin(self._lateral_angle)
      a  = px
      px = c * px + s * pz
      pz = c * pz - s * a
      
    py = py - self._top_view * self._distance
    
    if (fabs(px) < 1.0) and (pz > 0.0): lat = 0
    else:                               lat = -cmp(px, 0.0) # XXX optimizable
    if fabs(py - 3.0) < 1.0: vert = 0
    else:                    vert = -cmp(py, 3.0) # XXX optimizable
    
    best_dist = self._check(self._context, target, direction, result)
    if (best_dist > self._distance - 0.1) and (lat == 0) and (vert == 0): return result
    else: best.clone(result)
    
    direction.convert_to(camera)

    old_dirx = direction._matrix[0]
    old_diry = direction._matrix[1]


    direction._matrix[0] = old_dirx - 0.3 * self._speed
    dist = self._check(self._context, target, direction, result)
    if lat == -1: dist = dist + 0.1
    #dist = dist - 0.1 * lat
    if dist > best_dist: best.clone(result); best_dist = dist

    direction._matrix[0] = old_dirx + 0.3 * self._speed
    dist = self._check(self._context, target, direction, result)
    if lat == 1: dist = dist + 0.1
    #dist = dist + 0.1 * lat
    if dist > best_dist: best.clone(result); best_dist = dist

    direction._matrix[0] = old_dirx
    direction._matrix[1] = old_diry - 0.2 * self._speed
    dist = self._check(self._context, target, direction, result)
    if vert == -1: dist = dist + 0.1
    #dist = dist - 0.1 * vert
    if dist > best_dist: best.clone(result); best_dist = dist
    
    direction._matrix[1] = old_diry + 0.2 * self._speed
    dist = self._check(self._context, target, direction, result)
    if vert == 1: dist = dist + 0.1
    #dist = dist + 0.1 * vert
    if dist > best_dist: best.clone(result); best_dist = dist
    
#     for i from -1 <= i < 2:
#       for j from -1 <= j < 2:
#         if (i != 0) or (j != 0):
#           direction._matrix[0] = old_dirx + self._speed * camera._speed_value * i
#           direction._matrix[1] = old_diry + self._speed * camera._speed_value * j
          
#           dist = self._check(root, target, direction, result)
#           dist = dist + 0.3 * (lat * i) + 0.3 * (vert * j)
          
#           if dist > best_dist: best.clone(result); best_dist = dist
#           # XXX if dist > best_dist + 0.2 => returns immediately (cannot find better)
          
          
    direction.set_start_end(target, best)
    
    return best
  
  def best_direction(self, _TravelingCamera camera):
    #return self._target
    self.__direction.clone(self._target)
    self.__direction._matrix[1] = self.__direction._matrix[1] + self._offset_y + self._offset_y2
    return self.__direction
  
  
  cdef float _check(self, RaypickContext root, Position target, _Vector direction, _Point result):
    # XXX use raypick context ?
    if not root.raypick(target, direction, self._distance, 1, 0, result, self.__normal):
      direction.set_length(self._distance)
      result.clone(target)
      result.add_vector(direction)
      return self._distance
    
    return result.distance_to(target)
  
