grow.py 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  1. # This code is called when instances of this SOP cook.
  2. #scaleFactor = node.parm("scaleFactor").eval()
  3. import random
  4. import hou
  5. node = hou.pwd()
  6. geo = node.geometry()
  7. # map user input
  8. GENERATIONS = hou.ch('generations')
  9. TROPISM = hou.Vector3(hou.parmTuple("tropDir").eval())
  10. TROPISMFACTOR = hou.ch('tropStrength')
  11. GRAVITYFACTOR = hou.ch('gFactor')
  12. SEED = hou.ch('seed')
  13. #hou.ch("falloff")
  14. # the "seed"
  15. # this is the first point from which the tree will grow.
  16. point = geo.createPoint()
  17. pointers = [point]
  18. # init the point attributes to store the values in, as these must exist.
  19. # As we start, the first point's normal will point upward to grow in that direction.
  20. geo.addAttrib(hou.attribType.Point, "N", hou.Vector3([0, 1, 0]))
  21. geo.addAttrib(hou.attribType.Point, "Branch", 0)
  22. geo.addAttrib(hou.attribType.Point, "Generation", 0)
  23. geo.addAttrib(hou.attribType.Point, "BranchPoint", 0)
  24. geo.addAttrib(hou.attribType.Point, 'NormalizedPosition', 0.0)
  25. geo.addAttrib(hou.attribType.Point, 'Diameter', 1.0)
  26. # Just in case, if we need groups, this is how to do it.
  27. #defaultGrp=geo.createPointGroup('name')
  28. #defaultGrp.add(point)
  29. def duplicate_point(p):
  30. # hmm. maybe there is sth like this already, but i missed it...
  31. # this duplicates a point and all it's attributes.
  32. dupe=geo.createPoint()
  33. dupe.setPosition(p.position())
  34. allAttr=geo.pointAttribs()
  35. for i in allAttr:
  36. holder = None
  37. holder = p.attribValue(i)
  38. dupe.setAttribValue(i, holder)
  39. return dupe
  40. def tropism(pt, vector, factor):
  41. '''Move point towards another point, as in growing towards a light source (sun)
  42. or a food source (roots)
  43. '''
  44. P = pt.position()
  45. vector = vector.normalized()
  46. pt.setPosition(P + (1*factor) * vector)
  47. def move_along_normal(pt, factor):
  48. P = pt.position()
  49. #P = hou.Vector3(P)
  50. # be careful - this returns a value you cant simply add to P, must convert it to a hou.Vector3
  51. N = pt.attribValue("N")
  52. N = hou.Vector3(N)
  53. pt.setPosition(P + (1*factor) * N)
  54. def gravity(pt, factor):
  55. '''Convenience function to move down a point, multiplied by diameter.'''
  56. P = pt.position()
  57. # The downforce D
  58. D = (1 - pt.attribValue('Diameter'))*factor
  59. G = hou.Vector3([0, D, 0])
  60. pt.setPosition(P - G)
  61. def create_random_vector():
  62. rx = (random.random()-0.5)*2
  63. ry = (random.random()-0.5)*2
  64. rz = (random.random()-0.5)*2
  65. return hou.Vector3([rx, ry, rz])
  66. def add_noise_to_normal(pt, factor):
  67. N = pt.attribValue("N")
  68. N = hou.Vector3(N)
  69. rx = (random.random()-0.5)*2
  70. ry = (random.random()-0.5)*2
  71. rz = (random.random()-0.5)*2
  72. rvec = hou.Vector3([rx*factor, ry*factor, rz*factor])
  73. newN = rvec + 1 * N
  74. pt.setAttribValue("N", newN)
  75. return pt
  76. def rotate_normal(pt, factor, seed):
  77. N = pt.attribValue("N")
  78. N = hou.Vector3(N)
  79. x = (random.random()-0.5)*2
  80. y = (random.random()-0.5)*2
  81. z = (random.random()-0.5)*2
  82. print 'p random x {0} y {1} z {2}'.format(x, y, z)
  83. x = (hou.hmath.rand(seed+12*43)-0.5)*2
  84. y = (hou.hmath.rand(seed+2+2*3)-0.5)*2
  85. z = (hou.hmath.rand(seed+33*2)-0.5)*2
  86. print 'h random x {0} y {1} z {2}'.format(x, y, z)
  87. rvec = hou.Vector3([x*factor, y*factor, z*factor])
  88. newN = rvec + 1 * N
  89. pt.setAttribValue("N", newN)
  90. return pt
  91. def get_points_by_attrib_value(attr, val):
  92. plist = []
  93. for p in geo.iterPoints():
  94. if p.attribValue(attr) == val:
  95. plist.append(p)
  96. return plist
  97. def get_highest_numeric_attr_val(points, attr):
  98. val = 0.0
  99. for p in points:
  100. v = float(p.attribValue(attr))
  101. if v > val:
  102. val = v
  103. return val
  104. branchNum = 0
  105. def step(pointers, stepseed):
  106. """
  107. step through all head pointers (aka growing branches)
  108. """
  109. updatedPointers=[]
  110. for pointer in pointers:
  111. #print 'pointer is point w/ # ' + str(p.number())
  112. newP = duplicate_point(pointer)
  113. #print 'copy is point w/ # ' + str(newP.number())
  114. updatedPointers.append(newP)
  115. newP.setAttribValue('BranchPoint', newP.attribValue('BranchPoint')+1)
  116. # Should we branch?
  117. # TODO allow multiple branches at once
  118. rnd = random.random()
  119. rnd = hou.hmath.rand(stepseed + 11)
  120. # BRANCH OFF
  121. if rnd > 0.8:
  122. # Dirty, find a better way soon!
  123. global branchNum
  124. branchNum += 1
  125. global treeInfo
  126. #treeInfo['totalbranches']+=1
  127. #print 'branch'
  128. branchP = duplicate_point(pointer)
  129. branchP.setAttribValue('BranchPoint', 0)
  130. b = branchP.attribValue('Branch')
  131. branchP.setAttribValue('Branch', branchNum)
  132. #branchP.setAttribValue('Branch',b+1)
  133. g = branchP.attribValue('Generation')
  134. branchP.setAttribValue('Generation', g + 1)
  135. updatedPointers.append(branchP)
  136. stepseed += 1
  137. newP = rotate_normal(newP, 1.7, stepseed)
  138. # now move along normal
  139. move_along_normal(newP, 1)
  140. v = hou.Vector3(0, 1, 1)
  141. tropism(newP, TROPISM, TROPISMFACTOR)
  142. gravity(newP, GRAVITYFACTOR)
  143. newP.setAttribValue('Diameter', newP.attribValue('Diameter')*0.7)
  144. stepseed += 1
  145. print 'seed', stepseed
  146. return updatedPointers
  147. x = step(pointers, 1)
  148. seed = SEED
  149. for arsch in range(GENERATIONS):
  150. #seed=1
  151. x = step(x, seed)
  152. seed += 1
  153. # until there is a better way, 'postprocess' the tree to gain info that is not present at runtime.
  154. def postprocess():
  155. tInfo = {}
  156. '''
  157. for n in range(0,branchNum):
  158. for p in geo.iterPoints():
  159. #print p
  160. print p.attribValue('BranchPoint')
  161. #if p.attribValue('Branch') == n:
  162. # print p.attribValue('BranchPoint')
  163. '''
  164. for p in geo.iterPoints():
  165. curBranch = p.attribValue('Branch')
  166. tInfo['branch'] = curBranch
  167. print '[[[[[[[[[[***]]]]]]]]]]'
  168. totalBranches = int(get_highest_numeric_attr_val(geo.iterPoints(), 'Branch'))
  169. for i in range(0,totalBranches):
  170. # go through all branches
  171. max = 0
  172. for p in get_points_by_attrib_value('Branch', i):
  173. v = p.attribValue('BranchPoint')
  174. if v > max:
  175. max = v
  176. for p in get_points_by_attrib_value('Branch', i):
  177. bp = p.attribValue('BranchPoint')
  178. #print max
  179. #print bp
  180. try:
  181. relpos = float(bp)/float(max)
  182. except:
  183. relpos = 0
  184. #p.setAttribValue('NormalizedPosition',1)
  185. p.setAttribValue('NormalizedPosition', float(relpos))
  186. #print 'branch {0} points {1}'.format(i,max)
  187. # TODO and thoughts
  188. # in addition to the branch 'id' we need a distance of each point of a branch towards it's parent.
  189. # useful: how many 'levels' are we away from the main parent? > point needs to have parent id, increment on branch
  190. # roots
  191. # DIAMETER should decrease by a step after each branch