Пример #1
0
		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)
				{
					SetLog(HEU_LoadData.LoadStatus.ERROR, "This heightfield is not supported. Please check documentation.");
					return false;
				}

				if (volumeInfo.xLength != volumeInfo.yLength)
				{
					SetLog(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);

				//Debug.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.GetAttributeStringValueSingle(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
				HAPI_AttributeInfo tileAttrInfo = new HAPI_AttributeInfo();
				int[] tileAttrData = new int[0];
				HEU_GeneralUtility.GetAttribute(session, nodeID, volumeParts[i].id, HEU_Defines.HAPI_HEIGHTFIELD_TILE_ATTR, ref tileAttrInfo, ref tileAttrData, session.GetAttributeIntData);

				int tileIndex = 0;
				if (tileAttrInfo.exists && tileAttrData.Length == 1)
				{
					tileIndex = tileAttrData[0];
				}

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

				Sleep();
			}

			// Each volume buffer is a self contained terrain tile
			foreach(HEU_LoadBufferVolume volumeBuffer in volumeBuffers)
			{
				List<HEU_LoadBufferVolumeLayer> layers = volumeBuffer._splatLayers;
				//Debug.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);

					Sleep();

					// 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;
				HAPI_AttributeInfo tileAttrInfo = new HAPI_AttributeInfo();
				int[] tileAttrData = new int[0];
				if (!HEU_GeneralUtility.GetAttribute(session, nodeID, scatterInstancerParts[i].id, HEU_Defines.HAPI_HEIGHTFIELD_TILE_ATTR, ref tileAttrInfo, ref tileAttrData, session.GetAttributeIntData))
				{
					// Try part 0 (the height layer) to get the tile index.
					// For scatter points merged with HF, in some cases the part ID doesn't have the tile attribute.
					HEU_GeneralUtility.GetAttribute(session, nodeID, 0, HEU_Defines.HAPI_HEIGHTFIELD_TILE_ATTR, ref tileAttrInfo, ref tileAttrData, session.GetAttributeIntData);
				}

				if (tileAttrData != null && tileAttrData.Length > 0)
				{
					terrainTile = tileAttrData[0];
				}

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

				HEU_TerrainUtility.PopulateScatterTrees(session, nodeID, scatterInstancerParts[i].id, scatterInstancerParts[i].pointCount, ref volumeBuffer._scatterTrees);
			}

			return true;
		}
Пример #2
0
	public void GenerateTerrainWithAlphamaps(HEU_SessionBase session, HEU_HoudiniAsset houdiniAsset, bool bRebuild)
	{
	    if (_layers == null || _layers.Count == 0)
	    {
		Debug.LogError("Unable to generate terrain due to lack of heightfield layers!");
		return;
	    }

	    HEU_VolumeLayer heightLayer = _layers[0];

	    HAPI_VolumeInfo heightVolumeInfo = new HAPI_VolumeInfo();
	    bool bResult = session.GetVolumeInfo(_ownerNode.GeoID, heightLayer._part.PartID, ref heightVolumeInfo);
	    if (!bResult)
	    {
		Debug.LogErrorFormat("Unable to get volume info for height layer: {0}!", heightLayer._layerName);
		return;
	    }

	    // Special handling of volume cache presets. It is applied here (if exists) because it might pertain to TerrainData that exists
	    // in the AssetDatabase. If we don't apply here but rather create a new one, the existing file will get overwritten.
	    // Applying the preset here for terrain ensures the TerrainData is reused.
	    // Get the volume preset for this part
	    HEU_VolumeCachePreset volumeCachePreset = houdiniAsset.GetVolumeCachePreset(_ownerNode.ObjectNode.ObjectName, _ownerNode.GeoName, TileIndex);
	    if (volumeCachePreset != null)
	    {
		ApplyPreset(volumeCachePreset);

		// Remove it so that it doesn't get applied when doing the recook step
		houdiniAsset.RemoveVolumeCachePreset(volumeCachePreset);
	    }

	    // The TerrainData and TerrainLayer files needs to be saved out if we create them. This creates the relative folder
	    // path from the Asset's cache folder: {assetCache}/{geo name}/Terrain/Tile{tileIndex}/...
	    string relativeFolderPath = HEU_Platform.BuildPath(_ownerNode.GeoName, HEU_Defines.HEU_FOLDER_TERRAIN, HEU_Defines.HEU_FOLDER_TILE + TileIndex);

	    if (bRebuild)
	    {
		// For full rebuild, re-create the TerrainData instead of using previous
		_terrainData = null;
	    }

	    //Debug.Log("Generating Terrain with AlphaMaps: " + (_terrainData != null ? _terrainData.name : "NONE"));
	    TerrainData terrainData = _terrainData;
	    Vector3 terrainOffsetPosition = Vector3.zero;

	    // Look up TerrainData export file path via attribute if user has set it
	    string userTerrainDataExportPath = HEU_GeneralUtility.GetAttributeStringValueSingle(session, _ownerNode.GeoID, heightLayer._part.PartID,
		    HEU_Defines.DEFAULT_UNITY_HEIGHTFIELD_TERRAINDATA_EXPORT_FILE_ATTR, HAPI_AttributeOwner.HAPI_ATTROWNER_PRIM);

	    // Look up TerrainData file via attribute if user has set it
	    string terrainDataFile = HEU_GeneralUtility.GetAttributeStringValueSingle(session, _ownerNode.GeoID, heightLayer._part.PartID,
		    HEU_Defines.DEFAULT_UNITY_HEIGHTFIELD_TERRAINDATA_FILE_ATTR, HAPI_AttributeOwner.HAPI_ATTROWNER_PRIM);
	    if (!string.IsNullOrEmpty(terrainDataFile))
	    {
		TerrainData loadedTerrainData = HEU_AssetDatabase.LoadAssetAtPath(terrainDataFile, typeof(TerrainData)) as TerrainData;
		if (loadedTerrainData == null)
		{
		    Debug.LogWarningFormat("TerrainData, set via attribute, not found at: {0}", terrainDataFile);
		}
		else
		{
		    // In the case that the specified TerrainData belongs to another Terrain (i.e. input Terrain), make a copy of it.
		    if (!string.IsNullOrEmpty(userTerrainDataExportPath))
		    {
			// Save it to explicit path specified by user
			terrainData = HEU_AssetDatabase.CopyAndLoadAssetAtGivenPath(loadedTerrainData, userTerrainDataExportPath, typeof(TerrainData)) as TerrainData;
		    }
		    else
		    {
			// Save it into cache
			// Note that this overwrites existing TerrainData in our cache because the workflow is 
			// such that attributes will always override local setting.
			string bakedTerrainPath = HEU_Platform.BuildPath(houdiniAsset.GetValidAssetCacheFolderPath(), relativeFolderPath);
			terrainData = HEU_AssetDatabase.CopyAndLoadAssetAtAnyPath(loadedTerrainData, bakedTerrainPath, typeof(TerrainData), true) as TerrainData;
			if (terrainData == null)
			{
			    Debug.LogErrorFormat("Unable to copy TerrainData from {0} for generating Terrain.", terrainDataFile);
			}
		    }
		}
	    }

	    Terrain terrain = null;

	    // Generate the terrain and terrain data from the height layer. This applies height values.
	    bResult = HEU_TerrainUtility.GenerateTerrainFromVolume(session, ref heightVolumeInfo, heightLayer._part.ParentGeoNode.GeoID,
		    heightLayer._part.PartID, heightLayer._part.OutputGameObject, ref terrainData, out terrainOffsetPosition,
		    ref terrain);
	    if (!bResult || terrainData == null)
	    {
		return;
	    }

	    if (_terrainData != terrainData)
	    {
		_terrainData = terrainData;
		heightLayer._part.SetTerrainData(terrainData, relativeFolderPath, userTerrainDataExportPath);
	    }

	    heightLayer._part.SetTerrainOffsetPosition(terrainOffsetPosition);

	    int terrainSize = terrainData.heightmapResolution;

	    // Now process TerrainLayers and alpha maps

	    // First, preprocess all layers to get heightfield arrays, converted to proper size
	    List<float[]> normalizedHeightfields = new List<float[]>();
	    // Corresponding list of HF volume layers to process as splatmaps
	    List<HEU_VolumeLayer> terrainLayersToProcess = new List<HEU_VolumeLayer>();

	    List<int[,]> convertedDetailMaps = new List<int[,]>();
	    List<HEU_DetailPrototype> detailPrototypes = new List<HEU_DetailPrototype>();

	    int numLayers = _layers.Count;
	    float minHeight = 0;
	    float maxHeight = 0;
	    float heightRange = 0;
	    // This skips the height layer, and processes all other layers.
	    // Note that mask shouldn't be part of _layers at this point.
	    // The layers are normalized and split into detail and terrain layers.
	    for (int i = 1; i < numLayers; ++i)
	    {
		if (_layers[i]._layerType == HFLayerType.DETAIL)
		{
		    if (_detailProperties == null)
		    {
			_detailProperties = new HEU_DetailProperties();
		    }

		    // Convert to detail map, and add to list to set later
		    int[,] normalizedDetail = HEU_TerrainUtility.GetDetailMapFromPart(
			    session, _ownerNode.GeoID, _layers[i]._part.PartID, out _detailProperties._detailResolution);
		    if (normalizedDetail != null && normalizedDetail.Length > 0)
		    {
			convertedDetailMaps.Add(normalizedDetail);
			detailPrototypes.Add(_layers[i]._detailPrototype);
		    }
		}
		else
		{
		    // Convert to normalized heightfield
		    float[] normalizedHF = HEU_TerrainUtility.GetNormalizedHeightmapFromPartWithMinMax(
			    session, _ownerNode.GeoID, _layers[i]._part.PartID, _layers[i]._xLength,
			    _layers[i]._yLength, ref minHeight, ref maxHeight, ref heightRange, false);
		    if (normalizedHF != null && normalizedHF.Length > 0)
		    {
			normalizedHeightfields.Add(normalizedHF);
			terrainLayersToProcess.Add(_layers[i]);
		    }
		}
	    }

	    int numTerrainLayersToProcess = terrainLayersToProcess.Count;

	    HAPI_NodeId geoID;
	    HAPI_PartId partID;

	    Texture2D defaultTexture = LoadDefaultSplatTexture();

#if UNITY_2018_3_OR_NEWER

	    // Create or update the terrain layers based on heightfield layers.

	    // Keep existing TerrainLayers, and either update or append to them
	    TerrainLayer[] existingTerrainLayers = terrainData.terrainLayers;

	    // Total layers are existing layers + new alpha maps
	    List<TerrainLayer> finalTerrainLayers = new List<TerrainLayer>(existingTerrainLayers);

	    // This holds the alpha map indices for each layer that will be added to the TerrainData.
	    // The alpha maps could be a mix of existing and new values, so need to know which to use
	    // Initially set to use existing alpha maps, then override later on if specified via HF layers
	    List<int> alphaMapIndices = new List<int>();
	    for (int a = 0; a < existingTerrainLayers.Length; ++a)
	    {
		// Negative indices for existing alpha map (offset by -1)
		alphaMapIndices.Add(-a - 1);
	    }

	    bool bNewTerrainLayer = false;
	    HEU_VolumeLayer layer = null;
	    TerrainLayer terrainLayer = null;
	    bool bSetTerrainLayerProperties = true;
	    for (int m = 0; m < numTerrainLayersToProcess; ++m)
	    {
		bNewTerrainLayer = false;
		bSetTerrainLayerProperties = true;

		layer = terrainLayersToProcess[m];

		geoID = _ownerNode.GeoID;
		partID = layer._part.PartID;

		terrainLayer = null;

		int terrainLayerIndex = -1;

		// The TerrainLayer attribute overrides existing TerrainLayer. So if its set, load and use it.
		string terrainLayerFile = HEU_GeneralUtility.GetAttributeStringValueSingle(session, geoID, partID,
				HEU_Defines.DEFAULT_UNITY_HEIGHTFIELD_TERRAINLAYER_FILE_ATTR, HAPI_AttributeOwner.HAPI_ATTROWNER_PRIM);
		if (!string.IsNullOrEmpty(terrainLayerFile))
		{
		    terrainLayer = HEU_AssetDatabase.LoadAssetAtPath(terrainLayerFile, typeof(TerrainLayer)) as TerrainLayer;
		    if (terrainLayer == null)
		    {
			Debug.LogWarningFormat("TerrainLayer, set via attribute, not found at: {0}", terrainLayerFile);
			// Not earlying out or skipping this layer due to error because we want to keep proper indexing
			// by creating a new TerrainLayer.
		    }
		    else
		    {
			// TerrainLayer loaded from attribute. 
			// It could be an existing TerrainLayer that is already part of finalTerrainLayers 
			// or could be a new one which needs to be added.

			// If its a different TerrainLayer than existing, update the finalTerrainLayers, and index.
			if (layer._terrainLayer != null && layer._terrainLayer != terrainLayer)
			{
			    terrainLayerIndex = HEU_TerrainUtility.GetTerrainLayerIndex(layer._terrainLayer, existingTerrainLayers);
			    if (terrainLayerIndex >= 0)
			    {
				finalTerrainLayers[terrainLayerIndex] = terrainLayer;
			    }
			}

			if (terrainLayerIndex == -1)
			{
			    // Always check if its part of existing list so as not to add it again
			    terrainLayerIndex = HEU_TerrainUtility.GetTerrainLayerIndex(terrainLayer, existingTerrainLayers);
			}
		    }
		}

		// No terrain layer specified, so try using existing if we have it
		if (terrainLayer == null)
		{
		    terrainLayerIndex = HEU_TerrainUtility.GetTerrainLayerIndex(layer._terrainLayer, existingTerrainLayers);
		    if (terrainLayerIndex >= 0)
		    {
			// Note the terrainLayerIndex is same for finalTerrainLayers as existingTerrainLayers
			terrainLayer = existingTerrainLayers[terrainLayerIndex];
		    }
		}

		// Still not found, so just create a new one
		if (terrainLayer == null)
		{
		    terrainLayer = new TerrainLayer();
		    terrainLayer.name = layer._layerName;
		    //Debug.LogFormat("Created new TerrainLayer with name: {0} ", terrainLayer.name);
		    bNewTerrainLayer = true;
		}

		if (terrainLayerIndex == -1)
		{
		    // Adding to the finalTerrainLayers if this is indeed a newly created or loaded TerrainLayer
		    // (i.e. isn't already part of the TerrainLayers for this Terrain).
		    // Save this layer's index for later on if we make a copy.
		    terrainLayerIndex = finalTerrainLayers.Count;
		    finalTerrainLayers.Add(terrainLayer);

		    // Positive index for alpha map from heightfield (starting at 1)
		    alphaMapIndices.Add(m + 1);
		}
		else
		{
		    // Positive index for alpha map from heightfield (starting at 1)
		    alphaMapIndices[terrainLayerIndex] = m + 1;
		}

		// For existing TerrainLayer, make a copy of it if it has custom layer attributes
		// because we don't want to change the original TerrainLayer.
		if (!bNewTerrainLayer && layer._hasLayerAttributes)
		{
		    string bakedTerrainPath = houdiniAsset.GetValidAssetCacheFolderPath();
		    bakedTerrainPath = HEU_Platform.BuildPath(bakedTerrainPath, relativeFolderPath);
		    TerrainLayer prevTerrainLayer = terrainLayer;
		    terrainLayer = HEU_AssetDatabase.CopyAndLoadAssetAtAnyPath(terrainLayer, bakedTerrainPath, typeof(TerrainLayer), true) as TerrainLayer;
		    if (terrainLayer != null)
		    {
			// Update the TerrainLayer reference in the list with this copy
			finalTerrainLayers[terrainLayerIndex] = terrainLayer;
		    }
		    else
		    {
			Debug.LogErrorFormat("Unable to copy TerrainLayer '{0}' for generating Terrain. "
				+ "Using original TerrainLayer. Will not be able to set any TerrainLayer properties.", layer._layerName);
			terrainLayer = prevTerrainLayer;
			bSetTerrainLayerProperties = false;
			// Again, continuing on to keep proper indexing.
		    }
		}

		// Now override layer properties if they have been set via attributes
		if (bSetTerrainLayerProperties)
		{
		    LoadLayerPropertiesFromAttributes(session, geoID, partID, terrainLayer, bNewTerrainLayer, defaultTexture);
		}

		if (bNewTerrainLayer)
		{
		    // In order to retain the new TerrainLayer, it must be saved to the AssetDatabase.
		    Object savedObject = null;
		    string layerFileNameWithExt = terrainLayer.name;
		    if (!layerFileNameWithExt.EndsWith(HEU_Defines.HEU_EXT_TERRAINLAYER))
		    {
			layerFileNameWithExt += HEU_Defines.HEU_EXT_TERRAINLAYER;
		    }
		    houdiniAsset.AddToAssetDBCache(layerFileNameWithExt, terrainLayer, relativeFolderPath, ref savedObject);
		}

		// Store reference
		layer._terrainLayer = terrainLayer;
	    }

	    // Get existing alpha maps so we can reuse the values if needed
	    float[,,] existingAlphaMaps = terrainData.GetAlphamaps(0, 0, terrainData.alphamapWidth, terrainData.alphamapHeight);

	    terrainData.terrainLayers = finalTerrainLayers.ToArray();

	    int numTotalAlphaMaps = finalTerrainLayers.Count;

#else
			// Create or update the SplatPrototype based on heightfield layers.

			// Need to create or reuse SplatPrototype for each layer in heightfield, representing the textures.
			SplatPrototype[] existingSplats = terrainData.splatPrototypes;

			// A full rebuild clears out existing splats, but a regular cook keeps them.
			List<SplatPrototype> finalSplats = new List<SplatPrototype>(existingSplats);

			// This holds the alpha map indices for each layer that will be added to the TerrainData
			// The alpha maps could be a mix of existing and new values, so need to know which to use
			List<int> alphaMapIndices = new List<int>();

			// Initially set to use existing alpha maps, then override later on if specified via HF layers.
			for (int a = 0; a < existingSplats.Length; ++a)
			{
				// Negative indices for existing alpha map (offset by -1)
				alphaMapIndices.Add(-a - 1);
			}

			bool bNewSplat = false;
			HEU_VolumeLayer layer = null;
			SplatPrototype splatPrototype = null;

			for (int m = 0; m < numTerrainLayersToProcess; ++m)
			{
				bNewSplat = false;

				layer = terrainLayersToProcess[m];

				geoID = _ownerNode.GeoID;
				partID = layer._part.PartID;

				// Try to find existing SplatPrototype for reuse. But not for full rebuild.
				splatPrototype = null;
				if (layer._splatPrototypeIndex >= 0 && layer._splatPrototypeIndex < existingSplats.Length)
				{
					splatPrototype = existingSplats[layer._splatPrototypeIndex];

					// Positive index for alpha map from heightfield (starting at 1)
					alphaMapIndices[layer._splatPrototypeIndex] = m + 1;
				}

				if (splatPrototype == null)
				{
					splatPrototype = new SplatPrototype();
					layer._splatPrototypeIndex = finalSplats.Count;
					finalSplats.Add(splatPrototype);

					// Positive index for alpha map from heightfield (starting at 1)
					alphaMapIndices.Add(m + 1);
				}

				// Now override splat properties if they have been set via attributes
				LoadLayerPropertiesFromAttributes(session, geoID, partID, splatPrototype, bNewSplat, defaultTexture);
			}

			// On regular cook, get existing alpha maps so we can reuse the values if needed.
			float[,,] existingAlphaMaps = terrainData.GetAlphamaps(0, 0, terrainData.alphamapWidth, terrainData.alphamapHeight);

			terrainData.splatPrototypes = finalSplats.ToArray();

			int numTotalAlphaMaps = finalSplats.Count;
#endif

	    // Set alpha maps by combining with existing alpha maps, and appending new heightfields

	    float[,,] alphamap = null;
	    if (numTotalAlphaMaps > 0 && terrainLayersToProcess.Count > 0)
	    {
		// Convert the heightfields into alpha maps with layer strengths
		float[] strengths = new float[terrainLayersToProcess.Count];
		for (int m = 0; m < terrainLayersToProcess.Count; ++m)
		{
		    strengths[m] = terrainLayersToProcess[m]._strength;
		}

		alphamap = HEU_TerrainUtility.AppendConvertedHeightFieldToAlphaMap(
			terrainLayersToProcess[0]._xLength, terrainLayersToProcess[0]._yLength, existingAlphaMaps,
			normalizedHeightfields, strengths, alphaMapIndices);

		// Update the alphamap resolution to the actual size of the first 
		// heightfield layer used for the alphamaps.
		// Setting the size before setting the alphamas applies proper scaling.
		int alphamapResolution = terrainLayersToProcess[0]._xLength;
		terrainData.alphamapResolution = alphamapResolution;

		terrainData.SetAlphamaps(0, 0, alphamap);
	    }

	    // Scattering - trees and details
	    HEU_TerrainUtility.ApplyScatterTrees(terrainData, _scatterTrees);
	    HEU_TerrainUtility.ApplyDetailLayers(terrain, terrainData, _detailProperties, detailPrototypes, convertedDetailMaps);

	    // If the layers were writen out, this saves the asset DB. Otherwise user has to save it themselves.
	    // Not 100% sure this is needed, but without this the editor doesn't know the terrain asset has been updated
	    // and therefore doesn't import and show the terrain layer.
	    HEU_AssetDatabase.SaveAssetDatabase();
	}