自分用Houdiniメモ (形状処理と自作SOPまわり)


Visualization

Texture

VDBの変換

Wrangle

int id = nearpoint(1, @P);
@Cd = point(1, "Cd", id);

Export

  • .objだとPoint Cloudの頂点カラーは吐けないっぽいので.plyで吐く

自作SOPまわり

  • 自作SOPを作るときはSOP_Nodeを継承して、overrideしたcookMySopに実装したい処理を書くと良い
  • Houdini側のGUI経由でc++側のパラメタを設定したい場合は、C++側でPRM_Templateを用意してあげる必要がある
  • Houdiniには頂点をpoint, 面(face)をvertexと呼ぶ謎の文化がある
    • 入力geometryからgeometryの属性を引っ張りたい場合はgdp->findAttribute()を使う
    • 頂点listのiteratorをぶん回したいときはGA_FOR_ALL_PTOFFまわりのマクロを読む

tiny example (公式exampleのSOP/SOP_CPPWave.h, SOP/SOP_CPPWave.Cの改変)

SOP_Sample.h
#pragma once
#include <SOP/SOP_Node.h>
#include <string>

namespace Samples {
class SOP_Sample : public SOP_Node {
public:
    SOP_Sample(OP_Network *net, const char *name, OP_Operator *op);
    ~SOP_Sample() override;

    static PRM_Template myTemplateList[];

    static OP_Node *myConstructor(OP_Network *, const char *, OP_Operator *);
    static const std::string INTERNAL_LAMBDA;

protected:
    OP_ERROR cookMySop(OP_Context &context) override;

private:
    fpreal LAMBDA() {
        return evalFloat(INTERNAL_LAMBDA.c_str(), 0, 0);
    }
};
SOP_Sample.cpp
#include "SOP_Sample.h"

#include <GA/GA_Handle.h>
#include <GU/GU_Detail.h>
#include <OP/OP_AutoLockInputs.h>
#include <OP/OP_Director.h>
#include <OP/OP_Operator.h>
#include <OP/OP_OperatorTable.h>
#include <PRM/PRM_Include.h>
#include <SYS/SYS_Math.h>
#include <UT/UT_DSOVersion.h>
#include <Eigen/Dense>

#include "SOP_Sample.proto.h"


using namespace Samples;

const std::string SOP_Sample::INTERNAL_LAMBDA = "lambda";

void newSopOperator(OP_OperatorTable *table) {
    table->addOperator(
            new OP_Operator("sample", // generated name at tree view
                            "Sample", // SOP name
                            // ref. https://www.sidefx.com/docs/hdk/class_o_p___operator.html
                            SOP_Sample::myConstructor,
                            SOP_Sample::myTemplateList, 1, 1, 0));
}

static PRM_Name sop_names[] = {
        PRM_Name(SOP_Sample::INTERNAL_LAMBDA, "Lambda"), // 0: internal name, 1: name at Houdini
};
static PRM_Default sop_lambda_default(0.01);
static PRM_Range sop_lambda_range(PRM_RANGE_RESTRICTED, 0.0001, PRM_RANGE_UI,
                                  0.1);

PRM_Template SOP_MeshSmoothing::myTemplateList[] = {
        PRM_Template(PRM_FLT_J, 1, &sop_names[0], &sop_lambda_default, 0,
                     &sop_lambda_range),
        PRM_Template()};

OP_Node *SOP_Sample::myConstructor(OP_Network *net, const char *name,
                                          OP_Operator *op) {
    return new SOP_Sample(net, name, op);
}

SOP_Sample::SOP_Sample(OP_Network *net, const char *name,
                                     OP_Operator *op)
    : SOP_Node(net, name, op) {
    mySopFlags.setManagesDataIDs(true);
}

SOP_Sample::~SOP_Sample() {}

OP_ERROR
SOP_Sample::cookMySop(OP_Context &context) {
    OP_AutoLockInputs inputs(this);
    if (inputs.lock(context) >= UT_ERROR_ABORT) {
        return error();
    }
    duplicateSource(0, context);

    // If you want to use animated mesh
    flags().setTimeDep(true);
    fpreal frame =
            OPgetDirector()->getChannelManager()->getSample(context.getTime());
    frame *= 0.03;

    auto pt_range = gdp->getPointRange();
    auto pt_size = pt_range.getEntries();      // vertex size
    auto face_range = gdp->getVertexRange();
    auto face_size = face_range.getEntries();  // face size (flattened)
    auto prim_range = gdp->getPrimitiveRange();
    auto prim_size = prim_range.getEntries();  // face size

    // (Optional)
    float prim_float_num = face_size / prim_size;
    int prim_int_num = static_cast<int>(prim_float_num);
    if (prim_int_num != prim_float_num || prim_int_num !=3) {
        addWarning(SOP_MESSAGE, "Please input a triangular mesh.");
        return error();
    }

    addMessage(SOP_MESSAGE, ("lambda: " + std::to_string(LAMBDA())).c_str());
    addMessage(SOP_MESSAGE, ("#V: " + std::to_string(pt_size)).c_str());
    addMessage(SOP_MESSAGE, ("#F: " + std::to_string(face_size)).c_str());
    addMessage(SOP_MESSAGE, ("#Prim: " + std::to_string(prim_size)).c_str());

    // Convert vertices to Eigen
    Eigen::MatrixXd V(pt_size, 3);
    GA_RWHandleV3 Phandle(gdp->findAttribute(GA_ATTRIB_POINT, "P"));
    GA_Offset ptoff;
    GA_FOR_ALL_PTOFF(gdp, ptoff) {
        UT_Vector3 Pvalue = Phandle.get(ptoff);
        V(ptoff, 0) = Pvalue.x();
        V(ptoff, 1) = Pvalue.y();
        V(ptoff, 2) = Pvalue.z();
    }

    // Convert faces to Eigen
    Eigen::MatrixXi F(prim_size, prim_int_num);
    for (auto primoff : prim_range) {
        const auto *face = gdp->getGEOPrimitive(primoff);
        int face_idx = 0;
        for (auto ptoff : face->getPointRange()) {
            F(primoff, face_idx) = gdp->pointIndex(ptoff);
            face_idx++;
        }
    }
    auto out = F; // apply your geometry processing

    for (auto ptoff : pt_range) {
        UT_Vector3 Pvalue = Phandle.get(ptoff);
        Pvalue.x() = out(ptoff, 0);
        Pvalue.y() = out(ptoff, 1);
        Pvalue.z() = out(ptoff, 2);
        Phandle.set(ptoff, Pvalue);
    }

    Phandle.bumpDataId();
    return error();
}

TODO

  • deep copyせずにHoudiniの頂点属性からEigen::MatrixXdをつくりたい