import Blender
from Blender import *
from math import *
from copy import *
from types import *
from Matrix import *
import random
from sys import *

force = [0.0, 1.0, 0.0]
scaleforce = 0.0
resLOD = 0.0
leafscale = 1.0
prune = 0.0
prunefirst = 0
thickness = 0.0
conism = 0.0
shorten = 0.0
maxdepth = 0
randScale = 0.0
randThin = 0.0
min_prune_level = 10
max_prune_level = 10
#random
randomForLeaf = random.Random()
randomForBranch = random.Random()
#leaves
_leafObjects = None
_leafPopulation = None
_useLeaf = None

def apply_force(turtle,scale):
	s = (1.0 - scale*scaleforce*10.0)
	if s <= 0.0:
		return
	lf = vec_scal_mul(force,s)
	turtle[1] = vec_normal(vec_add(turtle[1],lf))
	turtle[0] = vec_normal(crossp(turtle[1],turtle[2]))
	turtle[2] = crossp(turtle[0],turtle[1])


	
def add_leaf(mesh,leaf_data,turtle, isendpoint):
	if _useLeaf:
		# choose which leaf to add
		leafObject = _leafObjects[randomForLeaf.sample(_leafPopulation,1)[0]]
		
		# rotate the turtle according to the randomly chosed leaf
		
		if not isendpoint:
			matrix_rotate_about_side(turtle,rot_randomize(leafObject.rotSide))
		else:
			matrix_rotate_about_side(turtle,rot_randomize(90*(pi/180)))
		matrix_rotate_about_forward(turtle,rot_randomize(leafObject.rotForward))
	
	
		last_vertex = len(mesh.verts)
		sampleverts = leafObject.sampleMesh[0]
		samplefaces = leafObject.sampleMesh[1]
		sampleuvs	= leafObject.sampleMesh[2]
		samplemodes	= leafObject.sampleMesh[3]
		samplesmooths = leafObject.sampleMesh[4]
		sampleMaterials = leafObject.sampleMesh[5]

		''' Code For Generating With Mesh Object
		# this code needs optomisation
		# updating the source to Mesh from NMesh has
		# adds almost 2x the processing time to the add_leaf function
		
		for v in sampleverts:
			vg = transform_point_to_src(turtle,v)
			mesh.verts.extend(vg[0], vg[1], vg[2])

		iface = 0
		for f in samplefaces:
			nf = []
			for fv in f:
				nf.append(mesh.verts[last_vertex + fv])
			mesh.faces.extend(nf)
			faceslen = len(mesh.faces)-1
			if sampleuvs:
				mesh.faces[faceslen].uv = sampleuvs[iface]
				mesh.faces[faceslen].mode = samplemodes[iface]
			mesh.faces[faceslen].smooth = samplesmooths[iface]			
			mesh.faces[faceslen].mat = sampleMaterials[iface]
			
			iface += 1
			
		leaf_data[0] += len(sampleverts)
		'''
		
		# Code For Generating With NMesh Object
		for v in sampleverts:
			vg = transform_point_to_src(turtle,v)
			nv = NMesh.Vert(vg[0], vg[1], vg[2])
			mesh.verts.append(nv)

		iface = 0
		for f in samplefaces:
			nf = NMesh.Face()
			for fv in f:
				nf.v.append(mesh.verts[last_vertex + fv])
				if sampleuvs:
					nf.uv = sampleuvs[iface]
					nf.mode = samplemodes[iface]
				nf.smooth = samplesmooths[iface]
				nf.mat = sampleMaterials[iface]
			mesh.faces.append(nf)
			iface += 1
		leaf_data[0] += len(sampleverts)


def add_branch(mesh,turtle,bradius,blen,bres,branch_data):
		bresolution = int(bres)
		if bresolution < 3:
			bresolution = 3
		active_vertices = branch_data[0]
		by = branch_data[1]
		last_vertex = len(mesh.verts)
		new_vertices = []
		first_active_vertices = []
		for v in range(bresolution):
			phi = (float(v) / float(bresolution)) * pi * 2
			if active_vertices == []:
				v_local = [cos(phi) * bradius ,0.0, sin(phi)* bradius]
				vg = transform_point_to_src(turtle,v_local)
				nv = NMesh.Vert(vg[0],vg[1],vg[2])
				mesh.verts.append(nv)
				first_active_vertices.append(nv)
			v_local = [cos(phi)* bradius , blen , sin(phi)* bradius]
			vg = transform_point_to_src(turtle,v_local)
			nv = NMesh.Vert(vg[0],vg[1],vg[2])
			mesh.verts.append(nv)
			new_vertices.append(nv)
		if active_vertices == []:
			active_vertices = first_active_vertices
		for i in range(bresolution):
			nf = NMesh.Face()
			nf.v.append(active_vertices[i])
			nf.uv.append((float(i) / float(bresolution),by))
			nf.v.append(new_vertices[i])
			nf.uv.append((float(i) / float(bresolution),by + blen))
			nf.v.append(new_vertices[(i + 1) % bresolution])
			nf.uv.append((float(i+1) / float(bresolution),by + blen))
			nf.v.append(active_vertices[(i + 1) % bresolution])
			nf.uv.append((float(i+1) / float(bresolution),by))
			nf.smooth = 1
			nf.mode = 0
			mesh.faces.append(nf)
		branch_data[0] = new_vertices
		branch_data[1] = by + blen 

def close_branch(mesh,turtle,branch_data,scale):
	active_vertices = branch_data[0]
	by = branch_data[1]
	nav = len(active_vertices)
	v_local = [0.0,scale,0.0]
	vg = transform_point_to_src(turtle,v_local)
	nv = NMesh.Vert(vg[0],vg[1],vg[2])
	mesh.verts.append(nv)
	for i in range(nav):
		nf = NMesh.Face()
		nf.v.append(active_vertices[i])
		nf.uv.append((float(i) / float(nav),by))
		nf.v.append(nv)
		nf.uv.append(((float(i)+0.5) / float(nav),by+0.1))
		nf.v.append(active_vertices[(i+1) % nav])
		nf.uv.append((float(i+1) / float(nav),by))
		nf.smooth = 1
		nf.mode = 0
		mesh.faces.append(nf)
		
def is_substring(token):
	return token[0] == 'b'

def forward(param):
	return ['f',param]

def is_advance(token):
	return token[0] == 'a'

def is_rotS(token):
	return token[0] == 'rS'

def is_rotF(token):
	return token[0] == 'rF'

def is_rotU(token):
	return token[0] == 'rU'

def is_scale(token):
	return token[0] == 's'

def is_leaf(token):
	return token[0] == 'l'

def is_endpointleaf(token):
	return token[0] == 'el'

def ls_token():
	return[]


def get_rule(rules, token):
	res = None
	for rule in rules:
		if rule[0] == token:
			return rule
	return None

def grow_aux(str,grow_rules,iteration,level):
	grow_aux(str,grow_rules,iteration,0)

def grow(str,grow_rules,iteration,level):
	new_str = []
	str_len = len(str)
	for i in range(str_len):
		token = str[i]
		if type(token) is ListType:
			if iteration > prunefirst:
				if level < maxdepth:
					if (level < min_prune_level) or (level > max_prune_level) or (randomForBranch.random() > prune):
						sub_str = grow(token,grow_rules,iteration,level+1)
						new_str.append(sub_str)
		else:
			rule = get_rule(grow_rules,token)
			if rule:
				new_str.extend(rule[1])
			else:
				new_str.append(token)
	return new_str		
		

def finalize(str,terminal_rules):
	new_str = []
	for token in str:
		if type(token) is ListType:
			sub_str = finalize(token,terminal_rules)
			new_token = ['b',sub_str]
			new_str.append(new_token)
		else:
			rule = get_rule(terminal_rules,token)
			if rule:
				new_str.extend(rule[1])
	return new_str
	

def get_mesh(mesh,leafmesh,str,resolution,rot_rand_per,rot_rand_abs):
	turtle = identityMatrix4()
	get_branch(mesh,leafmesh,[0,0],turtle,str,1.0,1.0,resolution,rot_rand_per,rot_rand_abs)

def get_branch(mesh,leafmesh,leafmesh_data,turtle,str,scale,lenscale,resolution,rot_rand_per,rot_rand_abs):
	scale_falloff = 1.0 - conism
	len_keep = 1.0 - shorten
	branch_data = [[],0.0]
	for token in str:
		if is_substring(token):
			turtle_aux = deepcopy(turtle)
			matrix_rotate_about_side(turtle_aux,r_rot_per * (randomForBranch.random() - 0.5))
			matrix_rotate_about_forward(turtle_aux,r_rot_per* (randomForBranch.random() - 0.5))
			matrix_rotate_about_up(turtle_aux,r_rot_per* (randomForBranch.random() - 0.5))
			new_resolution = (1.0 * resolution)* (1.0 - resLOD) + (scale * resolution) * resLOD
			randscale_factor = 1.0 - (randScale * randomForBranch.random())
			get_branch(mesh,leafmesh,leafmesh_data,turtle_aux,token[1],(scale * randscale_factor) * (1.0 - (randThin * randomForBranch.random())),lenscale * randscale_factor,new_resolution,rot_rand_per,rot_rand_abs)
		elif is_advance(token):
			add_branch(mesh,turtle,thickness * scale,token[1] * lenscale, resolution,branch_data)
			matrix_advance(turtle,token[1]*lenscale)
			apply_force(turtle,scale)
			matrix_rotate_about_side(turtle,r_rot_abs* (randomForBranch.random() - 0.5))
			matrix_rotate_about_forward(turtle,r_rot_abs* (randomForBranch.random() - 0.5))
			matrix_rotate_about_up(turtle,r_rot_abs* (randomForBranch.random() - 0.5))
			scale *= scale_falloff
			lenscale *= len_keep
		# we do these even if this is a leaf as it allows the branch to control leaf placement
		elif is_rotS(token):
			matrix_rotate_about_side(turtle,rot_randomize(token[1]))
		elif is_rotF(token):
			matrix_rotate_about_forward(turtle,rot_randomize(token[1]))
		elif is_rotU(token):
			matrix_rotate_about_up(turtle,rot_randomize(token[1]))
		elif is_scale(token):
			scale *= token[1]
			lenscale *= token[2]
		elif is_leaf(token):
			add_leaf(leafmesh,leafmesh_data,turtle,0)
		elif is_endpointleaf(token):
			add_leaf(leafmesh,leafmesh_data,turtle,1)
			
	if branch_data[0]:
		close_branch(mesh,turtle,branch_data,thickness * scale)

r_rot_per = 0.0
r_rot_abs = 0.0


def lsystem_build(
				axiom,
				grow_rules,
				final_rules,
				iterations,
				g_thickness,
				g_conism,
				g_shorten,
				resolution,
				resolutionLOD,
				rot_rand_per,
				rot_rand_abs,
				g_randScale,
				g_prune,
				g_prunefirst,
				g_maxdepth,
				g_min_prune_level,
				g_max_prune_level,
				glob_force,
				sforce,
				useleaf,
				branchSeed,
				leafSeed,
				leaves,
				leafPopulation
					):
	global r_rot_per, r_rot_abs, force, resLOD, scaleforce, leafscale, prune, prunefirst, thickness, conism,shorten,maxdepth,min_prune_level, max_prune_level,randScale,randThin
	global _leafObjects, _leafPopulation, _useLeaf
	force = glob_force
	scaleforce = sforce
	r_rot_per = rot_rand_per
	r_rot_abs = rot_rand_abs
	randScale = g_randScale
	randThin = g_randScale
	resLOD = resolutionLOD
	prune = g_prune
	prunefirst = g_prunefirst
	maxdepth = g_maxdepth
	thickness = g_thickness
	conism = g_conism
	shorten = g_shorten
	min_prune_level = g_min_prune_level 
	max_prune_level = g_max_prune_level
	
	_leafObjects = leaves
	_leafPopulation = leafPopulation
	_useLeaf = useleaf
	
	randomForBranch.seed(branchSeed)
	randomForLeaf.seed(leafSeed)

	lsystem = axiom
	for i in range(iterations):
		lsystem = grow(lsystem,grow_rules,i,1)
	final_string = finalize(lsystem,final_rules)
	
	# Create the mesh datablocks that the tree and leaves will be created in
	mesh = NMesh.GetRaw()
	leafmesh = NMesh.GetRaw()
	
	get_mesh(mesh,leafmesh,final_string,resolution,rot_rand_per,rot_rand_abs)
	
	return [mesh, leafmesh]
	
def rot_randomize(rot):
# return rot + (-rot * random.random() * r_rot_per)
	return rot