本文内容来自于jurajtomori 的 CREATING A SIMPLE C++ OPENVDB NODE IN HDK
一、节点功能
基于输入点激活VDB中的体素。
二、代码编写
头文件(vdb_activate_from_points.h)
头文件包含类(节点)的定义和用于构造(myConstructor()、SOP_VdbActivateFromPoints()、~SOP_VdbActivateFromPoints()、UI参数(myTemplateList[])、输入标记(inputLabel()的函数。执行实际几何处理的函数cookMySop()。这里还设置了一个DEBUG()函数,它将在node的UI中计算调试参数(最后一个参数可以是计算的帧数,但是我们不太可能激活调试选项)。
#ifndef __SOP_vdb_activate_from_points_h__
#define __SOP_vdb_activate_from_points_h__
#include <SOP/SOP_Node.h>
namespace VdbActivateFromPoints {
class SOP_VdbActivateFromPoints : public SOP_Node
{
public:
// HDK的节点构造函数
static OP_Node *myConstructor(OP_Network*, const char *, OP_Operator *);
// Houdini UI的参数数组
static PRM_Template myTemplateList[];
protected:
// 构造函数与析构函数
SOP_VdbActivateFromPoints(OP_Network *net, const char *name, OP_Operator *op);
virtual ~SOP_VdbActivateFromPoints();
// 在Houdini UI中标记节点输入
virtual const char *inputLabel(unsigned idx) const;
// 节点的主要功能函数
virtual OP_ERROR cookMySop(OP_Context &context);
private:
// 帮助函数访问节点的参数值
int DEBUG() { return evalInt("debug", 0, 0); }
};
}
#endif
源文件(vdb_activate_from_points.C)
//加入相关头文件,然后是访问Houdini功能的头文件,最后是Houdini附带的OpenVDB头文件。
#include "vdb_activate_from_points.h"
#include <limits.h>
#include <SYS/SYS_Math.h>
#include <UT/UT_DSOVersion.h>
#include <UT/UT_Interrupt.h>
#include <OP/OP_Operator.h>
#include <OP/OP_OperatorTable.h>
#include <GU/GU_Detail.h>
#include <GEO/GEO_PrimPoly.h>
#include <PRM/PRM_Include.h>
#include <CH/CH_LocalVariable.h>
#include <OP/OP_AutoLockInputs.h>
#include <GU/GU_PrimVDB.h>
#include <openvdb/openvdb.h>
using namespace VdbActivateFromPoints;
//注册节点并标记节点输入(可以在网络编辑器中看到)。newSopOperator()是
//Houdini要找的函数。它的目的是将我们的节点添加到Houdini的操作表中。它是必需的
void newSopOperator(OP_OperatorTable *table)
{
OP_Operator *op;
op = new OP_Operator(
"vdbActivateFromPoints", // 内部名称
"VDB Activate from Points", // UI名称
SOP_VdbActivateFromPoints::myConstructor, // 如何构建节点——一个构造这种类型节点的函数
SOP_VdbActivateFromPoints::myTemplateList, // 参数列表
2, // 最小输入
2); // 最大输入
// 将这个节点放在Tab键下的VDB submenu菜单下
op->setOpTabSubMenuPath("VDB");
// 执行addOperator()之后,'table'将获得'op'的所有权
table->addOperator(op);
}
//标节点输入,0对应第一个输入,1对应第二个输入
const char *
SOP_VdbActivateFromPoints::inputLabel(unsigned idx) const
{
switch (idx){
case 0: return "VDB";
case 1: return "Points where active voxels should be";
default: return "default";
}
}
//建立参数用户界面:你可以参考所有可用的文档;参数类型(浮点型,整型,复选框,
//菜单)。创建了PRM Name对象之后,我们需要将它们分配给PRM模板数组,Houdini将
//使用该数组在UI中显示参数。注意,即使我们希望没有参数,我们仍然需要在数组中
//包含一个空的PRM模板()。
// 为调试选项定义参数
static PRM_Name debugPRM("debug", "Print debug information"); // 内部名称,ui名称
// 将参数赋给接口,它是PRM_Template对象的数组
PRM_Template SOP_VdbActivateFromPoints::myTemplateList[] =
{
PRM_Template(PRM_TOGGLE, 1, &debugPRM, PRMzeroDefaults),
// 类型(复选框),大小(在我们的例子中是1,但是rgb/xyz值需要3),指向描述参数名的PRM_Name的指针,默认值(0 -disable)
PRM_Template() // 最后需要有一个空的 PRM_Template object
};
// 构造函数,析构函数,通常这里不需要修改任何东西,构造函数的工作是确保节点被放到正确的网络中
OP_Node *
SOP_VdbActivateFromPoints::myConstructor(OP_Network *net, const char *name, OP_Operator *op)
{
return new SOP_VdbActivateFromPoints(net, name, op);
}
SOP_VdbActivateFromPoints::SOP_VdbActivateFromPoints(OP_Network *net, const char *name, OP_Operator *op) : SOP_Node(net, name, op) {}
SOP_VdbActivateFromPoints::~SOP_VdbActivateFromPoints() {}
// 完成实际工作的函数
OP_ERROR
SOP_VdbActivateFromPoints::cookMySop(OP_Context &context)
{
// 我们必须锁定我们的输入,然后访问其的几何图形,当我们返回时,
// OP_AutoLockInputs将自动解锁我们的输入
OP_AutoLockInputs inputs(this);
if (inputs.lock(context) >= UT_ERROR_ABORT)
return error();
// 复制传入的几何体
duplicateSource(0, context);
// 检查中断-中断作用域在“进程”被销毁时自动关闭。
UT_AutoInterrupt progress("Activating voxels...");
// 从第二个输入获取指向几何图形的指针
const GU_Detail *points = inputGeo(1);
// 检查是否启用调试参数,在头文件中定义debug()函数
if (DEBUG())
{
std::cout << "number of points: " << points->getNumPoints() << std::endl;
}
GEO_PrimVDB* vdbPrim = NULL; // 指向vdb原语的空指针
//遍历所有传入原语并找到第一个原语是VDB
for (GA_Iterator it(gdp->getPrimitiveRange()); !it.atEnd(); it.advance())
{
GEO_Primitive* prim = gdp->getGEOPrimitive(it.getOffset());
if(dynamic_cast<const GEO_PrimVDB *>(prim))
{
vdbPrim = dynamic_cast<GEO_PrimVDB *>(prim);
break;
}
}
// 如果不是VDB,则终止
if(!vdbPrim)
{
addError(SOP_MESSAGE, "First input must contain a VDB!");
return error();
}
//默认情况下,Houdini中不同节点中的volume primitives共享相同的volume
//tree(用于内存优化),这将确保我们有自己的volume tree的深度拷贝,并且
//我们可以将它写入
vdbPrim->makeGridUnique();
// 获取网格基指针并将其转换为浮动网格指针
openvdb::GridBase::Ptr vdbPtrBase = vdbPrim->getGridPtr();
openvdb::FloatGrid::Ptr vdbPtr = openvdb::gridPtrCast<openvdb::FloatGrid>(vdbPtrBase);
// 获取浮动网格的访问器
openvdb::FloatGrid::Accessor vdb_access = vdbPtr->getAccessor();
// 获取网格转换的参考
const openvdb::math::Transform &vdbGridXform = vdbPtr->transform();
// 通过句柄遍历所有的点
int i = 0;
GA_ROHandleV3 Phandle(points->findAttribute(GA_ATTRIB_POINT, "P")); // 只读属性的句柄
GA_Offset ptoff;
GA_FOR_ALL_PTOFF(points, ptoff)
{
// 测试用户是否请求中止
if (progress.wasInterrupted())
return error();
// 获取当前的pont位置
UT_Vector3 Pvalue = Phandle.get(ptoff);
// 使用来自houdini的向量的值创建openvdb向量,将它从世界空间转换为
//vdb的索引空间(基于vdb的转换),并在点位置激活体素
openvdb::Vec3R p_( Pvalue[0], Pvalue[1], Pvalue[2] );
openvdb::Coord p_xformed( vdbGridXform.worldToIndexCellCentered(p_) );
vdb_access.setValueOn( p_xformed );
if (DEBUG())
{
std::cout << i << ". point world space position: " << Pvalue << std::endl;
std::cout << " volmue index space position: " << p_xformed << std::endl;
}
i++;
}
return error();
}
三、编译输出,并在houdini里面创建该节点
源文件下载地址原作者github仓库