コード例 #1
0
	public void CopyValuesTo(HEU_VolumeCache destCache)
	{
	    destCache.UIExpanded = UIExpanded;

	    destCache._terrainData = Object.Instantiate(_terrainData);

	    if (_detailProperties != null)
	    {
		if (destCache._detailProperties == null)
		{
		    destCache._detailProperties = new HEU_DetailProperties();
		}
		CopyDetailProperties(_detailProperties, destCache._detailProperties);
	    }
	    else
	    {
		destCache._detailProperties = null;
	    }

	    foreach (HEU_VolumeLayer srcLayer in _layers)
	    {
		HEU_VolumeLayer destLayer = destCache.GetLayer(srcLayer._layerName);
		if (destLayer != null)
		{
		    CopyLayer(srcLayer, destLayer);
		}
	    }
	}
コード例 #2
0
		public bool ApplyPreset(HEU_VolumeCachePreset volumeCachePreset)
		{
			UIExpanded = volumeCachePreset._uiExpanded;

			// Load the TerrainData if the path is given
			//Debug.Log("Get terraindata path: " + volumeCachePreset._terrainDataPath);
			if (!string.IsNullOrEmpty(volumeCachePreset._terrainDataPath))
			{
				_terrainData = HEU_AssetDatabase.LoadAssetAtPath(volumeCachePreset._terrainDataPath, typeof(TerrainData)) as TerrainData;
				//Debug.Log("Loaded terrain? " + (_terrainData != null ? "yes" : "no"));
			}

			foreach (HEU_VolumeLayerPreset layerPreset in volumeCachePreset._volumeLayersPresets)
			{
				HEU_VolumeLayer layer = GetLayer(layerPreset._layerName);
				if (layer == null)
				{
					Debug.LogWarningFormat("Volume layer with name {0} not found! Unable to set heightfield layer preset.", layerPreset._layerName);
					return false;
				}

				layer._strength = layerPreset._strength;
				layer._tile = layerPreset._tile;
				layer._uiExpanded = layerPreset._uiExpanded;
			}
			
			IsDirty = true;

			return true;
		}
コード例 #3
0
		public void ResetParameters()
		{
			HEU_VolumeLayer defaultLayer = new HEU_VolumeLayer();

			foreach (HEU_VolumeLayer layer in _layers)
			{
				CopyLayer(defaultLayer, layer);
			}
		}
コード例 #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
		public void ResetParameters()
		{
			_terrainData = null;
			_scatterTrees = null;

			HEU_VolumeLayer defaultLayer = new HEU_VolumeLayer();

			foreach (HEU_VolumeLayer layer in _layers)
			{
				CopyLayer(defaultLayer, layer);
			}
		}
コード例 #6
0
		public void CopyValuesTo(HEU_VolumeCache destCache)
		{
			destCache.UIExpanded = UIExpanded;

			foreach(HEU_VolumeLayer srcLayer in _layers)
			{
				HEU_VolumeLayer destLayer = destCache.GetLayer(srcLayer._layerName);
				if(destLayer != null)
				{
					CopyLayer(srcLayer, destLayer);
				}
			}
		}
コード例 #7
0
		public void CopyValuesTo(HEU_VolumeCache destCache)
		{
			destCache.UIExpanded = UIExpanded;

			destCache._terrainData = Object.Instantiate(_terrainData);

			foreach (HEU_VolumeLayer srcLayer in _layers)
			{
				HEU_VolumeLayer destLayer = destCache.GetLayer(srcLayer._layerName);
				if(destLayer != null)
				{
					CopyLayer(srcLayer, destLayer);
				}
			}
		}
コード例 #8
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();
				}
			}
		}
コード例 #9
0
		public void CopyValuesTo(HEU_VolumeCache destCache)
		{
			destCache.UIExpanded = UIExpanded;

			foreach(HEU_VolumeLayer srcLayer in _layers)
			{
				HEU_VolumeLayer destLayer = destCache.GetLayer(srcLayer._layerName);
				if(destLayer != null)
				{
					destLayer._strength = srcLayer._strength;
					destLayer._splatTexture = srcLayer._splatTexture;
					destLayer._normalTexture = srcLayer._normalTexture;
					destLayer._tileSize = srcLayer._tileSize;
					destLayer._tileOffset = srcLayer._tileOffset;
					destLayer._metallic = srcLayer._metallic;
					destLayer._smoothness = srcLayer._smoothness;
					destLayer._uiExpanded = srcLayer._uiExpanded;
				}
			}
		}
コード例 #10
0
		public static void CopyLayer(HEU_VolumeLayer srcLayer, HEU_VolumeLayer destLayer)
		{
			destLayer._strength = srcLayer._strength;

			destLayer._diffuseTexture = srcLayer._diffuseTexture;
			destLayer._maskTexture = srcLayer._maskTexture;

			destLayer._metallic = srcLayer._metallic;

			destLayer._normalTexture = srcLayer._normalTexture;
			destLayer._normalScale = srcLayer._normalScale;
			destLayer._smoothness = srcLayer._smoothness;
			destLayer._specularColor = srcLayer._specularColor;

			destLayer._tileSize = srcLayer._tileSize;
			destLayer._tileOffset = srcLayer._tileOffset;

			destLayer._uiExpanded = srcLayer._uiExpanded;
			destLayer._tile = srcLayer._tile;

			destLayer._overrides = srcLayer._overrides;
		}
コード例 #11
0
	public static void CopyLayer(HEU_VolumeLayer srcLayer, HEU_VolumeLayer destLayer)
	{
	    destLayer._strength = srcLayer._strength;
	    destLayer._uiExpanded = srcLayer._uiExpanded;
	    destLayer._tile = srcLayer._tile;

	    destLayer._xLength = srcLayer._xLength;
	    destLayer._yLength = srcLayer._yLength;

	    destLayer._layerType = srcLayer._layerType;

	    if (srcLayer._detailPrototype != null)
	    {
		if (destLayer._detailPrototype == null)
		{
		    destLayer._detailPrototype = new HEU_DetailPrototype();
		}
		CopyPrototype(srcLayer._detailPrototype, destLayer._detailPrototype);
	    }
	    else
	    {
		destLayer._detailPrototype = null;
	    }
	}
コード例 #12
0
		public static void CopyLayer(HEU_VolumeLayer srcLayer, HEU_VolumeLayer destLayer)
		{
			destLayer._strength = srcLayer._strength;
			destLayer._uiExpanded = srcLayer._uiExpanded;
			destLayer._tile = srcLayer._tile;
		}
コード例 #13
0
		public static bool IsLayerFieldOverriden(HEU_VolumeLayer layer, HEU_VolumeLayer.Overrides field)
		{
			return (layer._overrides & field) == field;
		}
コード例 #14
0
		public void GenerateTerrainWithAlphamaps(HEU_SessionBase session, HEU_HoudiniAsset houdiniAsset)
		{
			if(_layers == null || _layers.Count == 0)
			{
				Debug.LogError("Unable to generate terrain due to lack of heightfield layers!");
				return;
			}

			HEU_VolumeLayer baseLayer = _layers[0];

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

			TerrainData terrainData = null;
			Vector3 terrainOffsetPosition = Vector3.zero;

			// Generate the terrain and terrain data from the heightmap's height layer
			bResult = HEU_GeometryUtility.GenerateTerrainFromVolume(session, ref baseVolumeInfo, baseLayer._part.ParentGeoNode.GeoID,
				baseLayer._part.PartID, baseLayer._part.OutputGameObject, out terrainData, out terrainOffsetPosition);
			if (!bResult)
			{
				return;
			}

			baseLayer._part.SetTerrainData(terrainData);
			baseLayer._part.SetTerrainOffsetPosition(terrainOffsetPosition);

			int terrainSize = terrainData.heightmapResolution;

			// Now set the alphamaps (textures with masks) for the other layers

			// First, preprocess all layers to get heightfield arrays, converted to proper size
			// Then, merge into a float[x,y,map]
			List<float[]> heightFields = new List<float[]>();
			List<HEU_VolumeLayer> validLayers = new List<HEU_VolumeLayer>();

			int numLayers = _layers.Count;
			for(int i = 1; i < numLayers; ++i)
			{
				float[] hf = HEU_GeometryUtility.GetHeightfieldFromPart(session, _ownerNode.GeoID, _layers[i]._part.PartID, _layers[i]._part.PartName, terrainSize);
				if (hf != null && hf.Length > 0)
				{
					heightFields.Add(hf);
					validLayers.Add(_layers[i]);
				}
			}

			// Total maps is masks plus base height layer
			int numMaps = heightFields.Count + 1;

			// Assign floats to alpha map
			float[,,] alphamap = new float[terrainSize, terrainSize, numMaps];
			for (int y = 0; y < terrainSize; ++y)
			{
				for (int x = 0; x < terrainSize; ++x)
				{
					float f = 0f;
					for (int m = numMaps - 1; m > 0; --m)
					{
						float a = heightFields[m - 1][y + terrainSize * x];
						a = Mathf.Clamp01(a - f) * validLayers[m - 1]._strength;
						alphamap[x, y, m] = a;

						f += a;
					}

					// Base layer gets leftover value
					alphamap[x, y, 0] = Mathf.Clamp01(1.0f - f) * baseLayer._strength;
				}
			}

#if UNITY_2018_3_OR_NEWER

			// Create TerrainLayer for each heightfield layer
			// Note that at time of this implementation the new Unity terrain
			// is still in beta. Therefore, the following layer creation is subject
			// to change.

			TerrainLayer[] terrainLayers = new TerrainLayer[numMaps];
			for (int m = 0; m < numMaps; ++m)
			{
				terrainLayers[m] = new TerrainLayer();

				HEU_VolumeLayer layer = (m == 0) ? baseLayer : validLayers[m - 1];

				terrainLayers[m].diffuseTexture = layer._diffuseTexture;
				terrainLayers[m].diffuseRemapMin = Vector4.zero;
				terrainLayers[m].diffuseRemapMax = Vector4.one;

				terrainLayers[m].maskMapTexture = layer._maskTexture;
				terrainLayers[m].maskMapRemapMin = Vector4.zero;
				terrainLayers[m].maskMapRemapMax = Vector4.one;

				terrainLayers[m].metallic = layer._metallic;

				terrainLayers[m].normalMapTexture = layer._normalTexture;
				terrainLayers[m].normalScale = layer._normalScale;

				terrainLayers[m].smoothness = layer._smoothness;
				terrainLayers[m].specular = layer._specularColor;
				terrainLayers[m].tileOffset = layer._tileOffset;

				if (layer._tileSize.magnitude == 0f)
				{
					// Use texture size if tile size is 0
					layer._tileSize = new Vector2(layer._diffuseTexture.width, layer._diffuseTexture.height);
				}
				terrainLayers[m].tileSize = layer._tileSize;
			}
			terrainData.terrainLayers = terrainLayers;

#else
			// Need to create SplatPrototype for each layer in heightfield, representing the textures.
			SplatPrototype[] splatPrototypes = new SplatPrototype[numMaps];
			for (int m = 0; m < numMaps; ++m)
			{
				splatPrototypes[m] = new SplatPrototype();

				HEU_VolumeLayer layer = (m == 0) ? baseLayer : validLayers[m - 1];

				splatPrototypes[m].texture = layer._diffuseTexture;
				splatPrototypes[m].tileOffset = layer._tileOffset;
				if(layer._tileSize.magnitude == 0f)
				{
					// Use texture size if tile size is 0
					layer._tileSize = new Vector2(layer._diffuseTexture.width, layer._diffuseTexture.height);
				}
				splatPrototypes[m].tileSize = layer._tileSize;

				splatPrototypes[m].metallic = layer._metallic;
				splatPrototypes[m].smoothness = layer._smoothness;
				splatPrototypes[m].normalMap = layer._normalTexture;
			}
			terrainData.splatPrototypes = splatPrototypes;
#endif

			terrainData.SetAlphamaps(0, 0, alphamap);
		}
コード例 #15
0
		private void GetPartLayerAttributes(HEU_SessionBase session, HAPI_NodeId geoID, HAPI_NodeId partID, HEU_VolumeLayer layer)
		{
			// 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, geoID, partID, HEU_Defines.HAPI_HEIGHTFIELD_TILE_ATTR, ref tileAttrInfo, ref tileAttrData, session.GetAttributeIntData);
			if (tileAttrData != null && tileAttrData.Length > 0)
			{
				layer._tile = tileAttrData[0];
				//Debug.LogFormat("Tile: {0}", tileAttrData[0]);
			}
			else
			{
				layer._tile = 0;
			}


			string[] layerAttrNames =
			{
				HEU_Defines.DEFAULT_UNITY_HEIGHTFIELD_TEXTURE_DIFFUSE_ATTR,
				HEU_Defines.DEFAULT_UNITY_HEIGHTFIELD_TEXTURE_MASK_ATTR,
				HEU_Defines.DEFAULT_UNITY_HEIGHTFIELD_TEXTURE_NORMAL_ATTR,
				HEU_Defines.DEFAULT_UNITY_HEIGHTFIELD_NORMAL_SCALE_ATTR,
				HEU_Defines.DEFAULT_UNITY_HEIGHTFIELD_METALLIC_ATTR,
				HEU_Defines.DEFAULT_UNITY_HEIGHTFIELD_SMOOTHNESS_ATTR,
				HEU_Defines.DEFAULT_UNITY_HEIGHTFIELD_SPECULAR_ATTR,
				HEU_Defines.DEFAULT_UNITY_HEIGHTFIELD_TILE_OFFSET_ATTR,
				HEU_Defines.DEFAULT_UNITY_HEIGHTFIELD_TILE_SIZE_ATTR
			};

			// Check if any of the layer attribute names show up in the existing primitive attributes
			layer._hasLayerAttributes = false;
			HAPI_AttributeInfo attrInfo = new HAPI_AttributeInfo();
			bool bResult = false;
			foreach (string layerAttr in layerAttrNames)
			{
				bResult = session.GetAttributeInfo(geoID, partID, layerAttr, HAPI_AttributeOwner.HAPI_ATTROWNER_PRIM, ref attrInfo);
				if (bResult && attrInfo.exists)
				{
					layer._hasLayerAttributes = true;
					break;
				}
			}
		}
コード例 #16
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);
				}
			}
		}
コード例 #17
0
        public void ProcessVolumeParts(HEU_SessionBase session, List <HEU_PartData> volumeParts, bool bRebuild)
        {
            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;
                HAPI_AttributeInfo tileAttrInfo = new HAPI_AttributeInfo();
                int[] tileAttrData = new int[0];
                if (HEU_GeneralUtility.GetAttribute(session, GeoID, _parts[i].PartID, HEU_Defines.HAPI_HEIGHTFIELD_TILE_ATTR, ref tileAttrInfo, ref tileAttrData, session.GetAttributeIntData))
                {
                    if (tileAttrData != null && tileAttrData.Length > 0)
                    {
                        terrainTile = tileAttrData[0];
                    }
                }

                // 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].ObjectInstancesBeenGenerated = true;

                            // Now populate scatter trees based on attributes on this part
                            volumeCache.PopulateScatterTrees(session, GeoID, _parts[i].PartID, treeInstAttrInfo.count);
                        }
                    }
                }
            }

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

                cache.IsDirty = false;
            }
        }
コード例 #18
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);
		}
	    }
	}
コード例 #19
0
	public void PopulateDetailPrototype(HEU_SessionBase session, HAPI_NodeId geoID, HAPI_PartId partID,
		HEU_VolumeLayer layer)
	{
	    HEU_TerrainUtility.PopulateDetailPrototype(session, geoID, partID, ref layer._detailPrototype);
	}
コード例 #20
0
		private void GetPartLayerAttributes(HEU_SessionBase session, HAPI_NodeId geoID, HAPI_NodeId partID, HEU_VolumeLayer layer)
		{
			// 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, geoID, partID, HEU_Defines.HAPI_HEIGHTFIELD_TILE_ATTR, ref tileAttrInfo, ref tileAttrData, session.GetAttributeIntData);
			if (tileAttrData != null && tileAttrData.Length > 0)
			{
				layer._tile = tileAttrData[0];
				//Debug.LogFormat("Tile: {0}", tileAttrData[0]);
			}
			else
			{
				layer._tile = 0;
			}

			layer._hasLayerAttributes = HEU_TerrainUtility.VolumeLayerHasAttributes(session, geoID, partID);
		}
コード例 #21
0
		private void GetPartLayerAttributes(HEU_SessionBase session, HAPI_NodeId geoID, HAPI_NodeId partID, HEU_VolumeLayer layer)
		{
			// 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, geoID, partID, "tile", ref tileAttrInfo, ref tileAttrData, session.GetAttributeIntData);
			if (tileAttrData != null && tileAttrData.Length > 0)
			{
				layer._tile = tileAttrData[0];
				//Debug.LogFormat("Tile: {0}", tileAttrData[0]);
			}
			else
			{
				layer._tile = 0;
			}

			// Get the layer textures, and other layer values from attributes

			Texture2D defaultTexture = LoadDefaultSplatTexture();

			if (!IsLayerFieldOverriden(layer, HEU_VolumeLayer.Overrides.Diffuse) && (layer._diffuseTexture == null || layer._diffuseTexture == defaultTexture))
			{
				layer._diffuseTexture = LoadLayerTextureFromAttribute(session, geoID, partID, HEU_Defines.DEFAULT_UNITY_HEIGHTFIELD_TEXTURE_DIFFUSE_ATTR);

				if (layer._diffuseTexture == null)
				{
					layer._diffuseTexture = defaultTexture;
				}
			}

			if (!IsLayerFieldOverriden(layer, HEU_VolumeLayer.Overrides.Mask) && layer._maskTexture == null)
			{
				layer._maskTexture = LoadLayerTextureFromAttribute(session, geoID, partID, HEU_Defines.DEFAULT_UNITY_HEIGHTFIELD_TEXTURE_MASK_ATTR);
			}

			if (!IsLayerFieldOverriden(layer, HEU_VolumeLayer.Overrides.Normal) && layer._normalTexture == null)
			{
				layer._normalTexture = LoadLayerTextureFromAttribute(session, geoID, partID, HEU_Defines.DEFAULT_UNITY_HEIGHTFIELD_TEXTURE_NORMAL_ATTR);
			}

			if (!IsLayerFieldOverriden(layer, HEU_VolumeLayer.Overrides.NormalScale))
			{
				LoadLayerFloatFromAttribute(session, geoID, partID, HEU_Defines.DEFAULT_UNITY_HEIGHTFIELD_NORMAL_SCALE_ATTR, ref layer._normalScale);
			}

			if (!IsLayerFieldOverriden(layer, HEU_VolumeLayer.Overrides.Metallic))
			{
				LoadLayerFloatFromAttribute(session, geoID, partID, HEU_Defines.DEFAULT_UNITY_HEIGHTFIELD_METALLIC_ATTR, ref layer._metallic);
			}

			if (!IsLayerFieldOverriden(layer, HEU_VolumeLayer.Overrides.Smoothness))
			{
				LoadLayerFloatFromAttribute(session, geoID, partID, HEU_Defines.DEFAULT_UNITY_HEIGHTFIELD_SMOOTHNESS_ATTR, ref layer._smoothness);
			}

			if (!IsLayerFieldOverriden(layer, HEU_VolumeLayer.Overrides.Specular))
			{
				LoadLayerColorFromAttribute(session, geoID, partID, HEU_Defines.DEFAULT_UNITY_HEIGHTFIELD_SPECULAR_ATTR, ref layer._specularColor);
			}

			if (!IsLayerFieldOverriden(layer, HEU_VolumeLayer.Overrides.TileOffset))
			{
				LoadLayerVector2FromAttribute(session, geoID, partID, HEU_Defines.DEFAULT_UNITY_HEIGHTFIELD_TILE_OFFSET_ATTR, ref layer._tileOffset);
			}

			if (!IsLayerFieldOverriden(layer, HEU_VolumeLayer.Overrides.TileSize))
			{
				LoadLayerVector2FromAttribute(session, geoID, partID, HEU_Defines.DEFAULT_UNITY_HEIGHTFIELD_TILE_SIZE_ATTR, ref layer._tileSize);
			}
		}
コード例 #22
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 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 and store it in our cache. Note that this overwrites existing TerrainData in our cache
					// because the workflow is such that attributes will always override local setting.
					string bakedTerrainPath = houdiniAsset.GetValidAssetCacheFolderPath();
					bakedTerrainPath = HEU_Platform.BuildPath(bakedTerrainPath, 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);
					}
				}
			}

			// 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);
			if (!bResult || terrainData == null)
			{
				return;
			}

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

			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[]> heightFields = new List<float[]>();
			// Corresponding list of HF volume layers to process as splatmaps
			List<HEU_VolumeLayer> volumeLayersToProcess = new List<HEU_VolumeLayer>();

			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.
			for(int i = 1; i < numLayers; ++i)
			{
				float[] normalizedHF = HEU_TerrainUtility.GetNormalizedHeightmapFromPartWithMinMax(session, _ownerNode.GeoID, _layers[i]._part.PartID, 
					_layers[i]._xLength, _layers[i]._yLength, ref minHeight, ref maxHeight, ref heightRange);
				if (normalizedHF != null && normalizedHF.Length > 0)
				{
					heightFields.Add(normalizedHF);
					volumeLayersToProcess.Add(_layers[i]);
				}
			}

			int numVolumeLayers = volumeLayersToProcess.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 < numVolumeLayers; ++m)
			{
				bNewTerrainLayer = false;
				bSetTerrainLayerProperties = true;

				layer = volumeLayersToProcess[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 < numVolumeLayers; ++m)
			{
				bNewSplat = false;

				layer = volumeLayersToProcess[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 && volumeLayersToProcess.Count > 0)
			{
				// Convert the heightfields into alpha maps with layer strengths
				float[] strengths = new float[volumeLayersToProcess.Count];
				for (int m = 0; m < volumeLayersToProcess.Count; ++m)
				{
					strengths[m] = volumeLayersToProcess[m]._strength;
				}

				alphamap = HEU_TerrainUtility.AppendConvertedHeightFieldToAlphaMap(
					volumeLayersToProcess[0]._xLength, volumeLayersToProcess[0]._yLength, existingAlphaMaps,
					heightFields, 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 = volumeLayersToProcess[0]._xLength;
				terrainData.alphamapResolution = alphamapResolution;

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

			// Tree instances for scattering
			HEU_TerrainUtility.ApplyScatter(terrainData, _scatterTrees);

			// 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();
		}
コード例 #23
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);
				}
			}
		}