这个案例是houdini安装后samples文件夹里面官方提供的,操作很简单就是把一个三维的模型某一个轴向设置为0,就是把一个模型拍扁。
这个案例主要讲了在有输入端的时候操作数据的一些注意事项,本地变量的操作,组的操作,几个默认的parm,简单的手动操作DataID,编写一个简单的Guide(就是图上蓝色的哪个网格,这个经常在一些生成点和发射器节点上看到类似的东西),分块操作点数据和一些简单的初学者应当知道的操作。
个人理解了大概百分之60左右。很多注释是我靠着自己的理解写上去的。是给自己做个笔记也希望能给其他人帮助。如果后续达到了更正确的理解我会在其他案例里面更新。
这个范例不是做一个完整的节点,只是给初学者说明一些常规操作的用法。
//防止重复声明宏命令
#ifndef __SOP_Myflatten_h__
#define __SOP_Myflatten_h__
//SOP节点从这里继承
#include
//命名空间防止重复类名
namespace HDK_Sample {
class SOP_Myflatten : public SOP_Node
{
public:
SOP_Myflatten(OP_Network* net, const char* name, OP_Operator* op);
virtual ~SOP_Myflatten();
//对于组的操作
virtual OP_ERROR cookInputGroups(OP_Context &context, int alone = 0);
//构建函数
static OP_Node *myConstructor(OP_Network*, const char*, OP_Operator*);
//参数表
static PRM_Template myTemplateList[];
protected:
virtual bool updateParmsFlags();
//后面的const表示这个函数不会修改该类的任何成员数据的值
//前面的const由于const在*前面所以表示不能通过该指针修改指向数据的值
virtual const char *inputLabel(unsigned idx) const;
//操作节点数据的函数
virtual OP_ERROR cookMySop(OP_Context &context);
//编写一个guide
virtual OP_ERROR cookMyGuide1(OP_Context &context);
private:
void getGroups(UT_String &str) { evalString(str, "GROUP", 0, 0); }
fpreal DIST(fpreal t) { return evalFloat("dist", 0, t); }
int DIRPOP() { return evalInt("usedir", 0, 0); }
//(参数名,参数idx,time)
int ORIENT() { return evalInt("orient", 0, 0); }
fpreal NX(fpreal t) { return evalFloat("dir", 0, t); }
fpreal NY(fpreal t) { return evalFloat("dir", 1, t); }
fpreal NZ(fpreal t) { return evalFloat("dir", 2, t); }
//操作组
const GA_PointGroup *myGroup;
};
} //END HDK_Sample namespace
#endif
#include "SOP_Myflatten.h"
#include
#include
#include
#include
#include
using namespace HDK_Sample;
void
newSopOperator(OP_OperatorTable *table)
{
//table frome
table->addOperator(new OP_Operator(
"hdk_myflatten",
"Myflatten",
SOP_Myflatten::myConstructor,
SOP_Myflatten::myTemplateList,
1,
1,
NULL));
}
//构建函数
OP_Node* SOP_Myflatten::myConstructor(OP_Network* net, const char* name, OP_Operator* op)
{
return new SOP_Myflatten(net, name, op);
}
//构造函数
SOP_Myflatten::SOP_Myflatten(OP_Network* net, const char* name, OP_Operator* op)
: SOP_Node(net, name, op), myGroup(NULL)
{
//手动操作数据ID
//设置为true这个就是比较高级的做法
//这允许我们检查一些可能存在的重复运算
//当数据ID得到妥善处理的情况下可以显著的提高性能
//比如当拓扑没变化的时候
mySopFlags.setManagesDataIDs(true);
//确保输入一个几何体
mySopFlags.setNeedGuide1(true);
}
//析构函数
SOP_Myflatten::~SOP_Myflatten() {}
//参数名
//PRM_Name frome or "SOP_Myflatten.h"
static PRM_Name names[] = {
PRM_Name("usedir", "Use Direction Vector"),
PRM_Name("dist", "Distance"),
};
//参数表
PRM_Template SOP_Myflatten::myTemplateList[] = {
//(类型,参数尺寸,参数名,默认值,字符串后面的向下的小箭头叫做选择列表,范围,回执,sparedata添加选组按钮)
//使用的是重载的第一项No extended type given and no export level given.
PRM_Template(PRM_STRING, 1, &PRMgroupName, 0, &SOP_Node::pointGroupMenu,
0, 0, SOP_Node::getGroupSelectButton(GA_GROUP_POINT)),
//(类型,参数尺寸,参数名,默认值,选择列表为空,范围)
PRM_Template(PRM_FLT_J, 1, &names[1], PRMzeroDefaults, 0, &PRMscaleRange),
//(类型复选框,参数尺寸,参数名)
PRM_Template(PRM_TOGGLE, 1, &names[0]),
//(类型三平面下拉菜单,参数尺寸,参数名,默认值,选择列表)
PRM_Template(PRM_ORD, 1, &PRMorientName, 0, &PRMplaneMenu),
//(类型方向,尺寸3矢量,参数名,默认值Z为1)
PRM_Template(PRM_DIRECTION, 3, &PRMdirectionName, PRMzaxisDefaults),
//(空对象)
PRM_Template(),
};
//设置参数状态是否可用
bool SOP_Myflatten::updateParmsFlags()
{
bool changed;
//(pi=parm iex,参数值)
//(如果use direction vector勾选了设置orientation不可用)
//(如果没勾选设置可用)
changed = enableParm(3, !DIRPOP());
//(如果use direction vector勾选了设置orientation可用)
//(如果没勾选设置不可用)
changed |= enableParm(4, DIRPOP());
return changed;
}
OP_ERROR SOP_Myflatten::cookInputGroups(OP_Context &context, int alone)
{
//这个函数会查找PRMgroupName这个是和parameter直接绑定的
return cookInputPointGroups(
context, //指定上下文,用于绑定到group parameter
myGroup, //该节点的操作组。
//无论上游有多少组在这个节点只使用一个组就是这个myGroup
alone, //如果在cookMySop外调用那么这个将会是true
//true意味着组将用于输入几何
//flase意味着组将用于gdp
true, //允许手动选择设置为组
0, //(default)指定组的parameter索引
-1, //(default)指定组级别的parameter索引
//-1因为没有这个参数,并且我们在设计节点的时候就只支持点组
true, //(default)true表示使用已经存在的组是允许的
//false表示必须指定一个新组
false, //(default)false表示新的组应该是无序的
//true表示新的组应该是有序的
true, // (default) true means that all new groups should be detached, so not owned by the detail;
// false means that new point and primitive groups on gdp will be owned by gdp.
//下面这一条符合当前状态
//If fetchgdp is false, you must supply a gdp to create groups on in the paramerer pgdp.
0 //(default)表示使用哪个输入端如果是单一的
);
}
OP_ERROR SOP_Myflatten::cookMySop(OP_Context &context)
{
//锁定上游//这个函数会在return的时候自动解锁
OP_AutoLockInputs inputs(this);
if (inputs.lock(context) >= UT_ERROR_ABORT)
return error();
//获取时间
fpreal now = context.getTime();
//复制输入的几何体
duplicateSource(0, context);
//绑定变量优先级(detail,prim,pt,vtx)
setVariableOrder(3, 2, 0, 1);
//绑定本地变量到第一个输入
setCurGdh(0, myGdpHandle);
//查找属性并且绑定到本地变量
setupLocalVars();
//确定要处理那些组
//UT_ERROR_ABORT错误意味停止当前操作
//如果当前没有错误并且输入组也没有错误并且myGroup不是空的
if (error() < UT_ERROR_ABORT && cookInputGroups(context) < UT_ERROR_ABORT &&
(!myGroup || !myGroup->isEmpty()))
{
//有组的情况,在组内的操作
//允许用ESC,progress是终止句柄,随时用这个对象进行终止操作
UT_AutoInterrupt progress("Flattening Points");
//可以容纳指定属性类型的数组(只有一个pos属性)
UT_Array positionattribs(1);
UT_Array normalattribs;
UT_Array vectorattribs;
//属性类型变量用于从gdp上面获取属性暂时存到这里
GA_Attribute *attrib;
//获取gdp上的属性并且同时操作所有属性
//按照属性确定的循环次数并且每次循环拿到不同属性的指针
GA_FOR_ALL_POINT_ATTRIBUTES(gdp, attrib)
{
//如果是浮点属性则跳过本次循环
//用于减少不必要的计算
if (!attrib->needsTransform())
continue;
//获得属性类型
GA_TypeInfo typeinfo = attrib->getTypeInfo();
if (typeinfo == GA_TYPE_POINT || typeinfo == GA_TYPE_HPOINT)
{
//得到属性操作句柄
GA_RWHandleV3 handle(attrib);
//确定属性是有效的
if (handle.isValid())
{
//把属性操作手柄添加到数组内
positionattribs.append(handle);
//因为设置了mySopFlags.setManagesDataIDs(true)
//刷新该属性的数据id
attrib->bumpDataId();
}
}
else if (typeinfo == GA_TYPE_NORMAL)
{
GA_RWHandleV3 handle(attrib);
if (handle.isValid())
{
normalattribs.append(handle);
attrib->bumpDataId();
}
}
else if (typeinfo == GA_TYPE_VECTOR)
{
GA_RWHandleV3 handle(attrib);
if (handle.isValid())
{
vectorattribs.append(handle);
attrib->bumpDataId();
}
}
}
GA_Offset start;
GA_Offset end;
//1024个点为单位分块处理
//初始值不重要了就是个it对象
//it.blockAdvance第一次调用start为0,之后每次调用start+1024
//这里start和end虽然是传值进去的但是it这个对象是有权限修改这两个变量的值的
//it.blockAdvance每次调用也会返回一个布尔值决定是否进行下一次迭代
//这样分块处理hdk会自动开启多线程么????
for (GA_Iterator it(gdp->getPointRange(myGroup)); it.blockAdvance(start, end);)
{
//std::cout << "*it=" << *it << std::endl;
//std::cout << "start=" << start << std::endl;
//std::cout << "end=" << end << std::endl;
//esc取消操作
if (progress.wasInterrupted())
break;
//对点进行循环
for (GA_Offset ptoff = start; ptoff < end; ++ptoff)
{
//自动绑定局部变量
myCurPtOff[0] = ptoff;
//获得方向参数
float dist = DIST(now);
//用于在orient模式下控制方向
UT_Vector3 normal;
//如果没有勾选使用usedir
if (!DIRPOP())
{
switch (ORIENT())
{
case 0:
//xy平面
normal.assign(0, 0, 1);
break;
case 1:
//yz平面
normal.assign(1, 0, 0);
break;
case 2:
//xz平面
normal.assign(0, 1, 0);
break;
}
}
//如果勾选了使用dist
else
{
//拿到参数
normal.assign(NX(now), NY(now), NZ(now));
//规格化//c++的库还是保留了一些常用操作的。比如normalize
normal.normalize();
}
//第二层循环对属性进行循环
//pos只有一个。只循环一次
for (exint i = 0; i < positionattribs.size(); ++i)
{
//拿到pos属性
UT_Vector3 p = positionattribs(i).get(ptoff);
//假设使用orient模式y轴压平那么就是xz平面
//dot(normal, p)会得到y轴的偏移值*normal会把normalx和z轴归零
//如果没有勾选dist那么P减去这个值就会把y轴归0
//dist就是个偏移值
p -= normal * (dot(normal, p) - dist);
positionattribs(i).set(ptoff, p);
}
//循环所有法线类型属性
for (exint i = 0; i < normalattribs.size(); ++i)
{
//拿到发现类型属性
UT_Vector3 n = normalattribs(i).get(ptoff);
//法线类型属性设置为单方向的
//如果是反方向就是-的normal
if (dot(normal, n) < 0)
n = -normal;
else
//正轴向既直接使用normal
n = normal;
normalattribs(i).set(ptoff, n);
}
//循环所有矢量类型属性
for (exint i = 0; i < vectorattribs.size(); ++i)
{
UT_Vector3 v = vectorattribs(i).get(ptoff);
//和set pos一样。只是去掉了偏移值
v -= normal * dot(normal, v);
vectorattribs(i).set(ptoff, v);
}
}
}
}
//重置本地变量
//帮助文档编写sop下的sop中的局部变量,并没有看懂。
resetLocalVarRefs();
return error();
}
//编写GUIDE
OP_ERROR SOP_Myflatten::cookMyGuide1(OP_Context &context)
{
//分段数
const int divs = 5;
//自动锁定上游。自动的意思是这个函数在return的时候会自动解锁
OP_AutoLockInputs inputs(this);
//如果锁定全部上游出现错误则返回错误
if (inputs.lock(context) >= UT_ERROR_ABORT)
return error();
//获取时间
float now = context.getTime();
//清除掉MyGuide1
//避免每次调参数重复生成guide
myGuide1->clearAndDestroy();
//获得dist参数
float dist = DIST(now);
//默认为z轴
float nx = 0;
float ny = 0;
float nz = 1;
//如果没有使用usedir就判断orient模式下的平面
if (!DIRPOP())
{
switch (ORIENT())
{
case 0: // XY Plane
nx = 0; ny = 0; nz = 1;
break;
case 1: // YZ Plane
nx = 1; ny = 0; nz = 0;
break;
case 2: // XZ Plane
nx = 0; ny = 1; nz = 0;
break;
}
}
//否则用usedir赋值
else
{
nx = NX(now); ny = NY(now); nz = NZ(now);
}
//判断是否有错误
if (error() >= UT_ERROR_ABORT)
return error();
//获得方向并且规格化
UT_Vector3 normal(nx, ny, nz);
normal.normalize();
//一个boundbox对象
UT_BoundingBox bbox;
//得到bbox边框大小
inputGeo(0, context)->getBBox(&bbox);
//计算出size
float sx = bbox.sizeX();
float sy = bbox.sizeY();
float sz = bbox.sizeZ();
float size = SYSsqrt(sx*sx + sy*sy + sz*sz);
//规格化后的normal乘偏移值
float cx = normal.x() * dist;
float cy = normal.y() * dist;
float cz = normal.z() * dist;
//创建meshGrid类型的Guide指定分段数和size
myGuide1->meshGrid(divs, divs, size, size);
//求出Z轴向旋转到normal的矩阵
UT_Vector3 zaxis(0, 0, 1);
UT_Matrix3 mat3;
mat3.dihedral(zaxis, normal);
//附加位置信息
UT_Matrix4 xform;
xform = mat3;
xform.translate(cx, cy, cz);
//设置guide位置
myGuide1->transform(xform);
return error();
}
//设置鼠标放在端口上的提示信息
const char *
SOP_Myflatten::inputLabel(unsigned) const
{
return "Geometry to Flatten";
}