上次说到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_DataTableMonoBehaviour[] 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 pathif (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);}
#endifprivate readonly HEU_InputInterfaceMesh _inputInterfaceMesh;public HEU_PDE_InputInterfaceMesh() : base(DEFAULT_PRIORITY + 100)// higher priority{// reflect to get instance of HEU_InputInterfaceMeshType 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 typeType dataListType = baseType.GetGenericArguments()[0];// Iterate through the properties of the generic argument typeFieldInfo 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 typesswitch (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 pathif (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_DataTableMonoBehaviour[] 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;}
}