python实现四元数轮子(motionBuilder)
之前已经在unity引擎里实现了四元数的轮子,随着写dcc插件的需求增加,无论是max,maya,blender,或者今天的主角动捕软件motionBuilder,在涉及到旋转时都离不开四元数。因为一些原因在开始研究motionbuilder并没有找到motionBuilder的内置四元数,一切四元数的计算都是FBVector4d()进行的,以下为motionBuilder内置了一些四元数的计算。
官方提供的几个函数还是太少,于是便开始在motionBuilder里实现自己的四元数轮子的想法,以后想拓展其他的方法也方便。
注意motionBuilder的四元数转欧拉角和欧拉角转四元数的旋转顺序,在查阅开发文档时发现motionBuilder里是xyz的旋转顺序,和unity不同,所以以下代码是按照xyz顺序写的。
不过由于我是python写的,读者若要移植到maya,max,或者blender里也很方便,但是移植的时候要注意旋转顺序是否改变。
已经实现的方法和unity里大同小异:加减乘除的运算符重载,求逆,求共轭,求模长,规范化,2个朝向转四元数,四元数转欧拉角,任意方向经过四元数旋转后得到一个新方向,欧拉角转四元数。该四元数轮子节省了本人大量的时间成本,在motionBuilder里写的十多个插件都是基于该四元数轮子进行开发的,希望它能帮助到motionBuilder开发插件的读者。
作为autodesk家族的一员,和遍地开花的maya,max的工具开发相比,motionBuilder的资料几乎找不到,不过随着对动画质量的要求提高,动捕也越来越会成为各个公司的标配,motionbuilder希望以后有更多人重视起来吧,如果您也是一位motionBuilder的插件开发者,欢迎技术交流沟通!
#encoding=UTF-8
from pyfbsdk import *
import math
def clamp(value,minValue,maxValue):
return max(minValue,min(maxValue,value))
def clamp01(value):
return clamp(value,0,1)
# motionBuilder 自带的四元数不太好用 自定义一套四元数 后面根据需求可以增加方法
class Quaternion:
def __init__(self,FBVector):
self.x=FBVector[0]
self.y=FBVector[1]
self.z=FBVector[2]
self.w=FBVector[3]
@staticmethod
def Identity():
return Quaternion([0,0,0,1])
def xyzw(self):
return FBVector4d(self.x,self.y,self.z,self.w)
def xyz(self):
return FBVector3d(self.x,self.y,self.z)
def __getitem__(self,key): #索引器
if key==0:
return self.x
elif key==1:
return self.y
elif key==2:
return self.z
elif key==3:
return self.w
def __add__(self,quaternion):
result=Quaternion(self.xyzw())
result.x+=quaternion.x
result.y+=quaternion.y
result.z+=quaternion.z
result.w+=quaternion.w
return result
def __sub__(self,quaternion):
result=Quaternion(self.xyzw())
result.x-=quaternion.x
result.y-=quaternion.y
result.z-=quaternion.z
result.w-=quaternion.w
return result
def __mul__(self,quaternion):
result=Quaternion(self.xyzw())
result.x=self.w*quaternion.x+self.x*quaternion.w-self.y*quaternion.z+self.z*quaternion.y
result.y=self.w*quaternion.y+self.y*quaternion.w+self.x*quaternion.z-self.z*quaternion.x
result.z=self.w*quaternion.z+self.z*quaternion.w-self.x*quaternion.y+self.y*quaternion.x
result.w=self.w*quaternion.w-self.x*quaternion.x-self.y*quaternion.y-self.z*quaternion.z
return result
def Divides(self,quaternion):
result=Quaternion(self.xyzw())
return result*(quaternion.Inversed())
def MagnitudeSqr(self):
return pow(self.x,2)+pow(self.y,2)+pow(self.z,2)+pow(self.w,2)
def Magnituded(self):
return pow(self.MagnitudeSqr(),0.5)
def Star(self):
result=Quaternion(self.xyzw())
result.x=-self.x
result.y=-self.y
result.z=-self.z
result.w=self.w
return result
def Inversed(self):
result=Quaternion(self.xyzw())
result=result.Star()
moder=self.MagnitudeSqr()
if moder>0.00001:
result.x/=moder
result.y/=moder
result.z/=moder
result.w/=moder
return result
else:
return self.Identity()
def __str__(self):
return str(self.x)+ +str(self.y)+ +str(self.z)+ +str(self.w)
def Normalize(self):
moder=self.Magnituded()
if moder>0.00001:
self.x/=moder
self.y/=moder
self.z/=moder
self.w/=moder
else:
return self.Identity()
def Normalized(self):
result=Quaternion(self.xyzw())
moder=result.Magnituded()
if moder>0.00001:
result.x/=moder
result.y/=moder
result.z/=moder
result.w/=moder
return result
else:
return self.Identity()
@staticmethod
def FromToRotation(fromDirection,toDirection):
if fromDirection==FBVector3d(0,0,0)or toDirection==FBVector3d(0,0,0):
return Quaternion.Identity()
fromDirection.Normalize()
toDirection.Normalize()
if FBVector3d.IsEqual(fromDirection,toDirection):
return Quaternion.Identity()
aixs=FBVector3d.CrossProduct(fromDirection,toDirection)
sinA=aixs.Length()
cosA=FBVector3d.DotProduct(fromDirection,toDirection)
sinHalfA=pow(((1-cosA)*0.5),0.5)
cosHalfA=sinA/(2.0*sinHalfA)
aixs.Normalize()
aixs=aixs*sinHalfA
return Quaternion(FBVector4d(aixs[0],aixs[1],aixs[2],cosHalfA))
@staticmethod
def FromEular(eular):
Deg2Rad =math.pi/180.0;
Cx =math.cos(eular[0]/2.0*Deg2Rad);
Cy =math.cos(eular[1]/2.0*Deg2Rad);
Cz =math.cos(eular[2]/2.0*Deg2Rad);
Sx =math.sin(eular[0]/2.0*Deg2Rad);
Sy =math.sin(eular[1]/2.0*Deg2Rad);
Sz =math.sin(eular[2]/2.0*Deg2Rad);
qX=Quaternion(FBVector4d(Sx,0.0,0.0,Cx));
qY=Quaternion(FBVector4d(0.0,Sy,0.0,Cy));
qZ=Quaternion(FBVector4d(0.0,0.0,Sz,Cz));
result=qX*qY*qZ;#MotionBulider RotateOrder xyz
return result;
def RotateDirection(self,direction):#multi vector3 使得Vector3经过该四元数变换得到新的vector3
result=Quaternion(self.xyzw())
result.Normalize()
qDirection=Quaternion(FBVector4d(direction[0],direction[1],direction[2],0))
resultDirection=result.Inversed()*qDirection*result
return FBVector3d(resultDirection.x,resultDirection.y,resultDirection.z)
def Euler(self): #四元数转欧拉角 按照xyz的旋转顺序进行计算
euler=FBVector3d(0.0,0.0,0.0)
q=Quaternion(self.xyzw())
q.Normalize()
euler[0]=math.atan2(2*(q.w*q.x+q.y*q.z),1-2*(q.x*q.x+q.y*q.y))
euler[1]=math.asin(clamp( 2*(q.w*q.y-q.x*q.z),-1,1))
euler[2]=math.atan2(2*(q.w*q.z+q.x*q.y),1-2*(q.y*q.y+q.z*q.z))
Rad2Deg =180.0/math.pi
euler*=Rad2Deg
return euler
以上皆为本人个人推导,不具备科学严谨性,其中也肯定有本人未发现的错误,仅供参考,欢迎提出见解。