"""
a plant/tree featuring simple procedural modelling (l-system like).

made for interactive controls: besides growing, and 'ungrow' (revert).

todo/wishlist:
- fix algo (support variable num of branches from one point)
  * needed for 3d too
- mesh support for body (now edges/faces (lines) only)
- leaves
- smooth growing/reverting (animate)
- ..
- good interactive controls (for VJing)
- better camera automatization, + controls

"""

import random
import soya, soya.sdlconst
from math import *

soya.init()

class Plant(soya.World):
    def __init__(self, scene):
        soya.World.__init__(self, scene)

        self.root = soya.Vertex(self, 0, 0, 0) #Root
        self.end = soya.Vertex(self, 0, 1, 0) #'latva'
        self.trunk = soya.Face(self, [self.root,
                                      self.end])
        self.branches = [] #all drawn lines, except for the trunk inited above
        self.num_branches = 0 #num of branches from the current branchfrom
        self.branchfrom = self.trunk #the branch which the next new will extend
        self.branchfrom_queue = [] #queue for coming branchpoints, used as FIFO

        #options
        self.d_angle = 35 #no random yet
        self.length_ratio = 0.8
        #algo can't handle >2 branches from one point yet

    def grow(self):
        if self.num_branches == 2: #branching limit achieved
            self.num_branches = 0
            try:
                self.branchfrom = self.branchfrom_queue.pop(0)
            except IndexError:
                print "set branching to root. never happens here?"
                self.branchfrom = self.trunk

        start = self.branchfrom.vertices[-1]

        n = (self.num_branches*2) - 1 #-1 for 0, 1 for 1

        prev_vector = self.branchfrom.vertices[0] >> self.branchfrom.vertices[1]
        length = prev_vector.length() * self.length_ratio
        prev_angle = prev_vector.angle_to(up) #is always a positive value
        if prev_vector.x < 0: #the angle is actually negative
            prev_angle = -prev_angle
            
        next_angle = (n*self.d_angle) + prev_angle
        rad = pi / (180/next_angle) #angle (from normal) as radians
        dx = length * sin(rad)
        dy = length * cos(rad)
        dz = 0
                          
        #print id(self.branchfrom), "angles:", prev_angle, next_angle

        end = soya.Vertex(self,
                          start.x + dx,
                          start.y + dy,
                          start.z + dz)
            
        branch = soya.Face(self, [start,
                                  end]) #overlaps with .children

        self.branches.append(branch)
        self.branchfrom_queue.append(branch)
        #print "added", id(self.branches[-1])
        self.num_branches += 1
        return True

    def revert(self):
        try:
            remove = self.branches.pop()
        except IndexError:
            pass #was empty already
        else:
            self.remove(remove)
            
            try:
                self.branchfrom_queue.pop()
            except IndexError:
                pass

            if self.num_branches > 0:
                self.num_branches -= 1

            try:
                self.branchfrom = self.branches[-1]
            except IndexError: #last was removed
                self.branchfrom = self.trunk


class PlantToy(soya.World):
    def begin_round(self):
        soya.World.begin_round(self) #is there anything?
        for event in soya.process_event():
            if event[0] == soya.sdlconst.KEYDOWN:
                if event[1] == soya.sdlconst.K_ESCAPE:
                    sys.exit()
                    
            elif event[0] == soya.sdlconst.MOUSEMOTION:
                mouse_x = event[1]
                mouse_y = event[2]
                mouse_pos = camera.coord2d_to_3d(mouse_x, mouse_y,
                                                 (plant % camera).z)
                mouse_pos.convert_to(self)

                if len(plant.branches) < 2:
                    plant.end.move(mouse_pos)
                    
                amount = int((plant.root.vector_to(mouse_pos).length() - 0.5) * 20)
                if amount > len(plant.branches):
                    if plant.grow():
                        camera.z += 0.02
                if amount < len(plant.branches):
                    plant.revert()
                    if camera.z > 2:
                        camera.z -= 0.02


scene = PlantToy()
plant = Plant(scene)

#globals (show for scene & plant)
light = soya.Light(scene)
light.set_xyz(1.0, 0.7, 1.0)

camera = soya.Camera(scene)
camera.set_xyz(0.0, 0.6, 2.0)
soya.set_root_widget(camera)

up = soya.Vector(scene, 0, 1, 0)

#start
idler = soya.Idler(scene)
idler.idle()

