/// <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 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);
    }
    /// <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);
        }
    }