public bool GenerateTerrainBuffers(HEU_SessionBase session, HAPI_NodeId nodeID, List<HAPI_PartInfo> volumeParts,
		List<HAPI_PartInfo> scatterInstancerParts, out List<HEU_LoadBufferVolume> volumeBuffers)
	{
	    volumeBuffers = null;
	    if (volumeParts.Count == 0)
	    {
		return true;
	    }

	    volumeBuffers = new List<HEU_LoadBufferVolume>();
	    int detailResolution = 0;

	    int numParts = volumeParts.Count;
	    for (int i = 0; i < numParts; ++i)
	    {
		HAPI_VolumeInfo volumeInfo = new HAPI_VolumeInfo();
		bool bResult = session.GetVolumeInfo(nodeID, volumeParts[i].id, ref volumeInfo);
		if (!bResult || volumeInfo.tupleSize != 1 || volumeInfo.zLength != 1 || volumeInfo.storage != HAPI_StorageType.HAPI_STORAGETYPE_FLOAT)
		{
		    AppendLog(HEU_LoadData.LoadStatus.ERROR, "This heightfield is not supported. Please check documentation.");
		    return false;
		}

		if (volumeInfo.xLength != volumeInfo.yLength)
		{
		    AppendLog(HEU_LoadData.LoadStatus.ERROR, "Non-square sized terrain not supported.");
		    return false;
		}

		string volumeName = HEU_SessionManager.GetString(volumeInfo.nameSH, session);

		HFLayerType layerType = HEU_TerrainUtility.GetHeightfieldLayerType(session, nodeID, volumeParts[i].id, volumeName);

		//HEU_Logger.LogFormat("Index: {0}, Part id: {1}, Part Name: {2}, Volume Name: {3}", i, volumeParts[i].id, HEU_SessionManager.GetString(volumeParts[i].nameSH), volumeName);

		// Ignoring mask layer because it is Houdini-specific (same behaviour as regular HDA terrain generation)
		if (layerType == HFLayerType.MASK)
		{
		    continue;
		}

		HEU_LoadBufferVolumeLayer layer = new HEU_LoadBufferVolumeLayer();
		layer._layerName = volumeName;
		layer._partID = volumeParts[i].id;
		layer._heightMapWidth = volumeInfo.xLength;
		layer._heightMapHeight = volumeInfo.yLength;
		layer._layerType = layerType;

		Matrix4x4 volumeTransformMatrix = HEU_HAPIUtility.GetMatrixFromHAPITransform(ref volumeInfo.transform, false);
		layer._position = HEU_HAPIUtility.GetPosition(ref volumeTransformMatrix);
		Vector3 scale = HEU_HAPIUtility.GetScale(ref volumeTransformMatrix);

		// Calculate real terrain size in both Houdini and Unity.
		// The height values will be mapped over this terrain size.
		float gridSpacingX = scale.x * 2f;
		float gridSpacingY = scale.y * 2f;
		layer._terrainSizeX = Mathf.Round((volumeInfo.xLength - 1) * gridSpacingX);
		layer._terrainSizeY = Mathf.Round((volumeInfo.yLength - 1) * gridSpacingY);

		// Get volume bounds for calculating position offset
		session.GetVolumeBounds(nodeID, volumeParts[i].id,
			out layer._minBounds.x, out layer._minBounds.y, out layer._minBounds.z,
			out layer._maxBounds.x, out layer._maxBounds.y, out layer._maxBounds.z,
			out layer._center.x, out layer._center.y, out layer._center.z);

		// Look up TerrainLayer file via attribute if user has set it
		layer._layerPath = HEU_GeneralUtility.GetAttributeStringValueSingleStrict(session, nodeID, volumeParts[i].id,
                                HEU_Defines.DEFAULT_UNITY_HEIGHTFIELD_TERRAINLAYER_FILE_ATTR, HAPI_AttributeOwner.HAPI_ATTROWNER_PRIM);

		if (layerType != HFLayerType.DETAIL)
		{
		    layer._hasLayerAttributes = HEU_TerrainUtility.VolumeLayerHasAttributes(session, nodeID, volumeParts[i].id);

		    if (layer._hasLayerAttributes)
		    {
			LoadStringFromAttribute(session, nodeID, volumeParts[i].id, HEU_Defines.DEFAULT_UNITY_HEIGHTFIELD_TEXTURE_DIFFUSE_ATTR, ref layer._diffuseTexturePath);
			LoadStringFromAttribute(session, nodeID, volumeParts[i].id, HEU_Defines.DEFAULT_UNITY_HEIGHTFIELD_TEXTURE_MASK_ATTR, ref layer._maskTexturePath);
			LoadStringFromAttribute(session, nodeID, volumeParts[i].id, HEU_Defines.DEFAULT_UNITY_HEIGHTFIELD_TEXTURE_NORMAL_ATTR, ref layer._normalTexturePath);

			LoadFloatFromAttribute(session, nodeID, volumeParts[i].id, HEU_Defines.DEFAULT_UNITY_HEIGHTFIELD_NORMAL_SCALE_ATTR, ref layer._normalScale);
			LoadFloatFromAttribute(session, nodeID, volumeParts[i].id, HEU_Defines.DEFAULT_UNITY_HEIGHTFIELD_METALLIC_ATTR, ref layer._metallic);
			LoadFloatFromAttribute(session, nodeID, volumeParts[i].id, HEU_Defines.DEFAULT_UNITY_HEIGHTFIELD_SMOOTHNESS_ATTR, ref layer._smoothness);

			LoadLayerColorFromAttribute(session, nodeID, volumeParts[i].id, HEU_Defines.DEFAULT_UNITY_HEIGHTFIELD_SPECULAR_ATTR, ref layer._specularColor);
			LoadLayerVector2FromAttribute(session, nodeID, volumeParts[i].id, HEU_Defines.DEFAULT_UNITY_HEIGHTFIELD_TILE_OFFSET_ATTR, ref layer._tileOffset);
			LoadLayerVector2FromAttribute(session, nodeID, volumeParts[i].id, HEU_Defines.DEFAULT_UNITY_HEIGHTFIELD_TILE_SIZE_ATTR, ref layer._tileSize);
		    }

		    // Get the height values from Houdini along with the min and max height range.
		    layer._normalizedHeights = HEU_TerrainUtility.GetNormalizedHeightmapFromPartWithMinMax(
			    _session, nodeID, volumeParts[i].id, volumeInfo.xLength, volumeInfo.yLength,
			    ref layer._minHeight, ref layer._maxHeight, ref layer._heightRange,
			    (layerType == HFLayerType.HEIGHT));
		}

		// Get the tile index, if it exists, for this part
		int tileIndex = 0;
		HEU_TerrainUtility.GetAttributeTile(session, nodeID, volumeParts[i].id, out tileIndex);

		// Add layer based on tile index
		if (tileIndex >= 0)
		{
		    HEU_LoadBufferVolume volumeBuffer = null;
		    for (int j = 0; j < volumeBuffers.Count; ++j)
		    {
			if (volumeBuffers[j]._tileIndex == tileIndex)
			{
			    volumeBuffer = volumeBuffers[j];
			    break;
			}
		    }

		    if (volumeBuffer == null)
		    {
			volumeBuffer = new HEU_LoadBufferVolume();
			volumeBuffer.InitializeBuffer(volumeParts[i].id, volumeName, false, false);

			volumeBuffer._tileIndex = tileIndex;
			volumeBuffers.Add(volumeBuffer);
		    }

		    if (layerType == HFLayerType.HEIGHT)
		    {
			// Height layer always first layer
			volumeBuffer._splatLayers.Insert(0, layer);

			volumeBuffer._heightMapWidth = layer._heightMapWidth;
			volumeBuffer._heightMapHeight = layer._heightMapHeight;
			volumeBuffer._terrainSizeX = layer._terrainSizeX;
			volumeBuffer._terrainSizeY = layer._terrainSizeY;
			volumeBuffer._heightRange = layer._heightRange;

			// The terrain heightfield position in y requires offset of min height
			layer._position.y += layer._minHeight;

			// Use y position from attribute if user has set it
			float userYPos;
			if (HEU_GeneralUtility.GetAttributeFloatSingle(session, nodeID, volumeParts[i].id,
				HEU_Defines.DEFAULT_UNITY_HEIGHTFIELD_YPOS, out userYPos))
			{
			    layer._position.y = userYPos;
			}

			// Look up TerrainData file path via attribute if user has set it
			volumeBuffer._terrainDataPath = HEU_GeneralUtility.GetAttributeStringValueSingleStrict(session, nodeID, volumeBuffer._id,
				HEU_Defines.DEFAULT_UNITY_HEIGHTFIELD_TERRAINDATA_FILE_ATTR, HAPI_AttributeOwner.HAPI_ATTROWNER_PRIM);

			// Look up TerrainData export file path via attribute if user has set it
			volumeBuffer._terrainDataExportPath = HEU_GeneralUtility.GetAttributeStringValueSingleStrict(session, nodeID, volumeBuffer._id,
				HEU_Defines.DEFAULT_UNITY_HEIGHTFIELD_TERRAINDATA_EXPORT_FILE_ATTR, HAPI_AttributeOwner.HAPI_ATTROWNER_PRIM);

			// Load the TreePrototype buffers
			List<HEU_TreePrototypeInfo> treePrototypeInfos = HEU_TerrainUtility.GetTreePrototypeInfosFromPart(session, nodeID, volumeBuffer._id);
			if (treePrototypeInfos != null)
			{
			    if (volumeBuffer._scatterTrees == null)
			    {
				volumeBuffer._scatterTrees = new HEU_VolumeScatterTrees();
			    }
			    volumeBuffer._scatterTrees._treePrototypInfos = treePrototypeInfos;
			}

			HEU_TerrainUtility.PopulateDetailProperties(session, nodeID,
				volumeBuffer._id, ref volumeBuffer._detailProperties);

			// Get specified material if any
			volumeBuffer._specifiedTerrainMaterialName = HEU_GeneralUtility.GetMaterialAttributeValueFromPart(session,
				nodeID, volumeBuffer._id);
		    }
		    else if (layer._layerType == HFLayerType.DETAIL)
		    {
			// Get detail prototype
			HEU_DetailPrototype detailPrototype = null;
			HEU_TerrainUtility.PopulateDetailPrototype(session, nodeID, volumeParts[i].id, ref detailPrototype);

			int[,] detailMap = HEU_TerrainUtility.GetDetailMapFromPart(session, nodeID,
				volumeParts[i].id, out detailResolution);

			volumeBuffer._detailPrototypes.Add(detailPrototype);
			volumeBuffer._detailMaps.Add(detailMap);

			// Set the detail resolution which is formed from the detail layer
			if (volumeBuffer._detailProperties == null)
			{
			    volumeBuffer._detailProperties = new HEU_DetailProperties();
			}
			volumeBuffer._detailProperties._detailResolution = detailResolution;
		    }
		    else
		    {
			volumeBuffer._splatLayers.Add(layer);
		    }
		}
	    }

	    // Each volume buffer is a self contained terrain tile
	    foreach (HEU_LoadBufferVolume volumeBuffer in volumeBuffers)
	    {
		List<HEU_LoadBufferVolumeLayer> layers = volumeBuffer._splatLayers;
		//HEU_Logger.LogFormat("Heightfield: tile={0}, layers={1}", tile._tileIndex, layers.Count);

		int heightMapWidth = volumeBuffer._heightMapWidth;
		int heightMapHeight = volumeBuffer._heightMapHeight;

		int numLayers = layers.Count;
		if (numLayers > 0)
		{
		    // Convert heightmap values from Houdini to Unity
		    volumeBuffer._heightMap = HEU_TerrainUtility.ConvertHeightMapHoudiniToUnity(heightMapWidth, heightMapHeight, layers[0]._normalizedHeights);

		    // Convert splatmap values from Houdini to Unity.
		    // Start at 2nd index since height is strictly for height values (not splatmap).
		    List<float[]> heightFields = new List<float[]>();
		    for (int m = 1; m < numLayers; ++m)
		    {
			// Ignore Detail layers as they are handled differently
			if (layers[m]._layerType != HFLayerType.DETAIL)
			{
			    heightFields.Add(layers[m]._normalizedHeights);
			}
		    }

		    // The number of maps are the number of splatmaps (ie. non height/mask layers)
		    int numMaps = heightFields.Count;
		    if (numMaps > 0)
		    {
			// Using the first splatmap size for all splatmaps
			volumeBuffer._splatMaps = HEU_TerrainUtility.ConvertHeightFieldToAlphaMap(layers[1]._heightMapWidth, layers[1]._heightMapHeight, heightFields);
		    }
		    else
		    {
			volumeBuffer._splatMaps = null;
		    }

		    // TODO: revisit how the position is calculated
		    volumeBuffer._position = new Vector3(
			    volumeBuffer._terrainSizeX + volumeBuffer._splatLayers[0]._minBounds.x,
			    volumeBuffer._splatLayers[0]._position.y,
			    volumeBuffer._splatLayers[0]._minBounds.z);
		}
	    }

	    // Process the scatter instancer parts to get the scatter data
	    for (int i = 0; i < scatterInstancerParts.Count; ++i)
	    {
		// Find the terrain tile (use primitive attr). Assume 0 tile if not set (i.e. not split into tiles)
		int terrainTile = 0;
		HEU_TerrainUtility.GetAttributeTile(session, nodeID,  scatterInstancerParts[i].id, out terrainTile);

		// Find the volume layer associated with this part using the terrain tile index
		HEU_LoadBufferVolume volumeBuffer = GetLoadBufferVolumeFromTileIndex(terrainTile, volumeBuffers);
		if (volumeBuffer == null)
		{
		    continue;
		}

		bool throwWarningIfNoTileAttribute = volumeBuffers.Count > 1;

		foreach (HEU_LoadBufferVolume volume in volumeBuffers)
		{
		    HEU_TerrainUtility.PopulateScatterTrees(session, nodeID, scatterInstancerParts[i].id, scatterInstancerParts[i].pointCount, ref volume._scatterTrees, throwWarningIfNoTileAttribute);
		}
	    }

	    return true;
	}
        internal void ProcessVolumeParts(HEU_SessionBase session, List <HEU_PartData> volumeParts, bool bRebuild)
        {
            if (ParentAsset == null)
            {
                return;
            }

            int numVolumeParts = volumeParts.Count;

            if (numVolumeParts == 0)
            {
                DestroyVolumeCache();
            }
            else if (_volumeCaches == null)
            {
                _volumeCaches = new List <HEU_VolumeCache>();
            }

            // First update volume caches. Each volume cache represents a set of terrain layers grouped by tile index.
            // Therefore each volume cache represents a potential Unity Terrain (containing layers)
            _volumeCaches = HEU_VolumeCache.UpdateVolumeCachesFromParts(session, this, volumeParts, _volumeCaches);

            // Heightfield scatter nodes come in as mesh-type parts with attribute instancing.
            // So process them here to get all the tree/detail instance scatter information.
            int numParts = _parts.Count;

            for (int i = 0; i < numParts; ++i)
            {
                // Find the terrain tile (use primitive attr). Assume 0 tile if not set (i.e. not split into tiles)
                int terrainTile = 0;
                HEU_TerrainUtility.GetAttributeTile(session, GeoID, _parts[i].PartID, out terrainTile);

                // Find the volumecache associated with this part using the terrain tile index
                HEU_VolumeCache volumeCache = GetVolumeCacheByTileIndex(terrainTile);
                if (volumeCache == null)
                {
                    continue;
                }

                HEU_VolumeLayer volumeLayer = volumeCache.GetLayer(_parts[i].GetVolumeLayerName());
                if (volumeLayer != null && volumeLayer._layerType == HFLayerType.DETAIL)
                {
                    // Clear out outputs since it might have been created when the part was created.
                    _parts[i].DestroyAllData();

                    volumeCache.PopulateDetailPrototype(session, GeoID, _parts[i].PartID, volumeLayer);
                }
                else if (_parts[i].IsAttribInstancer())
                {
                    HAPI_AttributeInfo treeInstAttrInfo = new HAPI_AttributeInfo();
                    if (HEU_GeneralUtility.GetAttributeInfo(session, GeoID, _parts[i].PartID, HEU_Defines.HEIGHTFIELD_TREEINSTANCE_PROTOTYPEINDEX, ref treeInstAttrInfo))
                    {
                        if (treeInstAttrInfo.exists && treeInstAttrInfo.count > 0)
                        {
                            // Clear out outputs since it might have been created when the part was created.
                            _parts[i].DestroyAllData();

                            // Mark the instancers as having been created so that the object instancer step skips this.
                            _parts[i]._objectInstancesGenerated = true;

                            bool throwWarningIfNoTileAttribute = _volumeCaches.Count > 1;

                            // Now populate scatter trees based on attributes on this part
                            // Note: Might do redudant work for each volume and might need refactoring for performance.
                            foreach (HEU_VolumeCache cache in _volumeCaches)
                            {
                                cache.PopulateScatterTrees(session, GeoID, _parts[i].PartID, treeInstAttrInfo.count, throwWarningIfNoTileAttribute);
                            }
                        }
                    }
                }
            }

            // Now generate the terrain for each volume cache
            foreach (HEU_VolumeCache cache in _volumeCaches)
            {
                cache.GenerateTerrainWithAlphamaps(session, ParentAsset, bRebuild);

                cache.IsDirty = false;
            }
        }