""" 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()