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