前幾天看到機器貓的一片博客講到了用曲線填充幾何體的方法,其中很受啟發的地方是按照他的叫做“彈彈彈”的方法,其實就是raytrace的一個物理上的實現。這里是原博客地址: http://blog.csdn.net/cuckon/article/details/43710653
他應該是在maya里面實現的這一方法,出於好奇於是嘗試看能不能在Houdini里面也作出類似的效果出來,純粹是照虎畫貓了。
這是完成的效果圖:
接下來談談在Houdini里面實現的方法:
1;首先當然需要實現所謂“彈彈彈”的方法,這個主要是碰撞反彈的物理模型,查閱資料有發現vex里面有兩個函數能夠起到關鍵性作用。第一個是intersect(),這個函數能夠計算出某一點沿着法線方向射到另一幾何體表面的坐標值,那么現在知道了碰撞之后下一個“彈”的起始點了。還是一個關鍵矢量就是彈完之后的反射方向,vex里面恰好有個函數叫reflect()專門就干這個是的,輸入入射法線和反彈表面法線就能直接得出反射法線。
既然這兩個關鍵的因素都能在vex里面完成那相當於問題就解決了一半,另外無非就是設定最多反彈多少次以及判定要是沒有反射了也直接結束。
值得注意的是,在houdini里面如果直接用intersect求出的位置來進行下一次反彈的計算時,出錯的概率是非常大的。原因在於該點就是在反射表面上的。在這種點與面重疊的情況下,intersect就搞不清下一個碰撞點是該點本身還是要算其他地方。所以在進行下一次交叉運算之前把這個位置稍微加上一點點反彈方向的向量,這樣下一個位置又精確有保證了不會出現重疊的錯誤。
下面是在attrib wrangle里面實現的代碼,選擇的是在detail級別下,所以只需要計算一次,效率還是非常高的:
float radius = 1000;
int maxIterate = ch("maxNumber");
vector origPosition = point(geoself(), "P", 0);
vector origNormal = point(geoself(), "N", 0);
origNormal *= radius;
//get the reflection normal direction
vector getReflection(vector origPosition; vector nextPosition; int primNum){
vector direction, primNormal;
vector reflect;
direction = normalize(nextPosition - origPosition);
primNormal = prim(1, "N", primNum);
reflect = reflect(direction, primNormal);
return reflect;
}
int flag = 0;
while(flag < maxIterate){
int primNum;
int pointNum;
float u, v;
vector nextPosition,nextNormal;
//find the intersection point
primNum = intersect(1, origPosition, origNormal, nextPosition, u, v);
if(primNum != -1){
i@primNum = primNum;
pointNum = addpoint(geoself(), nextPosition);
nextNormal = getReflection(origPosition, nextPosition, primNum);
setattrib(geoself(), "point", "N", pointNum, 0, nextNormal, "set");
//extend the original position out of the surface a little bit
//this way can avoid some errors from the intersection
origPosition = nextPosition + normalize(nextNormal)*0.00001;
origNormal = normalize(nextNormal) * radius;
}if(primNum == -1){
break;
}
flag += 1;
}
2:第二個問題是把計算出來的點用曲線的方式穿起來。個人感覺其實這才是這個效果的難點地方,vex里面沒有提供在幾何體級別下操作曲線的方法,但是hou模塊中有直接操作曲線的createNURBSCurve和createBezierCurve,順着這個思路,那么接下來的工作就緒要轉入到python中去進行了。
最開始的時候我使用了createNURBSCurve這個函數來把所有之前生成的點連接起來,但是有一個很大的問題就是除了起點和末點其他點都不在曲線身上,雖然這是NURBS的特點,但效果肯定不能使用這個方法。為了解決這個問題也查找了不少資料,最后確定在了BezierCurve曲線上面,這是一個非常特殊的樣條曲線,可以理解為BezierCurve是B樣條曲線的特例,而B樣條曲線是NURBS的一個特例。而且正好hou模塊里面有生成這個曲線的方法,如下圖:
使用的要求就是非閉合曲線的生成點數量必須滿足3n+1,其中曲線會穿過3n的點,其他點都會成為權重點。另外為了達到穿過這些點的時候曲線的曲率是連續的,權重點和對應穿過的點必須保證在同一條直線上。基於這些條件下圖為我能想到的實現方法:
運用這個模型開始在python里面添加新的點,從而使生成的曲線能夠穿過之前“彈”出來的點,下面是在python中的代碼:
node = hou.pwd()
geo = node.geometry()
#get the weight value from the interface
weight = node.evalParm("weight")
# Add code to modify contents of geo.
# Use drop down menu to select examples.
#get all the point instances from the geo itself
points = geo.points()
pointNum = len(points)
#define the up direction for cross product
up = hou.Vector3(0, 1, 0)
#put all the new points in this list
newPointList = []
# the function to get the center point between two positions
def getCenterPosition(firstPosition, secondPosition):
centerPosition = (firstPosition + secondPosition) * 0.5
return centerPosition
#the function to get the second or last second point position
def getSecondPosition(currentPosition, nextPosition, thirdPosition):
mirrorDirection = nextPosition - thirdPosition
tmpDirection = currentPosition - nextPosition
mirrorLength = tmpDirection.length()
mirrorPosition = mirrorLength * mirrorDirection.normalized() + nextPosition
secondPosition = (getCenterPosition(mirrorPosition, currentPosition) - currentPosition) * 0.2 + currentPosition
return secondPosition
# Set the weight for Bezier Curve
def setWeight(currentPosition, weightPosition, weight):
direction = weightPosition - currentPosition
direction *= weight
newWeightPosition = currentPosition + direction
return newWeightPosition
if pointNum >= 3:
#create the new position list based on the original points
for i in range(0, pointNum):
#for the first point
if i == 0 :
#to find the position of the second position
currentPosition = points[i].position()
nextPosition = points[i+1].position()
thirdPosition = points[i+2].position()
secondPosition = getSecondPosition(currentPosition, nextPosition, thirdPosition)
#add the first and second point into the list
newPointList.append(currentPosition)
newPointList.append(secondPosition)
#for the last point
elif i == pointNum - 1:
#to find the position of the last second position
currentPosition = points[i].position()
nextPosition = points[i-1].position()
thirdPosition = points[i-2].position()
secondPosition = getSecondPosition(currentPosition, nextPosition, thirdPosition)
#add the first and second point into the list
newPointList.append(secondPosition)
newPointList.append(currentPosition)
#for the rest of points
else:
#find the center positions between the previous and next point
currentPosition = points[i].position()
previousPosition = points[i-1].position()
nextPosition = points[i+1].position()
previousCenterPosition = getCenterPosition(currentPosition, previousPosition)
nextCenterPosition = getCenterPosition(currentPosition, nextPosition)
#get the new center position based on the two center positions calculated above
newCenterPosition = getCenterPosition(previousCenterPosition, nextCenterPosition)
#get the displacement value and use it to push the two center positions out
#this method can make sure the Bezier Curve can pass through the original point positions
displacement = currentPosition - newCenterPosition
newPreviousCenterPosition = previousCenterPosition + displacement
newPreviousCenterPosition = setWeight(currentPosition, newPreviousCenterPosition, weight)
newNextCenterPosition = nextCenterPosition + displacement
newNextCenterPosition = setWeight(currentPosition, newNextCenterPosition, weight)
#add the new positions to the list
newPointList.append(newPreviousCenterPosition)
newPointList.append(currentPosition)
newPointList.append(newNextCenterPosition)
#clean the original points
geo.deletePoints(points)
#create the curve instance and sign the new positions to this curve one by one
curve = geo.createBezierCurve(len(newPointList))
for i in range(0, len(newPointList)):
vertex = curve.vertices()[i]
position = newPointList[i]
vertex.point().setPosition(position)
if pointNum < 3:
geo.deletePoints(points)
在實現了一條曲線的原型之后,把這個方法放到foreach里面循環一下就能夠實現很多條曲線纏繞的效果了,注意的是原始點也不能夠是貼在反彈幾何體的表面的,最好把位置加上一點點法向量的矢量值。