コード例 #1
0
		public void Initialize(HEU_GeoNode ownerNode, int tileIndex)
		{
			_ownerNode = ownerNode;
			_geoName = ownerNode.GeoName;
			_objName = ownerNode.ObjectNode.ObjectName;
			_tileIndex = tileIndex;
		}
コード例 #2
0
	private HEU_GeoNode CreateGeoNode(HEU_SessionBase session, HAPI_GeoInfo geoInfo)
	{
	    HEU_GeoNode geoNode = ScriptableObject.CreateInstance<HEU_GeoNode>();
	    geoNode.Initialize(session, geoInfo, this);
	    geoNode.UpdateGeo(session);
	    return geoNode;
	}
コード例 #3
0
		public void Initialize(HEU_GeoNode ownerNode, int tileIndex)
		{
			_ownerNode = ownerNode;
			_geoName = ownerNode.GeoName;
			_objName = ownerNode.ObjectNode.ObjectName;
			_tileIndex = tileIndex;
			_terrainData = null;
			_scatterTrees = null;
		}
コード例 #4
0
		public void UpdateLayerFromPart(HEU_SessionBase session, HEU_PartData part)
		{
			HEU_GeoNode geoNode = part.ParentGeoNode;

			HAPI_VolumeInfo volumeInfo = new HAPI_VolumeInfo();
			bool bResult = session.GetVolumeInfo(geoNode.GeoID, part.PartID, ref volumeInfo);
			if (!bResult || volumeInfo.tupleSize != 1 || volumeInfo.zLength != 1 || volumeInfo.storage != HAPI_StorageType.HAPI_STORAGETYPE_FLOAT)
			{
				return;
			}

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

			//Debug.LogFormat("Part name: {0}, GeoName: {1}, Volume Name: {2}, Display: {3}", part.PartName, geoNode.GeoName, volumeName, geoNode.Displayable);

			bool bHeightPart = volumeName.Equals("height");

			HEU_VolumeLayer layer = GetLayer(volumeName);
			if (layer == null)
			{
				layer = new HEU_VolumeLayer();
				layer._layerName = volumeName;

				if (bHeightPart)
				{
					_layers.Insert(0, layer);
				}
				else
				{
					_layers.Add(layer);
				}
			}

			layer._part = part;

			GetPartLayerAttributes(session, geoNode.GeoID, part.PartID, layer);

			if (!bHeightPart)
			{
				part.DestroyAllData();
			}

			if (!_updatedLayers.Contains(layer))
			{
				if (bHeightPart)
				{
					_updatedLayers.Insert(0, layer);
				}
				else
				{
					_updatedLayers.Add(layer);
				}
			}
		}
コード例 #5
0
		private void UpdateVolumeLayers(HEU_SessionBase session, HEU_HoudiniAsset houdiniAsset, List<HEU_PartData> volumeParts)
		{
			bool bResult;
			foreach (HEU_PartData part in volumeParts)
			{
				HEU_GeoNode geoNode = part.ParentGeoNode;

				HAPI_VolumeInfo volumeInfo = new HAPI_VolumeInfo();
				bResult = session.GetVolumeInfo(geoNode.GeoID, part.PartID, ref volumeInfo);
				if (!bResult || volumeInfo.tupleSize != 1 || volumeInfo.zLength != 1 || volumeInfo.storage != HAPI_StorageType.HAPI_STORAGETYPE_FLOAT)
				{
					continue;
				}

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

				//Debug.LogFormat("Part name: {0}, GeoName: {1}, Volume Name: {2}, Display: {3}", part.PartName, geoNode.GeoName, volumeName, geoNode.Displayable);

				bool bHeightPart = volumeName.Equals("height");

				HEU_VolumeLayer layer = GetLayer(volumeName);
				if (layer == null)
				{
					layer = new HEU_VolumeLayer();
					layer._layerName = volumeName;

					layer._splatTexture = LoadDefaultSplatTexture();

					if (bHeightPart)
					{
						_layers.Insert(0, layer);
					}
					else
					{
						_layers.Add(layer);
					}
				}

				layer._part = part;

				if (!bHeightPart)
				{
					part.DestroyAllData();
				}
			}
		}
コード例 #6
0
ファイル: HEU_VolumeCache.cs プロジェクト: Raikuns/VR-Dining
		private void ParseVolumeDatas(HEU_SessionBase session, List<HEU_PartData> volumeParts)
		{
			bool bResult;
			foreach (HEU_PartData part in volumeParts)
			{
				HEU_GeoNode geoNode = part.ParentGeoNode;

				HAPI_VolumeInfo volumeInfo = new HAPI_VolumeInfo();
				bResult = session.GetVolumeInfo(geoNode.GeoID, part.PartID, ref volumeInfo);
				if(!bResult || volumeInfo.tupleSize != 1 || volumeInfo.zLength != 1 || volumeInfo.storage != HAPI_StorageType.HAPI_STORAGETYPE_FLOAT)
				{
					continue;
				}

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

				//Debug.LogFormat("Part name: {0}, GeoName: {1}, Volume Name: {2}, Display: {3}", part.PartName, geoNode.GeoName, volumeName, geoNode.Displayable);

				if(volumeName.Equals("height"))
				{
					if (_heightMapVolumeData == null)
					{
						_heightMapVolumeData = new HEU_VolumeData();
						_heightMapVolumeData._partData = part;
						_heightMapVolumeData._volumeInfo = volumeInfo;
					}
				}
				else
				{
					HEU_VolumeData volumeData = new HEU_VolumeData();
					volumeData._partData = part;
					volumeData._volumeInfo = volumeInfo;
					_textureVolumeDatas.Add(volumeData);
				}
			}
		}
コード例 #7
0
		public void UpdateLayerFromPart(HEU_SessionBase session, HEU_PartData part)
		{
			HEU_GeoNode geoNode = part.ParentGeoNode;

			HAPI_VolumeInfo volumeInfo = new HAPI_VolumeInfo();
			bool bResult = session.GetVolumeInfo(geoNode.GeoID, part.PartID, ref volumeInfo);
			if (!bResult || volumeInfo.tupleSize != 1 || volumeInfo.zLength != 1 || volumeInfo.storage != HAPI_StorageType.HAPI_STORAGETYPE_FLOAT)
			{
				return;
			}

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

			//Debug.LogFormat("Part name: {0}, GeoName: {1}, Volume Name: {2}, Display: {3}", part.PartName, geoNode.GeoName, volumeName, geoNode.Displayable);

			bool bHeightPart = volumeName.Equals(HEU_Defines.HAPI_HEIGHTFIELD_LAYERNAME_HEIGHT);
			bool bMaskPart = volumeName.Equals(HEU_Defines.HAPI_HEIGHTFIELD_LAYERNAME_MASK);

			HEU_VolumeLayer layer = GetLayer(volumeName);
			if (layer == null)
			{
				layer = new HEU_VolumeLayer();
				layer._layerName = volumeName;

				if (bHeightPart)
				{
					_layers.Insert(0, layer);
				}
				else if(!bMaskPart)
				{
					_layers.Add(layer);
				}
			}

			layer._part = part;
			layer._xLength = volumeInfo.xLength;
			layer._yLength = volumeInfo.yLength;

			if (!bMaskPart)
			{
				GetPartLayerAttributes(session, geoNode.GeoID, part.PartID, layer);
			}

			if (!bHeightPart)
			{
				// Non-height parts don't have any outputs as they are simply layers carrying info
				part.DestroyAllData();
			}
			else
			{
				// Height part

				List<HEU_TreePrototypeInfo> treePrototypeInfos = HEU_TerrainUtility.GetTreePrototypeInfosFromPart(session, geoNode.GeoID, part.PartID);
				if (treePrototypeInfos != null)
				{
					if (_scatterTrees == null)
					{
						_scatterTrees = new HEU_VolumeScatterTrees();
					}
					_scatterTrees._treePrototypInfos = treePrototypeInfos;
				}
			}

			if (!_updatedLayers.Contains(layer))
			{
				if (bHeightPart)
				{
					_updatedLayers.Insert(0, layer);
				}
				else if (!bMaskPart)
				{
					_updatedLayers.Add(layer);
				}
			}
		}
コード例 #8
0
		//	LOGIC -----------------------------------------------------------------------------------------------------

		public static List<HEU_VolumeCache> UpdateVolumeCachesFromParts(HEU_SessionBase session, HEU_GeoNode ownerNode, List<HEU_PartData> volumeParts, List<HEU_VolumeCache> volumeCaches)
		{
			HEU_HoudiniAsset parentAsset = ownerNode.ParentAsset;

			foreach (HEU_VolumeCache cache in volumeCaches)
			{
				// Remove current volume caches from parent asset.
				// These get added back in below.
				parentAsset.RemoveVolumeCache(cache);

				// Mark the cache for updating
				cache.StartUpdateLayers();
			}

			// This will keep track of volume caches still in use
			List<HEU_VolumeCache> updatedCaches = new List<HEU_VolumeCache>();

			int numParts = volumeParts.Count;
			for (int i = 0; i < numParts; ++i)
			{
				// 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, ownerNode.GeoID, volumeParts[i].PartID, HEU_Defines.HAPI_HEIGHTFIELD_TILE_ATTR, ref tileAttrInfo, ref tileAttrData, session.GetAttributeIntData);
				if (tileAttrData != null && tileAttrData.Length > 0)
				{
					//Debug.LogFormat("Tile: {0}", tileAttrData[0]);

					int tile = tileAttrData[0];
					HEU_VolumeCache volumeCache = null;

					// Find cache in updated list
					for (int j = 0; j < updatedCaches.Count; ++j)
					{
						if (updatedCaches[j] != null && updatedCaches[j].TileIndex == tile)
						{
							volumeCache = updatedCaches[j];
							break;
						}
					}

					if (volumeCache != null)
					{
						volumeCache.UpdateLayerFromPart(session, volumeParts[i]);

						// Skip adding new cache since already found in updated list
						continue;
					}

					// Find existing cache in old list
					if (volumeCaches != null && volumeCaches.Count > 0)
					{
						for(int j = 0; j < volumeCaches.Count; ++j)
						{
							if (volumeCaches[j] != null && volumeCaches[j].TileIndex == tile)
							{
								volumeCache = volumeCaches[j];
								break;
							}
						}
					}

					// Create new cache for this tile if not found
					if (volumeCache == null)
					{
						volumeCache = ScriptableObject.CreateInstance<HEU_VolumeCache>();
						volumeCache.Initialize(ownerNode, tile);
						volumeCache.StartUpdateLayers();
					}

					volumeCache.UpdateLayerFromPart(session, volumeParts[i]);

					if (!updatedCaches.Contains(volumeCache))
					{
						updatedCaches.Add(volumeCache);
					}
				}
				else
				{
					// No tile index. Most likely a single terrain tile.

					HEU_VolumeCache volumeCache = null;

					if (updatedCaches.Count == 0)
					{
						// Create a single volume cache, or use existing if it was just 1.
						// If more than 1 volume cache exists, this will recreate a single one

						if (volumeCaches == null || volumeCaches.Count != 1)
						{
							volumeCache = ScriptableObject.CreateInstance<HEU_VolumeCache>();
							volumeCache.Initialize(ownerNode, 0);
							volumeCache.StartUpdateLayers();
						}
						else if (volumeCaches.Count == 1)
						{
							// Keep the single volumecache
							volumeCache = volumeCaches[0];
						}

						if (!updatedCaches.Contains(volumeCache))
						{
							updatedCaches.Add(volumeCache);
						}
					}
					else
					{
						// Reuse the updated cache
						volumeCache = updatedCaches[0];
					}

					volumeCache.UpdateLayerFromPart(session, volumeParts[i]);
				}
			}

			foreach (HEU_VolumeCache cache in updatedCaches)
			{
				// Add to parent for UI and preset
				parentAsset.AddVolumeCache(cache);

				// Finish update by keeping just the layers in use for each volume cache.
				cache.FinishUpdateLayers();
			}

			return updatedCaches;
		}
コード例 #9
0
	public void GenerateGeometry(HEU_SessionBase session, bool bRebuild)
	{
	    // Volumes could come in as a geonode + part for each heightfield layer.
	    // Otherwise the other geo types can be done individually.

	    bool bResult = false;

	    List<HEU_PartData> meshParts = new List<HEU_PartData>();
	    List<HEU_PartData> volumeParts = new List<HEU_PartData>();

	    List<HEU_PartData> partsToDestroy = new List<HEU_PartData>();

	    HEU_HoudiniAsset parentAsset = ParentAsset;

	    foreach (HEU_GeoNode geoNode in _geoNodes)
	    {
		geoNode.GetPartsByOutputType(meshParts, volumeParts);

		if (volumeParts.Count > 0)
		{
		    // Volumes
		    // Each layer in the volume is retrieved as a volume part, in the display geo node. 
		    // But we need to handle all layers as 1 terrain output in Unity, with 1 height layer and 
		    // other layers as alphamaps.
		    geoNode.ProcessVolumeParts(session, volumeParts, bRebuild);

		    // Clear the volume parts after processing since we are done with this set
		    volumeParts.Clear();
		}
	    }

	    // Meshes
	    foreach (HEU_PartData part in meshParts)
	    {
		// This returns false when there is no valid geometry or is not instancing. Should remove it as otherwise
		// stale data sticks around on recook
		bResult = part.GenerateMesh(session, parentAsset.GenerateUVs, parentAsset.GenerateTangents, parentAsset.GenerateNormals, parentAsset.UseLODGroups);
		if (!bResult)
		{
		    partsToDestroy.Add(part);
		}
	    }

	    int numPartsToDestroy = partsToDestroy.Count;
	    for (int i = 0; i < numPartsToDestroy; ++i)
	    {
		HEU_GeoNode parentNode = partsToDestroy[i].ParentGeoNode;
		if (parentNode != null)
		{
		    parentNode.RemoveAndDestroyPart(partsToDestroy[i]);
		}
		else
		{
		    HEU_PartData.DestroyPart(partsToDestroy[i]);
		}
	    }
	    partsToDestroy.Clear();

	    ApplyObjectTransformToGeoNodes();

	    // Set visibility and attribute-based tag, layer, and scripts
	    bool bIsVisible = IsVisible();
	    foreach (HEU_GeoNode geoNode in _geoNodes)
	    {
		geoNode.CalculateVisiblity(bIsVisible);
		geoNode.CalculateColliderState();

		geoNode.SetAttributeModifiersOnPartOutputs(session);
	    }

	    // Create editable attributes.
	    // This should happen after visibility has been calculated above
	    // since we need to show/hide the intermediate geometry during painting.
	    foreach (HEU_PartData part in meshParts)
	    {
		if (part.ParentGeoNode.IsIntermediateOrEditable())
		{
		    part.SetupAttributeGeometry(session);
		}
	    }
	}
コード例 #10
0
ファイル: HEU_ObjectNode.cs プロジェクト: all-in-one-of/pds
		public void GenerateGeometry(HEU_SessionBase session)
		{
			// Volumes could come in as a geonode + part for each heightfield layer.
			// Otherwise the other geo types can be done individually.

			bool bResult = false;

			List<HEU_PartData> meshParts = new List<HEU_PartData>();
			List<HEU_PartData> volumeParts = new List<HEU_PartData>();

			List<HEU_PartData> partsToDestroy = new List<HEU_PartData>();

			HEU_HoudiniAsset parentAsset = ParentAsset;

			foreach (HEU_GeoNode geoNode in _geoNodes)
			{
				geoNode.GetPartsByOutputType(meshParts, volumeParts);
			}

			// Meshes
			foreach (HEU_PartData part in meshParts)
			{
				bResult = part.GenerateMesh(session, parentAsset.GenerateUVs, parentAsset.GenerateTangents, parentAsset.GenerateNormals, parentAsset.UseLODGroups);
				if (!bResult)
				{
					partsToDestroy.Add(part);
				}
			}

#if TERRAIN_SUPPORTED
			// Volumes
			// Each layer in the volume is retrieved as a volume part, in the display geo node. 
			// But we need to handle all layers as 1 terrain output in Unity, with 1 height layer and 
			// other layers as alphamaps.
			if (volumeParts.Count > 0)
			{
				HEU_PartData heightLayerPart = null;
				HEU_VolumeCache volumeCache = new HEU_VolumeCache();
				volumeCache.GenerateTerrainFromParts(session, volumeParts, ParentAsset, out heightLayerPart);

				// Remove volume parts that are not the height layer (even if heightLayerPart is null)
				foreach (HEU_PartData part in volumeParts)
				{
					if (part != heightLayerPart)
					{
						partsToDestroy.Add(part);
					}
				}
			}
#endif

			int numPartsToDestroy = partsToDestroy.Count;
			for(int i = 0; i < numPartsToDestroy; ++i)
			{
				HEU_GeoNode parentNode = partsToDestroy[i].ParentGeoNode;
				if (parentNode != null)
				{
					parentNode.RemoveAndDestroyPart(partsToDestroy[i]);
				}
				else
				{
					HEU_PartData.DestroyPart(partsToDestroy[i]);
				}
			}
			partsToDestroy.Clear();

			ApplyObjectTransformToGeoNodes();

			// Set visibility
			bool bIsVisible = IsVisible();
			foreach (HEU_GeoNode geoNode in _geoNodes)
			{
				geoNode.CalculateVisiblity(bIsVisible);
			}

			// Create editable attributes.
			// This should happen after visibility has been calculated above
			// since we need to show/hide the intermediate geometry during painting.
			foreach (HEU_PartData part in meshParts)
			{
				if (part.ParentGeoNode.IsIntermediateOrEditable())
				{
					part.SetupAttributeGeometry(session);
				}
			}
		}
コード例 #11
0
	public void UpdateLayerFromPart(HEU_SessionBase session, HEU_PartData part)
	{
	    HEU_GeoNode geoNode = part.ParentGeoNode;

	    HAPI_VolumeInfo volumeInfo = new HAPI_VolumeInfo();
	    bool bResult = session.GetVolumeInfo(geoNode.GeoID, part.PartID, ref volumeInfo);
	    if (!bResult || volumeInfo.tupleSize != 1 || volumeInfo.zLength != 1 || volumeInfo.storage != HAPI_StorageType.HAPI_STORAGETYPE_FLOAT)
	    {
		return;
	    }

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

	    //Debug.LogFormat("Part name: {0}, GeoName: {1}, Volume Name: {2}, Display: {3}", part.PartName, geoNode.GeoName, volumeName, geoNode.Displayable);

	    HFLayerType layerType = HEU_TerrainUtility.GetHeightfieldLayerType(session, geoNode.GeoID, part.PartID, volumeName);

	    HEU_VolumeLayer layer = GetLayer(volumeName);
	    if (layer == null)
	    {
		layer = new HEU_VolumeLayer();
		layer._layerName = volumeName;

		if (layerType == HFLayerType.HEIGHT)
		{
		    _layers.Insert(0, layer);
		}
		else if (layerType != HFLayerType.MASK)
		{
		    _layers.Add(layer);
		}
	    }

	    layer._part = part;
	    layer._xLength = volumeInfo.xLength;
	    layer._yLength = volumeInfo.yLength;
	    layer._layerType = layerType;

	    if (layerType != HFLayerType.MASK)
	    {
		GetPartLayerAttributes(session, geoNode.GeoID, part.PartID, layer);
	    }

	    if (layerType != HFLayerType.HEIGHT)
	    {
		// Non-height parts don't have any outputs as they are simply layers carrying info
		part.DestroyAllData();
	    }
	    else
	    {
		// Height part
		// Might contain terrain properties via attributes (i.e. not layer specific, but for entire terrain)

		// Scatter Tree Prototypes
		List<HEU_TreePrototypeInfo> treePrototypeInfos = HEU_TerrainUtility.GetTreePrototypeInfosFromPart(session, geoNode.GeoID, part.PartID);
		if (treePrototypeInfos != null)
		{
		    if (_scatterTrees == null)
		    {
			_scatterTrees = new HEU_VolumeScatterTrees();
		    }
		    _scatterTrees._treePrototypInfos = treePrototypeInfos;
		}

		HEU_TerrainUtility.PopulateDetailProperties(session, geoNode.GeoID,
			part.PartID, ref _detailProperties);
	    }

	    if (!_updatedLayers.Contains(layer))
	    {
		if (layerType == HFLayerType.HEIGHT)
		{
		    _updatedLayers.Insert(0, layer);
		}
		else if (layerType != HFLayerType.MASK)
		{
		    _updatedLayers.Add(layer);
		}
	    }
	}
コード例 #12
0
		//	LOGIC -----------------------------------------------------------------------------------------------------

		public void Initialize(HEU_GeoNode ownerNode)
		{
			_ownerNode = ownerNode;
			_geoName = ownerNode.GeoName;
			_objName = ownerNode.ObjectNode.ObjectName;
		}
コード例 #13
0
		public void UpdateLayerFromPart(HEU_SessionBase session, HEU_PartData part)
		{
			HEU_GeoNode geoNode = part.ParentGeoNode;

			HAPI_VolumeInfo volumeInfo = new HAPI_VolumeInfo();
			bool bResult = session.GetVolumeInfo(geoNode.GeoID, part.PartID, ref volumeInfo);
			if (!bResult || volumeInfo.tupleSize != 1 || volumeInfo.zLength != 1 || volumeInfo.storage != HAPI_StorageType.HAPI_STORAGETYPE_FLOAT)
			{
				return;
			}

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

			//Debug.LogFormat("Part name: {0}, GeoName: {1}, Volume Name: {2}, Display: {3}", part.PartName, geoNode.GeoName, volumeName, geoNode.Displayable);

			HEU_VolumeLayer.HFLayerType layerType = GetHeightfieldLayerType(session, geoNode.GeoID, part.PartID, volumeName);

			HEU_VolumeLayer layer = GetLayer(volumeName);
			if (layer == null)
			{
				layer = new HEU_VolumeLayer();
				layer._layerName = volumeName;

				if (layerType == HEU_VolumeLayer.HFLayerType.HEIGHT)
				{
					_layers.Insert(0, layer);
				}
				else if(layerType != HEU_VolumeLayer.HFLayerType.MASK)
				{
					_layers.Add(layer);
				}
			}

			layer._part = part;
			layer._xLength = volumeInfo.xLength;
			layer._yLength = volumeInfo.yLength;
			layer._layerType = layerType;

			if (layerType != HEU_VolumeLayer.HFLayerType.MASK)
			{
				GetPartLayerAttributes(session, geoNode.GeoID, part.PartID, layer);
			}

			if (layerType != HEU_VolumeLayer.HFLayerType.HEIGHT)
			{
				// Non-height parts don't have any outputs as they are simply layers carrying info
				part.DestroyAllData();
			}
			else
			{
				// Height part
				// Might contain terrain properties via attributes (i.e. not layer specific, but for entire terrain)

				// Scatter Tree Prototypes
				List<HEU_TreePrototypeInfo> treePrototypeInfos = HEU_TerrainUtility.GetTreePrototypeInfosFromPart(session, geoNode.GeoID, part.PartID);
				if (treePrototypeInfos != null)
				{
					if (_scatterTrees == null)
					{
						_scatterTrees = new HEU_VolumeScatterTrees();
					}
					_scatterTrees._treePrototypInfos = treePrototypeInfos;
				}

				// Detail distance
				HAPI_AttributeInfo detailDistanceAttrInfo = new HAPI_AttributeInfo();
				int[] detailDistances = new int[0];
				HEU_GeneralUtility.GetAttribute(session, geoNode.GeoID, part.PartID, 
					HEU_Defines.HEIGHTFIELD_DETAIL_DISTANCE, ref detailDistanceAttrInfo, ref detailDistances, 
					session.GetAttributeIntData);

				// Scatter Detail Resolution Per Patch (note that Detail Resolution comes from HF layer size)
				HAPI_AttributeInfo resolutionPatchAttrInfo = new HAPI_AttributeInfo();
				int[] resolutionPatches = new int[0];
				HEU_GeneralUtility.GetAttribute(session, geoNode.GeoID, part.PartID, 
					HEU_Defines.HEIGHTFIELD_DETAIL_RESOLUTION_PER_PATCH, ref resolutionPatchAttrInfo, 
					ref resolutionPatches, session.GetAttributeIntData);


				if (_detailProperties == null)
				{
					_detailProperties = new HEU_DetailProperties();
				}

				// Unity only supports 1 set of detail resolution properties per terrain
				int arraySize = 1;

				if (detailDistanceAttrInfo.exists && detailDistances.Length >= arraySize)
				{
					_detailProperties._detailDistance = detailDistances[0];
				}

				if (resolutionPatchAttrInfo.exists && resolutionPatches.Length >= arraySize)
				{
					_detailProperties._detailResolutionPerPatch = resolutionPatches[0];
				}
			}

			if (!_updatedLayers.Contains(layer))
			{
				if (layerType == HEU_VolumeLayer.HFLayerType.HEIGHT)
				{
					_updatedLayers.Insert(0, layer);
				}
				else if (layerType != HEU_VolumeLayer.HFLayerType.MASK)
				{
					_updatedLayers.Add(layer);
				}
			}
		}