Maya API开发【1】自定义图形


Shapes in Maya
    maya中的shapes是显示在3D中可选择的DAG object。Meshs, Nurbs surfaces, curves,locator仅仅是shape的一部分例子而已。shapes也是DG node,该node可以拥有属性并能连接到其他node。
    shapes可以看做是geometry的容器。这个容器的作用就是为交互显示和操作而显示geometry。
    shapes既被控制点cvs形成也被其控制。控制点通常是shape的属性,表现为点数组。控制点变成可交互后,就可以选择和并通过和component连接来控制。
    Components包含两方面信息,类型和索引或者范围。例如,一个mesh的顶点component。这个component代表了mesh shape的顶点(pnts)属性。vtx[0] 代表了mesh中的顶点0,vtx[0:7]代表了前8个顶点。
    定义surface的shape,比如 Nurbs surface和mesh支持硬件材质,交互环境下的硬件光照,还有硬件渲染缓冲区。
  1. user-defined shapes
    自定义shape以及相关的shapes都是为了给自定义的数据一个壳,让其作为一个DAG(DG)节点,或者可以讲数据传送给DG。
    MPxSurfaceShape就可以让我们用Maya API来写shape。写shape 节点和写独立节点类似,事实上用户定义的shape(MPxSurfaceShape)继承自MPxNode。
    MPxSurfaceShape和MPxNode最大的区别就是额外的component处理,drawing,selection函数。可重载的函数被分成了两个类,DG节点函数都在MPxSurfaceShape,绘制drawing和选择select函数在MPxSurfaceShapeUI中。
    用户自定义shape非常适合基于控制点的shape比如polygonal mesh或者基于样条线的物体。但这不意味着你不能写其他种类的shape,仅说明了MPxSurfaceShape是为基于控制点的shape准备的。
    注册shape使用MFnPlugin::registerShape函数。此函数和registerNode类似,但包含了一个额外的参数说明了MPxSurfaceShapeUI类的creator。注销函数使用MFnPlugin::deregisterNode,这和MPxNode类的注销类似。

Shape classes
    下面列出了你需要继承的代理类:
  •     MPxSurfaceShape MPxComponentShape(必须): DG node 类
  •     MPxSurfaceShapeUI(必须): 绘制和选择 drawing selection
  •     MPxGeometryIterator(可选): component的迭代器。
  •     MPxGeometryData(可选): 用来将数据传过DG
  •     MTextureEditorDrawInfo(可选): 被MPxSurfaceShapeUI的drawUV()函数用来说明当前物体的绘制状态。
    MPxSurfaceShape和MPxSurfaceShapeUI是强制使用的。shape的主要功能被分别放在两个类里面,为的是将drawing,selection的代码和 评估的代码分开。
  1. MPxSurfaceShape 或者 MPxComponentShape(必须)
    • 这个类定义了shape中非UI的部分。这个类的目的就是在Maya的DAG中代表用户定义的geometry。MPxComponentShape类集成自MPxSurface,其支持带component的shape。
  2. MPxSurfaceShapeUI(必须)
    • 这个类定义了shape中drawing和selection的部分。
  3. MPxGeometryIterator(可选)
    • 这个类提供了自定义shape的控制点迭代器。此迭代器查询并设置自定义geometry中的点。当shape中的component和属性相连时,才会使用迭代器。
  4. MPxGeometryData(可选)
    • 此类为你shape的geometry提供了一个容器,其可以在DG连接中使用。将你的geometry的所有信息作为data块传送要比分别传送效率高。如果自定义shape想和maya的deformer交互,你就必须给自定义shape提供data class的输入, attribute作为输出。
  5. MTextureEditorDrawInfo(可选)
    • 此函数被MPxSurfaceShapeUI的drawUV函数使用来说明当前UV的绘制状态。API开发者必须重载MPxSurfaceShapeUI的canDrawUV方法来获得drawUV的回调。

With a shape
    写一个shape的目的就是将自己定义的geometry类以DAG 对象的形式整合到maya里面。
    将几何相关的信息保存在自己的geometry类中是个好主意。这样将geometry表示成DG data就更容易了。在自己的geometry类中保存几何信息将让绘制drawing更加容易,因为你必须将几何信息作为绘制数据传送给maya的绘制队列。
  1.     从那开始
    • 用户定义的shape要比DG节点复杂,因为必须考虑额外的 drawing,selection,componet函数。在动手写代码前做好设计是好习惯。这包括定义输入input,输出output属性,决定属性间的关系。
    • 最简单的shape就是没有component或者geometry数据的,例如,一个圆形输入为半径r,或许还有x,y分量,并从这些数据计算geometry,最后送到openGL渲染。
    • 分阶段来写自定义shape也不错。第一步,从MPxSurfaceShape和MPxSurfaceShapeUI。写MPxSurfaceShape和其他MPxNode类一样。额外的虚函数可以作为函数来重载。
  2. 注册和注销shape
    • 注册shape和注册DG 节点类似。唯一的差别是shape有一个单独的UI类,需要和shape节点一起注册。MFnPlugin::resgisterShape函数是用来注册shape的。注销shape和注销节点完全一样。下面是一个例子
MStatus initializePlugin( MObject obj )
{
 MFnPlugin plugin( obj,"Autodesk","2.0", "Any");
 return plugin.registerShape( "yourShape", yourShape::id,
 &yourShape::creator,
 &yourShape::initialize,
 &yourShapeUI::creator );
}
MStatus uninitializePlugin( MObject obj )
{
 return plugin.deregisterNode( yourShape::id );
}                                                        
 
 
 
  
Drawing and refresh
    drawing是一个两个步骤的过程。第一步,几何图元和材质被预估,绘制所需的信息都被放到了队列里面。第二步,就是OpenGL绘制阶段。 这两部允许利用maya的多线程绘制来提升性能。
    当摄像机变化或者视口被刷新时,就会重绘。当重绘发生时,MPxSurfaceShapeUI::getDrawRequest被调用。这是maya询问哪些数据需要绘制的方式。在这个函数内部,你需要查询绘制状态,那就使用MDrawInfo,然后重建一个新的绘制请求(MDrawRequest)放到队列里面。你可能想在此函数里将多个绘制请求放到队列里,在共享模式下,如果shape被选中,你可能会添加一个渲染有材质物体的命令,另一个请求则要在彩色物体上面绘制线框。
    绘制数据MDrawData保存了自定义shape的数据,这些maya并不完全清楚。绘制数据就像一个容器,将自定义shape的数据传到绘制请求队列里。你创建的每个绘制请求,都要添加一个包含几何信息的绘制数据包,此包在MPxSurfaceShapeUI::draw中使用。
    为了创建绘制数据,使用MPxSurfaceShapeUI::getDrawData,并使用MDrawRequest::setDrawData将其添加到绘制队列。下面的代码说明了如何创建绘制请求并绘制你的几何体。
void yourShapeUI::getDrawRequests( const MDrawInfo & info,
 bool objectAndActiveOnly,
 MDrawRequestQueue & queue )
{
 MDrawData data;
 MDrawRequest request = info.getPrototype( *this );
 yourShape* shapeNode = (yourShape*)surfaceShape();
 yourGeom* geom = shapeNode->geometry();
 getDrawData( geom, data );
 request.setDrawData( data );
...
}

   绘制请求(MDrawRequest)应该在重载的MPxSurfaceShapeUI::getDrawRequests里面创建。一旦请求被创建,合适的set方法就应该定义什么该被绘制。然后就用MDrawRequestQueue::add将其添加到绘制请求队列中。
     
    maya会处理绘制请求队列,对每个绘制请求都会调用对应的绘制函数。这种情况下,你自定义的MPxSurfaceShapeUI::draw会被调用。
 enum {
 kDrawVertices, // component token
 kDrawWireframe,
 kDrawWireframeOnShaded,
 kDrawSmoothShaded,
 kDrawFlatShaded,
 kLastToken
 };

  1.     Drawing in shaded mode
    • 支持渲染模式显示也是两个步骤。第一个步骤在你的getDrawRequests函数,这里你必须“预估”或者设置材质,这样在绘制时才会显示。第二个步骤发生在draw函数里,你必须设置视口来显示材质。
    • 下面的代码显示了如何在getDrawRequests中设置材质,如果绘制请求是shaded显示模式。
    •        
             
      MDagPath path = info.multiPath(); M3dView view = info.view(); MMaterial material = MPxSurfaceShapeUI::material( path ); material.evaluateMaterial( view, path ); if ( material.materialIsTextured() ) { material.evaluateTexture( data ); } request.setMaterial( material );
  • 在你的绘制函数里需要设置场景视口,这样就可以显示材质了。这使用MMaterial::setMaterial。让场景视口显示纹理,使用MMaterial::applyTexture。下面是示例代码:
    • void yourShapeUI::draw( const MDrawRequest& request,
       M3dView & view ) const
      {
       ...
       MMaterial material = request.material();
       material.setMaterial( request.isTransparent() );
       drawTexture = material.materialIsTextured();
       if ( drawTexture ) glEnable(GL_TEXTURE_2D);
       if ( drawTexture ) {
       material.applyTexture( view, data );
       }
       ...
      }
  • 注意所有的OpenGL调用都必须在M3dView::beginGL()和M3dView::endGL()中间。

Selection
    当在maya里面选中一个物体时,就会根据摄像机角度和鼠标位置创建一条选择射线。选择有两个步骤,第一,相交射线检测每个物体的碰撞盒,当和碰撞盒相交时调用物体的select函数。
    自定义shape的选择函数是通过重载MPxSurfaceShapeUI的select函数实现的。你必须重载此函数,为特定的选择队列添加对象,MSelectInfo负责提供选择状态信息。
    查看apiMeshShape中的apiMeshShapeUI::select和apiMeshShapeUI::selectVertices来了解物体和物体组件的选择。
Components
    有些物体需要虚拟反馈和操作物体属性,比如基于控制点的物体就必须把component和attribute关联起来。四边形mesh或者样条线曲面就有控制点,其可被选择和编辑。这些控制点是作为属性存在于物体上的,在maya里面被作为component来显示。
    components包含两方面的信息,component的类型和索引值或者范围。一个列子就是,mesh的顶点component vtx[0]就代表了顶点0,vtx[0:7]代表了前8个顶点。
    用来创建,编辑,和查询components的类:
  •     MFnComponent
  •     MFnDoubleIndexedComponent
  •     MFnSingleIndexedComponent
  •     MFnTripleIndexedComponent
    根据索引的维度可以讲componet分成3类。类型为 single, double,triple索引。这些类型的例子 mesh顶点(single), Nurbs surface(double) lattice点(triple)。
    componet可以被标记成完全的,表示componet代表全部的索引从0到numElements-1.
  1.  Writing a Component Shape
    • 如果你想创建一个支持component的曲面,那么MPxComponentShape就比MPxSurfaceShape好。MPxComponetShape继承自MPxSurfaceShape并提供了操作component的基本功能。
    • 如果你想编辑图形上显示界面,请继承MPxSurfaceShapeUI。这个类允许绘制和选择图形中任何componet。
  2. Mapping attributes to components
    • 在maya里,componets是作为字符串的。每类componet都有不同的字符名字。在API层面,componet是根据他们的API类型来区分的(请看MFn.h). 例如,一个mesh的顶点componet在maya里可以被解读为vtx[0],在插件里它是作为一个MObject,其类型为MFn::kMeshVertComponent. 索引信息可以通过继承MFnComponent来抽取。
    • 想将一个component和自定义shape的attribute相连,你需要选maya已经存在的componet类型,重载MPxSurfaceShape::componetToPlugs来将componet类型转换成plugs。
    • 下面是一个列子,将mesh的顶点componet和shape的mControlPoints attribute相连。
    • void yourShape::componentToPlugs( MObject& component,
       MSelectionList& list )const
      {
       if ( component.hasFn(MFn::kMeshVertComponent) ) {
       MFnSingleIndexedComponent fnVtxComp( component );
       MObject thisNode = thisMObject();
       MPlug plug( thisNode, mControlPoints );
       int len = fnVtxComp.elementCount();
       for ( int i = 0; i < len; i++ ) {
       MPlug vtxPlug = plug.elementByLogicalIndex(
       fnVtxComp.element(i) );
       list.add( vtxPlug );
       }
       }
      }

  3. Component matching
    • 属性可以在mel里面声明为字符串。自定义shape必须确保这些字符串和实际物体,索引对应起来。MPxSurfaceShape::matchComponent就是这个目的。

    • virtual MatchResult matchComponent( const MSelectionList& item,
       const MAttributeSpecArray& spec,
       MSelectionList& list );

    • 此方法从组件的字符串名字确保物体存在,并将对应的物体添加到选择链中。选择命令比如select shape1.vtx[0:7]被确认后,对应的component被添加到了选择链里面。
    • 属性规范(MAttributeSpec)是一个提供获取属性描述信息的类。其包括属性名,索引,范围。

  4. Componet iteration
    • 想要maya能get和setcomponent的位置,就要重载MPxGeometryIterator为自己的图形定义迭代器。图形迭代器是在物体被选中后,进行平移、缩放、旋转操作时这些控制器用来控制自己位置的。形变也同样需要图形迭代器,这需要重载setPoint和point这两个方法。一般情况,你需要重载MPxGeometryIterator的方法如下:
    •  MPxGeometryIterator( void * userGeometry,
       MObjectArray & components );
       MPxGeometryIterator( void * userGeometry,
       MObject & components );
       virtual void reset();
       virtual MPoint point() const;
       virtual void setPoint( const MPoint & ) const;
    • 你必须重载MPxsurfaceShape的方法,将迭代器和自定义图元连接起来。
    •  virtual MPxGeometryIterator* 
       geometryIteratorSetup( MObjectArray&, MObject&, bool );
       virtual bool acceptsGeometryIterator( bool writeable );
       virtual bool acceptsGeometryIterator( MObject&,
       bool, bool );
  5. components的平移、缩放、旋转工具
    • 为了支持平移、旋转、缩放工具,你要重载MPxSurfaceShape::transformUsing方法。其参数是component的矩阵和数组。矩阵就是实际应用的矩阵,component说明了变换使用到的属性索引。
    • 有大量控制点的模型,不要过分依赖maya的计算机制来设置属性值。这种情况下可以直接用MPxNode::forceCache来直接获取节点的datablock,不通过计算直接设置节点的属性。需要注意的是修改属性后,相关联的属性也会被更新。例如,如果一个mesh的顶点变了,法线和包围盒子一样要变。如果想要移动工具正常工作就要重载vertexOffsetDirection。

Tweaks and internal attributes
    有输入历史的图形,例如,图元数据由另一个节点提供,就需要存储顶点的偏移或者扭曲。输入图元每次变化都要重新计算图形。tweak可以存储为单独属性,这样扭曲就可以被添加到形成最终输出的输入顶点上了。
    如果没有输入历史,就不必另存tweak扭曲。顶点移动就可以直接应用到输出曲面。
    通过MFnAttribute::setInternal可以标记属性为内部的。这样就可以重载setAttr和getAttr,用完全不同的方法来处理tweak,就看有没有输入历史了。
    注意:用不用内部属性完全取决于你。使用内部属性不是处理tweak的唯一方法。
    输入历史意味着其他节点给你的图形提供输入数据了。创建节点是独立的,其负责创建图形的特殊数据类型。典型的,一个图形有一个或者多个创建节点。例如,一个polygonal图形有分别创建圆形和立方体的节点。请查看apiMeshCreator中的创建节点。
Geometry data
    为了支持set和形变,自定义图形要能通过属性连接传输自定义数据类型。想要为自己的图形定义点数据,要从MPxGeometryData继承一个新类。其和MPxData很像,但包含支持set(group)和component迭代器的方法。
    MPxGeometryData的作用就是为你的图形数据提供一个壳,这样它就可以像其他数据一样在DG连接中传输。
    如果你的图元数据对应的迭代器,那么将迭代器和你的数据关联起来就可以支持maya的形变。
    想完成上面的功能需要重载下面的函数。
 virtual MPxGeometryIterator* iterator( MObjectArray&
 MObject&, bool );
 virtual MPxGeometryIterator* iterator( MObjectArray&,
 MObject&,
 bool, bool );


File IO
    没有自动数据的图形就不用操心文件IO了。默认情况,节点上的属性都会存下来,如果你将其设为可存储的话(这是默认设置)并且这些属性没有输入连接。如果你想选择性地保存,就重载MPxNode的shouldSave()方法。
    如果你定义了一种新数据类型,就重载MPxData的writeASCII(), writeBinary()吧,不然到文件IO就废了。
    对于支持tweak属性的图形(比如在输入历史上加偏移量),那你就得说清楚哪些数据该存下来了。

Deformers
    变形器作用于图形上的控制点,这些有component的图形必须定义控制点属性。
    为了支持maya的变形器,需要提供如下内容:
  •     MPxGeometryData 继承其类,封装自己的图元。
  •     localShapeInAttr函数
  •     locakShapeOutAttr函数
  •     worldShapeOutAttr函数
  •     MPxGeometryIterator 自己的图元要有一个
  •     match函数
  •     createFullVertexGroup函数
  •     geometryData函数
  1. MPxGeometryData
    • 想要变形器能干活,就必须给自己的数据找个壳封装一下。
  2. localShapeAttr
    • 必须重载此函数来返回与输入历史对应的属性。这个属性的类型要和自己图元类型一样
  3. localShapeOutAttr
    • 必须重载此函数以返回和输出图元对应的属性。属性类型要和自定义的一致。
  4. wordShapeOutAttr
    • 必须重载这个函数来返回属性数组,此数组代表了你输出几何图元的实例。属性类型必须和自定义的一样。数组中的每个元素代表了图形的一个实例。
  5. MPxGeometryIterator
    • 点数据是通过你定义并实现的迭代器来操控的。
  6. match
    • 必须重载这个函数来比对选中的元素类型和实际物体的类型。这被set和deformer使用来确保选中的元素落入vertex这个类型。
  7. createFullVertexGroup
    • 当需要创建一个包含所有顶点的component时,maya会用到此函数。如果你对整个物体变形就会用到它。在object模式下选择模型,并给他添加一个变形器。
  8. geometryData
    • 此函数该为surface返回输入数据。maya会在内部调用此函数来获取模型的group信息。

Marking Menus
    MPxSurfaceShape物体可以有对应的菜单。可以利用mel为node定义菜单的函数来实现,创建格式: $nodeName + "DagMenuProc" . 例子如下:
global proc apiMeshDagMenuProc( string $parent, string $child )
{
	setParent -m $parent;
	// Simple marking menu with 3 items at
	// N north, W west and E east.
	string $cmd = "hide " + $child;
	menuItem -l "Hide" -rp "N" -c $cmd;
	$cmd = "boundingBoxDisplayCtrl(1,\"\");";
	menuItem -l "Show bounding box" -rp "W" -c $cmd;
	$cmd = "boundingBoxDisplayCtrl(0,\"\");";
	menuItem -l "Hide bounding box" -rp "E" -c $cmd;
}
 
  
 
  
 
  
 
 
执行上面的代码,然后在自定义物体上右键,就可以调用菜单命令。

ps: 中午老犯困,翻译点东西换下脑子,水平有限,欢迎讨论。





















评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值