Semi-polite Python worm
April 17th, 2009I was doing so well, placidly retracing my steps through my MEL code and converting it to Python, like a responsible citizen. But then I got impatient, so I skipped straight to the last version of my worm-building code, and Pythonized it all at once using my still-rudimentary Pymel knowledge.
It took a bit longer than I expected. I ran into some funny roadblocks, some because Pymel is still evolving, and some because my brain is. Anyway, it seems to be working, with some small improvements, and only one teensy drawback.
The Python is easier to read and write than the MEL, but it takes eight times longer to run: for 100 pyramids, over 10 runs, an average of 97 seconds versus 12 seconds for the MEL. Whether this is a dirty secret of Pymel or my own ineptitude will take more eptitude to discover.
I may do a side-by-side translation/comparison in the future; there’s a lot of MEL floating around, and some kind of Rosetta stone may be useful. For now, you get the Python.
Code follows:
# start: semi-polite python worm
from pymel import *
import pymel.core.datatypes as dt
import random
startTime=timerX()
# find the center of a face - aka the "centroid"
def centerOfFace(face):
# get locations of all the face's vertices
pos = face.getPoints(space="world")
returnVec = [0, 0, 0]
# add them all together
for vec in pos:
returnVec += vec
# divide by # of vectors for average
returnVec /= len(pos)
return returnVec
# distange between two vectors
def distanceBetween(a, b):
a = dt.Vector(a)
b = dt.Vector(b)
return length(a-b)
def getNormal(someFace):
fc = centerOfFace(someFace)
# returns normal relative to face at origin
norm = someFace.getNormal(space='world')
returnVec = [fc[0]+norm[0], fc[1]+norm[1], fc[2]+norm[2]]
return returnVec
# detect sidedness of a point relative to a face
def isInFront(point, face):
faceNorm = getNormal(face)
facePos = centerOfFace(face)
if (getVectorAngle(point, faceNorm, facePos) <= 90):
return 1
else:
return 0
# get angle between two vectors
def getVectorAngle(aLoc, bLoc, orig):
aLoc = dt.Vector(aLoc)
bLoc = dt.Vector(bLoc)
orig = dt.Vector(orig)
cLoc = aLoc-bLoc
cLocFloor = round(cLoc, 4)
if (cLocFloor[0]==0 and cLocFloor[1]==0 and cLocFloor[2]==0):
return 0
else:
# normalize locations to the face center
aNorm = aLoc-orig
bNorm = bLoc-orig
returnAngle = degrees(angle(aNorm, bNorm))
# round answer down to two decimal places
returnAngle = round(returnAngle, 2)
return returnAngle
# get angle between two vertices with respect to a third point
def getVertexAngle(aVert, bVert, orig):
aVec = dt.Vector(pointPosition(aVert))
bVec = dt.Vector(pointPosition(bVert))
return getVectorAngle(aVec, bVec, orig)
# find the centroid of a cone:
# 1/4 of the way from the base to the peak
def centerOfCone(cone):
coneLoc = cone.t.get()
coneLoc = datatypes.Vector(coneLoc)
topLoc = pointPosition(cone.vtx[3]) # 3 = peak
topLoc = datatypes.Vector(topLoc)
center = ((topLoc-coneLoc)/4)+coneLoc
return center
# find nearby cones
def checkProximity(conePos):
ids = []
global coneList
for i in coneList:
iPos = centerOfCone(i)
iDist = distanceBetween(iPos, conePos)
# detection radius: 2r = 2
if (iDist < 0.70710678118654752440084436210485):
# 1/2*sqrt(2) : definite intersection
return "fail"
elif (iDist < 2.1213203435596425732025330863145):
# 2/3*sqrt(2) : possible intersection, add to list of suspects
ids.append(i)
return ids
def alignPyramids(pyrA, pyrB, fc):
# pick a vertex on each pyramid
newVertex = pyrA+".vtx[0]" # 0 is on the base
oldVertex = pyrB+".vtx[3]" # 3 is the tip
# get angle between two vertices
angle = getVertexAngle(newVertex, oldVertex, fc)
# if not already aligned
if (angle > 0):
# rotate new to align the corners
xform(pyrA, r=1, os=1, ro=[0, angle, 0])
# check the result
angle2 = getVertexAngle(newVertex, oldVertex, fc)
# if still not aligned, go the other way twice
if (angle2 > 0):
angle3 = angle * -2
xform(pyrA, r=1, os=1, ro=[0, angle3, 0])
def copyCone(old):
# make a list of faces on old that aren't the base
faces = [1, 2, 3]
new = ''
while (len(faces) > 0):
# pick a random face
r = random.choice(faces)
# strike face off the list
faces.remove(r)
selectedFace = old.f[r]
fc = centerOfFace(selectedFace)
# copy cone and move to selected face
dupe = duplicate(old)
new = dupe[0]
xform(new, a=1, t=[fc[0], fc[1], fc[2]])
# aim new at face with a temporary normal constraint
tmpConst = normalConstraint(selectedFace, new, aim=(0, 1, 0))
delete(tmpConst)
alignPyramids(new, old, fc)
# collision detection - is area in new occupied?
# reference coneList
global coneList
# check for other cones in search radius
conePos = centerOfCone(new)
suspects = checkProximity(conePos)
if (suspects == "fail"):
delete(new) # start over
new = "fail"
else:
for i in suspects:
if (pyramidsIntersect(new, i) == 1):
# intersection found, try another face
delete(new)
new = "fail"
break
# no intersections? copy is good, clear facelist
if (new == 'fail'):
continue
else:
faces = []
# facelist empty?
return new
# do pyramids intersect?
def pyramidsIntersect(pyrA, pyrB):
# get all vertexes in pyrA
pos = pyrA.getPoints(space="world")
for i in pos:
# for each point in pyrA
if isInPyramid(i, pyrB):
return 1
# not yet? try the other way around
pos = pyrB.getPoints(space="world")
for i in pos:
if isInPyramid(i, pyrA):
return 1
# made it this far? then fail
return 0
def isInPyramid(point, pyramid):
for face in pyramid.faces:
if (isInFront(point, face)):
return 0 # it's outside
# if it's behind all four faces, it's inside
return 1
# initialize global variable
coneList = []
def doit():
# make a tetrahedron
cone = polyCone(r=1, h=1.414214, sx=3)[0]
# move its pivot point to the bottom face
xform(rp=(0, -0.707107, 0))
# move to origin and freeze xforms
xform(t=(0, 0.707107, 0))
makeIdentity(apply=True, t=1)
# initialize cone list
global coneList
coneList = [cone]
totalCones = 100
growCone = 0 # index of cone to grow from
# main creation loop
for i in range(totalCones):
while (1 == 1):
# attempt to grow a new cone
new = copyCone(coneList[growCone])
# if no growth spaces open on that cone
if (new == "fail"):
# remove it from the coneList
del coneList[growCone]
# pick another random cone in the list and try again
growCone = random.randint(0,len(coneList)-1)
else: # success!
setKeyframe(new, attribute = "visibility", v = 0, t = 1)
setKeyframe(new, attribute = "visibility", v = 1, t = i+2)
break
# if success, add new cone to list
coneList.append(new)
growCone = len(coneList)-1
# delete all bottom faces to lighten the load
for i in coneList:
delete(i.f[0])
del coneList
doit()
select(None)
print("done!")
totalTime = timerX(startTime=startTime)
print("Total Time: "+str(totalTime))
# end
« previously: Taste of Surimi | Home | next: Polite shrub »