上次说到Unity Houdini插件将Tag设为group,里面用到了自定义输入接口。然后那个Houdini教程又给我出难题了,unreal 可以用一种叫data Table的数据结构来完成向Houdini结构化数据的传递(链接),我没找到Unity类似的功能,又得自己写。用chatGPT的话,写起来还是挺容易的。

定义带有数据对象列表的组件

首先得有一个在编辑器里定义结构数据的组件,用于定义数据结构。

public abstract class HEU_PDE_DataTable<T> : MonoBehaviour {
    public List<T> dataTable;
}
using UnityEngine;
using System.Collections.Generic;

[System.Serializable]
public struct TileObject {
    public string name;
    public GameObject gameObject;
    public float height;
    public int num;
    public Vector3 N;
    public Color Cd;
}

public class HEU_PDE_TileObject : HEU_PDE_DataTable<TileObject> {}

这里使用了泛型,定义了一个抽象的组件,带有一个List属性,其子类可以定义dataTable的数据结构。

效果如下

接着把这个GameObject传递到Houdini的HDA的属性或输入里。

Houdini定义HDA的参数:

 这里使用了object merge节点,将Object抽取成HDA的参数。

然后在自定义输入接口里面处理DataTable,将每个列表项转换成点的属性。

编辑自定义输入接口

首先更改IsThisInputObjectSupported函数,加入对DataTable的支持

    private bool isDataTable(GameObject gameObject) {
        // judge if it is a subclass of the HEU_PDE_DataTable
        MonoBehaviour[] components = gameObject.GetComponents<MonoBehaviour>();
        Type baseGenericType = typeof(HEU_PDE_DataTable<>);
        foreach (MonoBehaviour component in components) {
            Type componentType = component.GetType();
            Type baseType = componentType.BaseType;

            if (baseType != null && baseType.IsGenericType && baseType.GetGenericTypeDefinition() == baseGenericType) {
                return true;
            }
        }
        return false;

    }

    public override bool IsThisInputObjectSupported(GameObject inputObject) {
        if (_inputInterfaceMesh.IsThisInputObjectSupported(inputObject)) return true;
        if (isDataTable(inputObject)) return true;
        return false;
    }

 就是遍历对象的组件,用反射获取其父类,判断父类是不是DataTable。

接着在CreateInputNodeWithDataUpload中遍历DataTable,转换成点属性即可

关键代码是

    switch (fieldType.Name) {
        case nameof(Double):
            if (HEU_InputMeshUtility.SetMeshPointAttribute(session, inputNodeID, 0, fieldName,
                                    1, (values.Cast<double>().Select(v => (float)v)).ToArray(), ref partInfo)) {
                continue;
            }
            break;
        case nameof(System.Single):
            if (HEU_InputMeshUtility.SetMeshPointAttribute(session, inputNodeID, 0, fieldName, 1,
                                    (values.Cast<float>()).ToArray(), ref partInfo)) {
                continue;
            }
            break;
        case nameof(Boolean):
            if (HEU_InputMeshUtility.SetMeshPointAttribute(session, inputNodeID, 0, fieldName, 1,
                                   (values.Cast<bool>().Select(v => new Vector3Int(v ? 1 : 0, 0, 0))).ToArray(), ref partInfo)) {
                continue;
            }
            break;
        case nameof(Int32):
            if (HEU_InputMeshUtility.SetMeshPointAttribute(session, inputNodeID, 0, fieldName, 1,
                                   (values.Select(v => new Vector3Int((int)v, 0, 0))).ToArray(), ref partInfo)) {
                continue;
            }
            break;
        case nameof(Color):
            if (HEU_InputMeshUtility.SetMeshPointAttribute(session, inputNodeID, 0, fieldName, 3,
                (values.Cast<Color>().Select(v => new Vector3(v.r, v.g, v.b))).ToArray(), ref partInfo, false)) {
                continue;
            }
            break;
        case nameof(Vector3):
            if (HEU_InputMeshUtility.SetMeshPointAttribute(session, inputNodeID, 0, fieldName,
                                3, (values.Cast<Vector3>()).ToArray(), ref partInfo, false)) {
                continue;
            }
            break;
        case nameof(String):
            if (HEU_InputMeshUtility.SetMeshPointAttribute(session, inputNodeID, 0, fieldName,
                                 (values.Cast<string>()).ToArray(), ref partInfo)) {
                continue;
            }
            break;
        case nameof(GameObject):
            // Convert to resource file path
            if (HEU_InputMeshUtility.SetMeshPointAttribute(session, inputNodeID, 0, fieldName,
                      (values.Select(v => (AssetDatabase.GetAssetPath((GameObject)v)))).ToArray(), ref partInfo)) {
                continue;
            }
            break;
    }
    HEU_Logger.LogError(string.Format("Failed to set attribute {0}!", fieldName));
    return false;

根据不同的C#类型转成对应的点属性

效果:

全部代码

HEU_PDE_DataTable.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public abstract class HEU_PDE_DataTable<T> : MonoBehaviour
{
    public List<T> dataTable;
}

HEU_PDE_TileObject.cs

using UnityEngine;
using System.Collections.Generic;

[System.Serializable]
public struct TileObject {
    public string name;
    public GameObject gameObject;
    public float height;
    public int num;
    public Vector3 N;
    public Color Cd;
}

public class HEU_PDE_TileObject : HEU_PDE_DataTable<TileObject> {}

HEU_PDE_InputInterfaceMesh.cs

using HoudiniEngineUnity;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using TMPro;
using UnityEditor;
using UnityEngine;
using static UnityEditor.Searcher.SearcherWindow.Alignment;

public class HEU_PDE_InputInterfaceMesh : HEU_InputInterface {
#if UNITY_EDITOR
    [InitializeOnLoadMethod]
    [UnityEditor.Callbacks.DidReloadScripts]
    private static void OnScriptsReloaded() {
        HEU_PDE_InputInterfaceMesh inputInterface = new HEU_PDE_InputInterfaceMesh();
        HEU_InputUtility.RegisterInputInterface(inputInterface);
    }
#endif
    private readonly HEU_InputInterfaceMesh _inputInterfaceMesh;
    public HEU_PDE_InputInterfaceMesh() : base(DEFAULT_PRIORITY + 100)// higher priority
    {
        // reflect to get instance of HEU_InputInterfaceMesh
        Type type = typeof(HEU_InputInterfaceMesh);
        BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.NonPublic;
        ConstructorInfo privateConstructor = type.GetConstructor(bindingFlags, null, new Type[0], null);
        _inputInterfaceMesh = (HEU_InputInterfaceMesh)privateConstructor.Invoke(null);
    }
    public override bool CreateInputNodeWithDataUpload(HEU_SessionBase session, int connectNodeID, GameObject inputObject, out int inputNodeID) {
        if (isDataTable(inputObject)) {

            MonoBehaviour[] components = inputObject.GetComponents<MonoBehaviour>();
            Type baseGenericType = typeof(HEU_PDE_DataTable<>);
            inputNodeID = HEU_Defines.HEU_INVALID_NODE_ID;
            int mergeNodeId = HEU_Defines.HEU_INVALID_NODE_ID;
            HAPI_PartInfo partInfo = new HAPI_PartInfo();
            string inputName = null;
            int newNodeID = HEU_Defines.HEU_INVALID_NODE_ID;
            session.CreateInputNode(out newNodeID, inputName);
            if (newNodeID == HEU_Defines.HEU_INVALID_NODE_ID || !HEU_HAPIUtility.IsNodeValidInHoudini(session, newNodeID)) {
                HEU_Logger.LogError("Failed to create new input node in Houdini session!");
                return false;
            }

            inputNodeID = newNodeID;

            foreach (MonoBehaviour component in components) {
                Type componentType = component.GetType();
                Type baseType = componentType.BaseType;

                if (baseType != null && baseType.IsGenericType && baseType.GetGenericTypeDefinition() == baseGenericType) {
                    // Get the generic argument type
                    Type dataListType = baseType.GetGenericArguments()[0];

                    // Iterate through the properties of the generic argument type
                    FieldInfo dataTableInfo = componentType.GetField("dataTable");
                    IList dataTable = (IList)dataTableInfo.GetValue(component);
                    FieldInfo[] fields = dataListType.GetFields();

                    partInfo.pointCount = dataTable.Count;
                    if (!session.SetPartInfo(inputNodeID, 0, ref partInfo)) {
                        HEU_Logger.LogError("Failed to set input part info. ");
                        return false;
                    }
                    if (!HEU_InputMeshUtility.SetMeshPointAttribute(session, inputNodeID, 0, HEU_HAPIConstants.HAPI_ATTRIB_POSITION,
                        3, Enumerable.Repeat(new Vector3(0, 0, 0), partInfo.pointCount).ToArray(), ref partInfo, true)) {
                        HEU_Logger.LogError("Failed to set input geometry position.");
                        return false;
                    }
                    partInfo.pointAttributeCount = fields.Length;
                    foreach (FieldInfo field in fields) {
                        Type fieldType = field.FieldType;

                        string fieldName = field.Name;
                        List<object> values = new();
                        for (int i = 0; i < partInfo.pointCount; i++) {
                            object fieldValue = field.GetValue(dataTable[i]);
                            values.Add(fieldValue);
                        }
                        // Handle available types
                        switch (fieldType.Name) {
                            case nameof(Double):
                                if (HEU_InputMeshUtility.SetMeshPointAttribute(session, inputNodeID, 0, fieldName,
                                                        1, (values.Cast<double>().Select(v => (float)v)).ToArray(), ref partInfo)) {
                                    continue;
                                }
                                break;
                            case nameof(System.Single):
                                if (HEU_InputMeshUtility.SetMeshPointAttribute(session, inputNodeID, 0, fieldName, 1,
                                                        (values.Cast<float>()).ToArray(), ref partInfo)) {
                                    continue;
                                }
                                break;
                            case nameof(Boolean):
                                if (HEU_InputMeshUtility.SetMeshPointAttribute(session, inputNodeID, 0, fieldName, 1,
                                                       (values.Cast<bool>().Select(v => new Vector3Int(v ? 1 : 0, 0, 0))).ToArray(), ref partInfo)) {
                                    continue;
                                }
                                break;
                            case nameof(Int32):
                                if (HEU_InputMeshUtility.SetMeshPointAttribute(session, inputNodeID, 0, fieldName, 1,
                                                       (values.Select(v => new Vector3Int((int)v, 0, 0))).ToArray(), ref partInfo)) {
                                    continue;
                                }
                                break;
                            case nameof(Color):
                                if (HEU_InputMeshUtility.SetMeshPointAttribute(session, inputNodeID, 0, fieldName, 3,
                                    (values.Cast<Color>().Select(v => new Vector3(v.r, v.g, v.b))).ToArray(), ref partInfo, false)) {
                                    continue;
                                }
                                break;
                            case nameof(Vector3):
                                if (HEU_InputMeshUtility.SetMeshPointAttribute(session, inputNodeID, 0, fieldName,
                                                    3, (values.Cast<Vector3>()).ToArray(), ref partInfo, false)) {
                                    continue;
                                }
                                break;
                            case nameof(String):
                                if (HEU_InputMeshUtility.SetMeshPointAttribute(session, inputNodeID, 0, fieldName,
                                                     (values.Cast<string>()).ToArray(), ref partInfo)) {
                                    continue;
                                }
                                break;
                            case nameof(GameObject):
                                // Convert to resource file path
                                if (HEU_InputMeshUtility.SetMeshPointAttribute(session, inputNodeID, 0, fieldName,
                                          (values.Select(v => (AssetDatabase.GetAssetPath((GameObject)v)))).ToArray(), ref partInfo)) {
                                    continue;
                                }
                                break;
                        }
                        HEU_Logger.LogError(string.Format("Failed to set attribute {0}!", fieldName));
                        return false;

                    }
                    break;
                }
            }
            if (!session.CommitGeo(inputNodeID)) {
                HEU_Logger.LogError("Filed to commit geo!");
                return false;
            }
            int parentId = HEU_HAPIUtility.GetParentNodeID(session, newNodeID);
            if (!session.CreateNode(parentId, "merge", null, false, out mergeNodeId)) {
                HEU_Logger.LogErrorFormat("Unable to create merge SOP node for connecting input assets.");
                return false;
            }

            if (!session.ConnectNodeInput(mergeNodeId, 0, newNodeID)) {
                HEU_Logger.LogErrorFormat("Unable to connect to input node!");
                return false;
            }

            if (!session.SetNodeDisplay(mergeNodeId, 1)) {
                HEU_Logger.LogWarningFormat("Unable to set display flag!");
            }

            inputNodeID = mergeNodeId;
        }
        else {
            bool bRes = _inputInterfaceMesh.CreateInputNodeWithDataUpload(session, connectNodeID, inputObject, out inputNodeID);
            if (!bRes) return false;
            //SetDetailStringAttribute(session, inputNodeID, "unity_tag",inputObject.tag);
            String groupName = inputObject.tag;
            SetPointGroup(session, inputNodeID, groupName);
            if (!session.CommitGeo(inputNodeID)) {
                HEU_Logger.LogError("Filed to commit geo!");
                return false;
            }

        }
        if (!session.CookNode(inputNodeID, false)) {
            HEU_Logger.LogError("New input node failed to cook!");
            return false;
        }
        return true;
    }
    private bool SetDetailStringAttribute(HEU_SessionBase session, int inputNodeID, string attributeName, string attributeValue) {
        HAPI_AttributeInfo attrInfo = new HAPI_AttributeInfo();
        attrInfo.owner = HAPI_AttributeOwner.HAPI_ATTROWNER_DETAIL;
        attrInfo.storage = HAPI_StorageType.HAPI_STORAGETYPE_STRING;
        attrInfo.count = 1;
        attrInfo.tupleSize = 1;

        if (!session.AddAttribute(inputNodeID, 0, attributeName, ref attrInfo)
            || !session.SetAttributeStringData(inputNodeID, 0, attributeName, ref attrInfo, new String[] { attributeValue }, 0, 1)) {
            HEU_Logger.LogError("Failed to add detail attribute.");
            return false;
        }
        return true;
    }

    private bool SetPointGroup(HEU_SessionBase session, int inputNodeID, string groupName) {
        HAPI_GeoInfo geoInfo = new HAPI_GeoInfo();
        if (session.GetGeoInfo(inputNodeID, ref geoInfo)) {
            for (int i = 0; i < geoInfo.partCount; i++) {
                HAPI_PartInfo partInfo = new HAPI_PartInfo();
                session.GetPartInfo(inputNodeID, i, ref partInfo);
                session.AddGroup(inputNodeID, i, HAPI_GroupType.HAPI_GROUPTYPE_POINT, groupName);

                int pointCount = partInfo.pointCount;
                int[] membership = new int[pointCount];
                for (int j = 0; j < pointCount; j++) {
                    membership[j] = 1;
                }
                session.SetGroupMembership(inputNodeID, i, HAPI_GroupType.HAPI_GROUPTYPE_POINT, groupName, membership, 0, pointCount);
            }
        }
        else return false;
        return true;
    }
    private bool isDataTable(GameObject gameObject) {
        // judge if it is a subclass of the HEU_PDE_DataTable
        MonoBehaviour[] components = gameObject.GetComponents<MonoBehaviour>();
        Type baseGenericType = typeof(HEU_PDE_DataTable<>);
        foreach (MonoBehaviour component in components) {
            Type componentType = component.GetType();
            Type baseType = componentType.BaseType;

            if (baseType != null && baseType.IsGenericType && baseType.GetGenericTypeDefinition() == baseGenericType) {
                return true;
            }
        }
        return false;

    }

    public override bool IsThisInputObjectSupported(GameObject inputObject) {
        if (_inputInterfaceMesh.IsThisInputObjectSupported(inputObject)) return true;
        if (isDataTable(inputObject)) return true;
        return false;
    }
}

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐