public void Start() { // Grab the HEU_HoduiniAsset _evergreenAsset = _evergreenGameObject.GetComponent <HEU_HoudiniAssetRoot>() != null?_evergreenGameObject.GetComponent <HEU_HoudiniAssetRoot>().HoudiniAsset : null; // Always get the latest parms after each cook List <HEU_ParameterData> parms = _evergreenAsset.Parameters.GetParameters(); foreach (HEU_ParameterData parmData in parms) { HEU_Logger.Log(parmData._labelName); if (parmData._parmInfo.type == HAPI_ParmType.HAPI_PARMTYPE_BUTTON) { // Display a button: parmData._intValues[0]; } else if (parmData._parmInfo.type == HAPI_ParmType.HAPI_PARMTYPE_FLOAT) { // Display a float: parmData._floatValues[0]; // You can set a float this way HEU_ParameterUtility.SetFloat(_evergreenAsset, parmData._name, 1f); // Or this way (the index is 0, unless its for array of floats) parmData._floatValues[0] = 1; } } // Make sure to cook after changing _evergreenAsset.RequestCook(true, false, true, true); // Start a repeating updater InvokeRepeating("UpdateGravity", _updateRate, _updateRate); }
/// <summary> /// Query all attributes of a specific part and a specific owner (detail, primitive, point, vertex). /// </summary> /// <param name="session">Houdini Engine session</param> /// <param name="geoID">The geometry object ID</param> /// <param name="partID">The part ID</param> /// <param name="owner">The attribute owner</param> /// <param name="count">The number of expected attributes for this owner</param> public static void QueryPartAttributeByOwner(HEU_SessionBase session, HAPI_NodeId geoID, HAPI_PartId partID, HAPI_AttributeOwner owner, int count, StringBuilder sb) { if (count == 0) { HEU_Logger.LogFormat("No attributes with owner {0}", owner); return; } string[] attrNames = new string[count]; if (session.GetAttributeNames(geoID, partID, owner, ref attrNames, count)) { for (int i = 0; i < attrNames.Length; ++i) { HAPI_AttributeInfo attrInfo = new HAPI_AttributeInfo(); if (HEU_GeneralUtility.GetAttributeInfo(session, geoID, partID, attrNames[i], ref attrInfo) && attrInfo.exists) { sb.AppendLine(string.Format("Attribute {0} has storage: {1}", attrNames[i], attrInfo.storage)); // Query the actual values with helper for each type QueryAttributeByStorageType(session, geoID, partID, ref attrInfo, attrNames[i]); } } } }
/// <summary> /// Query object nodes in the Asset. An object node represents a Houdini transform node. /// Each object might have any number of SOP geometry containers and a transform. /// <param name="houdiniAsset">The HEU_HoudiniAsset of the loaded asset</param> /// </summary> public static void QueryObjects(HEU_HoudiniAsset houdiniAsset) { // Get access to the Houdini Engine session used by this asset. // This gives access to call Houdini Engine APIs directly. HEU_SessionBase session = houdiniAsset.GetAssetSession(true); if (session == null || !session.IsSessionValid()) { HEU_Logger.LogWarningFormat("Invalid Houdini Engine session! Try restarting session."); return; } HAPI_ObjectInfo[] objectInfos = null; HAPI_Transform[] objectTransforms = null; HAPI_NodeInfo assetNodeInfo = houdiniAsset.NodeInfo; // Fill in object infos and transforms based on node type and number of child objects. // This the hiearchy of the HDA when loaded in Houdini Engine. It can contain subnets with // multiple objects containing multiple geometry, or a single object containting any number of geometry. // This automatically handles object-level HDAs and geometry (SOP) HDAs. if (!HEU_HAPIUtility.GetObjectInfos(session, houdiniAsset.AssetID, ref assetNodeInfo, out objectInfos, out objectTransforms)) { return; } // For each object, get the display and editable geometries contained inside. for (int i = 0; i < objectInfos.Length; ++i) { // Get display SOP geo info HAPI_GeoInfo displayGeoInfo = new HAPI_GeoInfo(); if (!session.GetDisplayGeoInfo(objectInfos[i].nodeId, ref displayGeoInfo)) { return; } QueryGeoParts(session, ref displayGeoInfo); // Optional: Get editable nodes, cook em, then create geo nodes for them HAPI_NodeId[] editableNodes = null; HEU_SessionManager.GetComposedChildNodeList(session, objectInfos[i].nodeId, (int)HAPI_NodeType.HAPI_NODETYPE_SOP, (int)HAPI_NodeFlags.HAPI_NODEFLAGS_EDITABLE, true, out editableNodes); if (editableNodes != null) { foreach (HAPI_NodeId editNodeID in editableNodes) { if (editNodeID != displayGeoInfo.nodeId) { session.CookNode(editNodeID, HEU_PluginSettings.CookTemplatedGeos); HAPI_GeoInfo editGeoInfo = new HAPI_GeoInfo(); if (session.GetGeoInfo(editNodeID, ref editGeoInfo)) { QueryGeoParts(session, ref editGeoInfo); } } } } } }
/// <summary> /// Query each geometry container's parts to get the actual geometry data. /// A HAPI_GeoInfo represents a SOP geometry container that might have one or more /// HAPI_PartInfos.A geometry containing more than one part could mean different /// geometry types merged together, or different layers in a heightfield volume. /// </summary> /// <param name="session">Houdini Engine session</param> /// <param name="geoInfo">The HEU_GeoInfo pertaining to the geometry to query</param> public static void QueryGeoParts(HEU_SessionBase session, ref HAPI_GeoInfo geoInfo) { int numParts = geoInfo.partCount; for (int i = 0; i < numParts; ++i) { HAPI_PartInfo partInfo = new HAPI_PartInfo(); if (!session.GetPartInfo(geoInfo.nodeId, 0, ref partInfo)) { continue; } StringBuilder sb = new StringBuilder(); // Process each geometry by its type if (partInfo.type == HAPI_PartType.HAPI_PARTTYPE_MESH) { // Meshes sb.AppendLine(string.Format("Mesh part at {0} with vertex count {1}, point count {2}, and primitive count {3}", i, partInfo.vertexCount, partInfo.pointCount, partInfo.faceCount)); } else if (partInfo.type == HAPI_PartType.HAPI_PARTTYPE_VOLUME) { // Heightfield / terrain sb.AppendLine(string.Format("Volume part at {0}", i)); } else if (partInfo.type == HAPI_PartType.HAPI_PARTTYPE_CURVE) { // Curves sb.AppendLine(string.Format("Curve part at {0}", i)); } else if (partInfo.type == HAPI_PartType.HAPI_PARTTYPE_INSTANCER) { // Instancer sb.AppendLine(string.Format("Instancer part at {0}", i)); } else if (partInfo.type == HAPI_PartType.HAPI_PARTTYPE_INVALID) { // Not valid Houdini Engine type - ignore sb.AppendLine(string.Format("Invalid part at {0}", i)); } // Query attributes for each part QueryPartAttributeByOwner(session, geoInfo.nodeId, i, HAPI_AttributeOwner.HAPI_ATTROWNER_DETAIL, partInfo.detailAttributeCount, sb); QueryPartAttributeByOwner(session, geoInfo.nodeId, i, HAPI_AttributeOwner.HAPI_ATTROWNER_PRIM, partInfo.primitiveAttributeCount, sb); QueryPartAttributeByOwner(session, geoInfo.nodeId, i, HAPI_AttributeOwner.HAPI_ATTROWNER_POINT, partInfo.pointAttributeCount, sb); QueryPartAttributeByOwner(session, geoInfo.nodeId, i, HAPI_AttributeOwner.HAPI_ATTROWNER_VERTEX, partInfo.vertexAttributeCount, sb); HEU_Logger.Log("Part: \n" + sb.ToString()); } }
/// <summary> /// Query the parameters in the HDA, and change some values. /// </summary> /// <param name="houdiniAsset">The HEU_HoudiniAsset of the loaded asset</param> public static void ChangeParmsAndCook(HEU_HoudiniAsset houdiniAsset) { // Always get the latest parms after each cook List <HEU_ParameterData> parms = houdiniAsset.Parameters.GetParameters(); if (parms == null || parms.Count == 0) { HEU_Logger.LogFormat("No parms found"); return; } // -------------------------------------------------------------------- // Example to loop over each parm, checking its type and name. Then setting value. StringBuilder sb = new StringBuilder(); foreach (HEU_ParameterData parmData in parms) { sb.AppendLine(string.Format("Parm: name={0}, type={1}", parmData._labelName, parmData._parmInfo.type)); if (parmData._parmInfo.type == HAPI_ParmType.HAPI_PARMTYPE_BUTTON) { // Display a button: parmData._intValues[0]; } else if (parmData._parmInfo.type == HAPI_ParmType.HAPI_PARMTYPE_FLOAT) { // Display a float: parmData._floatValues[0]; // You can set a float this way HEU_ParameterUtility.SetFloat(houdiniAsset, parmData._name, 1f); // Or this way (the index is 0, unless its for array of floats) parmData._floatValues[0] = 1; } } HEU_Logger.Log("Parameters: \n" + sb.ToString()); // -------------------------------------------------------------------- // Examples to look up a parm via name, and set it. // Use helper to set float parameter with name HEU_ParameterUtility.SetFloat(houdiniAsset, "gravity", 5f); // Use helper to set random color HEU_ParameterUtility.SetColor(houdiniAsset, "branch_vtx_color_color", Random.ColorHSV()); // Make sure to cook after changing parms CookAsset(houdiniAsset); }
// Start is called before the first frame update public static void StartQuery() { string evergreenAssetPath = "Assets/Plugins/HoudiniEngineUnity/HDAs/EverGreen.otl"; string evergreenFullPath = HEU_AssetDatabase.GetAssetFullPath(evergreenAssetPath); if (string.IsNullOrEmpty(evergreenFullPath)) { HEU_Logger.LogErrorFormat("Unable to load Evergreen asset at path: {0}", evergreenAssetPath); return; } // Always need a Houdini Engine session in order to use the APIs. // This call will create a new session if one does not exist, or continue using // an existing session. HEU_SessionBase session = HEU_SessionManager.GetOrCreateDefaultSession(); // Load the Evergreen HDA into the Houdini Engine session, as well as the Unity scene. // This gives back the root gameobject of the generated HDA hiearchy in Unity. GameObject rootGO = HEU_HAPIUtility.InstantiateHDA(evergreenFullPath, Vector3.zero, session, true); if (rootGO != null) { HEU_EditorUtility.SelectObject(rootGO); } // Get reference to the Houdini script component on the asset. // This is the main container of the HDA's loaded data, and will be used in all // APIs to query and manipulate the asset. HEU_HoudiniAsset houdiniAsset = QueryHoudiniAsset(rootGO); if (houdiniAsset == null) { return; } // Make sure the HDA is cooked before querying or changing its properties. CookAsset(houdiniAsset); // Example of querying and changing parms. ChangeParmsAndCook(houdiniAsset); // This will query objects, geometry, parts, and attributes in the asset. QueryObjects(houdiniAsset); // This will query the gravity attribute. QueryAttribute(houdiniAsset, "EvergreenGenerator", "EvergreenGenerator1", 0, "Cd"); }
static void LogAttr(HEU_OutputAttribute outAttr) { HEU_Logger.LogFormat("Found {0} attribute:", outAttr._name); if (outAttr._intValues != null) { LogArray(outAttr._name, outAttr._intValues, outAttr._tupleSize); } else if (outAttr._floatValues != null) { LogArray(outAttr._name, outAttr._floatValues, outAttr._tupleSize); } else if (outAttr._stringValues != null) { LogArray(outAttr._name, outAttr._stringValues, outAttr._tupleSize); } }
/// <summary> /// Query a specific attribute on an asset, within its geometry. /// </summary> /// <param name="objName">The object name</param> /// <param name="geoName">The SOP geometry name</param> /// <param name="partID">The part ID</param> /// <param name="attrName">The attribute name</param> public static void QueryAttribute(HEU_HoudiniAsset houdiniAsset, string objName, string geoName, HAPI_PartId partID, string attrName) { // Get access to the Houdini Engine session used by this asset. // This gives access to call Houdini Engine APIs directly. HEU_SessionBase session = houdiniAsset.GetAssetSession(true); if (session == null || !session.IsSessionValid()) { HEU_Logger.LogWarningFormat("Invalid Houdini Engine session! Try restarting session."); return; } // First get the object (transform) node, then the geometry container, then the part. // Finally, get the attribute on the part. HEU_ObjectNode objNode = houdiniAsset.GetObjectNodeByName(objName); if (objNode == null) { HEU_Logger.LogWarningFormat("Object with name {0} not found in asset {1}!", objName, houdiniAsset.AssetName); return; } HEU_GeoNode geoNode = objNode.GetGeoNode(geoName); if (geoNode == null) { HEU_Logger.LogWarningFormat("Geometry with name {0} not found in object {1} in asset {2}!", geoNode.GeoName, objName, houdiniAsset.AssetName); } HAPI_AttributeInfo attrInfo = new HAPI_AttributeInfo(); if (!HEU_GeneralUtility.GetAttributeInfo(session, geoNode.GeoID, partID, attrName, ref attrInfo) && attrInfo.exists) { HEU_Logger.LogWarningFormat("Attribute {0} not found in asset.", attrName); } HEU_Logger.LogFormat("Found attribute {0} on geo {1}", attrName, geoName); // Now query the actual values on this attribute QueryAttributeByStorageType(session, geoNode.GeoID, partID, ref attrInfo, attrName); }
/// <summary> /// Shows how to get the HEU_HoudiniAsset component from a HDA root gameobject. /// </summary> public static HEU_HoudiniAsset QueryHoudiniAsset(GameObject rootGO) { // First get the HEU_HoudiniAssetRoot which is the script at the root gameobject HEU_HoudiniAssetRoot heuRoot = rootGO.GetComponent <HEU_HoudiniAssetRoot>(); if (heuRoot == null) { HEU_Logger.LogWarningFormat("Unable to get the HEU_HoudiniAssetRoot from gameobject: {0}. Not a valid HDA.", rootGO.name); return(null); } // The HEU_HoudiniAssetRoot should have a reference to HEU_HoudiniAsset which is the main HEU asset script. if (heuRoot.HoudiniAsset == null) { HEU_Logger.LogWarningFormat("Unable to get the HEU_HoudiniAsset in root gameobject: {0}. Not a valid HDA.", rootGO.name); return(null); } return(heuRoot.HoudiniAsset); }
static void LogArray <T>(string name, T[] arr, int tupleSize) { int index = 0; int count = arr.Length / tupleSize; StringBuilder sb = new StringBuilder(); for (int i = 0; i < count; ++i) { sb.AppendFormat("{0}[{1}] = ", name, i); if (tupleSize > 1) { sb.Append("["); for (int j = 0; j < tupleSize; ++j) { index = i * tupleSize + j; if (j != 0) { sb.Append(","); } sb.AppendFormat("{0}", arr[index]); } sb.AppendLine("]"); } else { sb.AppendFormat("{0}\n", arr[i]); } } HEU_Logger.Log(sb.ToString()); }
/// <summary> /// Example to show how to use the HEU_OutputAttributeStore component to query /// attribute data and set it on instances. /// This should be used with HEUInstanceAttributesStore.hda. /// This function is called after HDA is cooked. /// </summary> void InstancerCallback() { // Acquire the attribute storage component (HEU_OutputAttributesStore). // HEU_OutputAttributesStore contains a dictionary of attribute names to attribute data (HEU_OutputAttribute). // HEU_OutputAttributesStore is added to the generated gameobject when an attribute with name // "hengine_attr_store" is created at the detail level. HEU_OutputAttributesStore attrStore = gameObject.GetComponent <HEU_OutputAttributesStore>(); if (attrStore == null) { HEU_Logger.LogWarning("No HEU_OutputAttributesStore component found!"); return; } // Query for the health attribute (HEU_OutputAttribute). // HEU_OutputAttribute contains the attribute info such as name, class, storage, and array of data. // Use the name to get HEU_OutputAttribute. // Can use HEU_OutputAttribute._type to figure out what the actual data type is. // Note that data is stored in array. The size of the array corresponds to the data type. // For instances, the size of the array is the point cound. HEU_OutputAttribute healthAttr = attrStore.GetAttribute("health"); if (healthAttr != null) { LogAttr(healthAttr); } // Query for the vector size attribute HEU_OutputAttribute sizeAttr = attrStore.GetAttribute("size"); if (sizeAttr != null) { LogAttr(sizeAttr); } // Query for the stringdata attribute HEU_OutputAttribute stringAttr = attrStore.GetAttribute("stringdata"); if (stringAttr != null) { LogAttr(stringAttr); } // Example of how to map the attribute array values to instances // Get the generated instances as children of this gameobject. // Note that this will include the current parent as first element (so its number of children + 1 size) Transform[] childTrans = transform.GetComponentsInChildren <Transform>(); int numChildren = childTrans.Length; // Starting at 1 to skip parent transform for (int i = 1; i < numChildren; ++i) { HEU_Logger.LogFormat("Instance {0}: name = {1}", i, childTrans[i].name); // Can use the name to match up indices string instanceName = "Instance" + i; if (childTrans[i].name.EndsWith(instanceName)) { // Now apply health as scale value Vector3 scale = childTrans[i].localScale; // Health index is -1 due to child indices off by 1 because of parent scale.y = healthAttr._intValues[i - 1]; childTrans[i].localScale = scale; } } }
/// <summary> /// Applies a Houdini's UVLayout node to each given gameobject's mesh data, and generates the output. /// The output could be a copy gameobject, or replace the mesh and materials on the original. /// </summary> /// <param name="gameObjects">Array of gameobjects containing meshes</param> /// <param name="outputMode">How the outputs should be generated</param> /// <param name="output_name_suffix">Name to append at end of each generated gameobject if outputMode == COPY</param> public static void ApplyUVLayoutTo(GameObject[] gameObjects, OutputMode outputMode, string output_name_suffix) { // A Houdini Engine session is always required. This should catch any licensing and installation issues. HEU_SessionBase session = HEU_SessionManager.GetOrCreateDefaultSession(); if (session == null || !session.IsSessionValid()) { HEU_Logger.LogError("Failed to get Houdini Engine session. Unable to apply UV layout."); return; } if (gameObjects == null || gameObjects.Length == 0) { HEU_Logger.LogError("No input objects found to apply UV layout."); return; } // For each gameobject with mesh: // -create an input node in the Houdini session // -import the mesh data into the input node // -connect input node to a new UVLayout node // -cook the UVLayout node // -generate the output mesh foreach (GameObject currentGO in gameObjects) { // Process the current gameobject to get the potential list of input mesh data. // HEU_InputUtility contains helper functions for uploading mesh data into Houdini. // Also handles LOD meshes. bool bHasLODGroup = false; HEU_InputInterfaceMesh inputMeshInterface = HEU_InputUtility.GetInputInterfaceByType(typeof(HEU_InputInterfaceMesh)) as HEU_InputInterfaceMesh; HEU_InputInterfaceMesh.HEU_InputDataMeshes inputMeshes = inputMeshInterface.GenerateMeshDatasFromGameObject(currentGO); if (inputMeshes == null || inputMeshes._inputMeshes.Count == 0) { HEU_Logger.LogWarningFormat("Failed to generate input mesh data for: {0}", currentGO.name); continue; } // Create the input node in Houdini. // Houdini Engine automatically creates a new object to contain the input node. string inputName = null; HAPI_NodeId inputNodeID = HEU_Defines.HEU_INVALID_NODE_ID; session.CreateInputNode(out inputNodeID, inputName); if (inputNodeID == HEU_Defines.HEU_INVALID_NODE_ID || !HEU_HAPIUtility.IsNodeValidInHoudini(session, inputNodeID)) { HEU_Logger.LogErrorFormat("Failed to create new input node in Houdini session!"); break; } // Need the HAPI_NodeInfo of the new input node to get its details, such as parent object ID. HAPI_NodeInfo nodeInfo = new HAPI_NodeInfo(); if (!session.GetNodeInfo(inputNodeID, ref nodeInfo)) { break; } // Cook the node to make sure everything is alright. if (!session.CookNode(inputNodeID, false)) { session.DeleteNode(nodeInfo.parentId); HEU_Logger.LogErrorFormat("New input node failed to cook!"); break; } // Now upload the mesh data into the input node. if (!inputMeshInterface.UploadData(session, inputNodeID, inputMeshes)) { session.DeleteNode(nodeInfo.parentId); HEU_Logger.LogErrorFormat("Failed to upload input mesh data"); break; } // Create UVLayout node in Houdini. Passing in the input node's parent node's ID will // create it within the same object as the input node.. HAPI_NodeId uvlayoutID = -1; if (!session.CreateNode(nodeInfo.parentId, "uvlayout", "UVLayout", true, out uvlayoutID)) { session.DeleteNode(nodeInfo.parentId); break; } // Example showing how to set the parameter on the new UVLayout node. session.SetParamIntValue(uvlayoutID, "correctareas", 0, 1); // Connect the input node to the UVLayout node. // Important bit here is the node IDs being passed in. if (!session.ConnectNodeInput(uvlayoutID, 0, inputNodeID, 0)) { session.DeleteNode(nodeInfo.parentId); break; } // Force cook the UVLayout node in Houdini. if (!HEU_HAPIUtility.CookNodeInHoudini(session, uvlayoutID, true, "uvlayout")) { session.DeleteNode(nodeInfo.parentId); break; } // Now its time to generate the actual output in Unity. A couple of utlity classes will help here. // materialCache will contain the list of materials generated.. List <HEU_MaterialData> materialCache = new List <HEU_MaterialData>(); // Suggested name of the folder within this project where output files might be written out to (eg. materials). string assetCachePathName = "uvlayoutcache"; // First create a HEU_GenerateGeoCache which will contain the geometry data from Houdiini. // This will get all the geometry data buffers from Houdini from the UVLayout node, along with the materials (new or existing). HEU_GenerateGeoCache geoCache = HEU_GenerateGeoCache.GetPopulatedGeoCache(session, inputNodeID, uvlayoutID, 0, bHasLODGroup, materialCache, assetCachePathName); if (geoCache == null) { session.DeleteNode(nodeInfo.parentId); break; } // Store reorganized data buffers into mesh groups. Groups are created if its a LOD mesh. List <HEU_GeoGroup> LODGroupMeshes = null; // The default material identifier (used when no material is supplied initially in Unity). int defaultMaterialKey = 0; // Flag whether to generate UVs, tangents, normals in Unity (in case they weren't created in Houdini). bool bGenerateUVs = false; bool bGenerateTangents = false; bool bGenerateNormals = false; bool bPartInstanced = false; // Now reorganize the data buffers into Unity mesh friendly format. // This handles point splitting into vertices, collider groups, submeshes based on multiple materials, LOD groups. // Can instead use HEU_GenerateGeoCache.GenerateGeoGroupUsingGeoCachePoints to keep as points instead. bool bResult = HEU_GenerateGeoCache.GenerateGeoGroupUsingGeoCacheVertices(session, geoCache, bGenerateUVs, bGenerateTangents, bGenerateNormals, bHasLODGroup, bPartInstanced, out LODGroupMeshes, out defaultMaterialKey); if (!bResult) { session.DeleteNode(nodeInfo.parentId); break; } // This will hold the output gameobject, along with any children and materials. HEU_GeneratedOutput generatedOutput = new HEU_GeneratedOutput(); if (outputMode == OutputMode.COPY) { // For copy mode, create and set new gameobject as output generatedOutput._outputData._gameObject = HEU_GeneralUtility.CreateNewGameObject(currentGO.name + "_HEU_modified"); } else if (outputMode == OutputMode.REPLACE) { // For replace, just use current input gameobject generatedOutput._outputData._gameObject = currentGO; } // Now generate the Unity meshes with material assignment. Handle LOD groups. int numLODs = LODGroupMeshes != null ? LODGroupMeshes.Count : 0; if (numLODs > 1) { bResult = HEU_GenerateGeoCache.GenerateLODMeshesFromGeoGroups(session, LODGroupMeshes, geoCache, generatedOutput, defaultMaterialKey, bGenerateUVs, bGenerateTangents, bGenerateNormals, bPartInstanced); } else if (numLODs == 1) { bResult = HEU_GenerateGeoCache.GenerateMeshFromSingleGroup(session, LODGroupMeshes[0], geoCache, generatedOutput, defaultMaterialKey, bGenerateUVs, bGenerateTangents, bGenerateNormals, bPartInstanced); } // Clean up by deleting the object node containing the input and uvlayout node. session.DeleteNode(nodeInfo.parentId); } }