/// <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())
        {
            Debug.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)
        {
            Debug.LogWarningFormat("Object with name {0} not found in asset {1}!", objName, houdiniAsset.AssetName);
            return;
        }

        HEU_GeoNode geoNode = objNode.GetGeoNode(geoName);

        if (geoNode == null)
        {
            Debug.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)
        {
            Debug.LogWarningFormat("Attribute {0} not found in asset.", attrName);
        }

        Debug.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>
    /// Query the attribute data by storage type.
    /// </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="attrInfo">A valid HAPI_AttributeInfo represendting the attribute</param>
    /// <param name="attrName">Name of the attribute</param>
    public static void QueryAttributeByStorageType(HEU_SessionBase session, HAPI_NodeId geoID, HAPI_PartId partID,
                                                   ref HAPI_AttributeInfo attrInfo, string attrName)
    {
        // Attribute values are usually accessed as arrays by their data type.

#pragma warning disable 0219   // Ignore unused warning

        if (attrInfo.storage == HAPI_StorageType.HAPI_STORAGETYPE_INT)
        {
            int[] data = new int[attrInfo.count];
            HEU_GeneralUtility.GetAttribute(session, geoID, partID, attrName, ref attrInfo, ref data, session.GetAttributeIntData);
        }
        else if (attrInfo.storage == HAPI_StorageType.HAPI_STORAGETYPE_FLOAT)
        {
            float[] data = new float[attrInfo.count];
            HEU_GeneralUtility.GetAttribute(session, geoID, partID, attrName, ref attrInfo, ref data, session.GetAttributeFloatData);
        }
        else if (attrInfo.storage == HAPI_StorageType.HAPI_STORAGETYPE_STRING)
        {
            string[] data = HEU_GeneralUtility.GetAttributeStringData(session, geoID, partID, attrName, ref attrInfo);
        }

#pragma warning restore 0219
    }
    /// <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);
        }
    }