示例#1
0
		private void GenerateTerrain(List<HEU_LoadBufferVolume> terrainBuffers)
		{
			Transform parent = this.gameObject.transform;

			// Directory to store generated terrain files.
			string outputTerrainpath = GetOutputCacheDirectory();
			outputTerrainpath = HEU_Platform.BuildPath(outputTerrainpath, "Terrain");

			int numVolumes = terrainBuffers.Count;
			for(int t = 0; t < numVolumes; ++t)
			{
				if (terrainBuffers[t]._heightMap != null)
				{
					GameObject newGameObject = new GameObject("heightfield_" + terrainBuffers[t]._tileIndex);
					Transform newTransform = newGameObject.transform;
					newTransform.parent = parent;

					HEU_GeneratedOutput generatedOutput = new HEU_GeneratedOutput();
					generatedOutput._outputData._gameObject = newGameObject;

					Terrain terrain = HEU_GeneralUtility.GetOrCreateComponent<Terrain>(newGameObject);
					TerrainCollider collider = HEU_GeneralUtility.GetOrCreateComponent<TerrainCollider>(newGameObject);

					if (!string.IsNullOrEmpty(terrainBuffers[t]._terrainDataPath))
					{
						// Load the source TerrainData, then make a unique copy of it in the cache folder

						TerrainData sourceTerrainData = HEU_AssetDatabase.LoadAssetAtPath(terrainBuffers[t]._terrainDataPath, typeof(TerrainData)) as TerrainData;
						if (sourceTerrainData == null)
						{
							Debug.LogWarningFormat("TerrainData, set via attribute, not found at: {0}", terrainBuffers[t]._terrainDataPath);
						}

						terrain.terrainData = HEU_AssetDatabase.CopyUniqueAndLoadAssetAtAnyPath(sourceTerrainData, outputTerrainpath, typeof(TerrainData)) as TerrainData;
						if (terrain.terrainData != null)
						{
							// Store path so that it can be deleted on clean up
							AddGeneratedOutputFilePath(HEU_AssetDatabase.GetAssetPath(terrain.terrainData));
						}
					}

					if (terrain.terrainData == null)
					{
						terrain.terrainData = new TerrainData();
					}
					TerrainData terrainData = terrain.terrainData;
					collider.terrainData = terrainData;

					HEU_TerrainUtility.SetTerrainMaterial(terrain, terrainBuffers[t]._specifiedTerrainMaterialName);

#if UNITY_2018_3_OR_NEWER
					terrain.allowAutoConnect = true;
					// This has to be set after setting material
					terrain.drawInstanced = true;
#endif

					int heightMapSize = terrainBuffers[t]._heightMapWidth;

					terrainData.heightmapResolution = heightMapSize;
					if (terrainData.heightmapResolution != heightMapSize)
					{
						Debug.LogErrorFormat("Unsupported terrain size: {0}", heightMapSize);
						continue;
					}

					// The terrainData.baseMapResolution is not set here, but rather left to whatever default Unity uses
					// The terrainData.alphamapResolution is set later when setting the alphamaps.

					// 32 is the default for resolutionPerPatch
					const int detailResolution = 1024;
					const int resolutionPerPatch = 32;
					terrainData.SetDetailResolution(detailResolution, resolutionPerPatch);

					terrainData.SetHeights(0, 0, terrainBuffers[t]._heightMap);

					// Note that Unity uses a default height range of 600 when a flat terrain is created.
					// Without a non-zero value for the height range, user isn't able to draw heights.
					// Therefore, set 600 as the value if height range is currently 0 (due to flat heightfield).
					float heightRange = terrainBuffers[t]._heightRange;
					if (heightRange == 0)
					{
						heightRange = 600;
					}

					terrainData.size = new Vector3(terrainBuffers[t]._terrainSizeX, heightRange, terrainBuffers[t]._terrainSizeY);

					terrain.Flush();

					// Set position
					HAPI_Transform hapiTransformVolume = new HAPI_Transform(true);
					hapiTransformVolume.position[0] += terrainBuffers[t]._position[0];
					hapiTransformVolume.position[1] += terrainBuffers[t]._position[1];
					hapiTransformVolume.position[2] += terrainBuffers[t]._position[2];
					HEU_HAPIUtility.ApplyLocalTransfromFromHoudiniToUnity(ref hapiTransformVolume, newTransform);

					// Set layers
					Texture2D defaultTexture = HEU_VolumeCache.LoadDefaultSplatTexture();
					int numLayers = terrainBuffers[t]._splatLayers.Count;

#if UNITY_2018_3_OR_NEWER

					// Create TerrainLayer for each heightfield layer.
					// Note that height and mask layers are ignored (i.e. not created as TerrainLayers).
					// Since height layer is first, only process layers from 2nd index onwards.
					if (numLayers > 1)
					{
						// 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);

						for (int m = 1; m < numLayers; ++m)
						{
							TerrainLayer terrainlayer = null;

							int terrainLayerIndex = -1;

							bool bSetTerrainLayerProperties = true;

							HEU_LoadBufferVolumeLayer layer = terrainBuffers[t]._splatLayers[m];

							// Look up TerrainLayer file via attribute if user has set it
							if (!string.IsNullOrEmpty(layer._layerPath))
							{
								terrainlayer = HEU_AssetDatabase.LoadAssetAtPath(layer._layerPath, typeof(TerrainLayer)) as TerrainLayer;
								if (terrainlayer == null)
								{
									Debug.LogWarningFormat("TerrainLayer, set via attribute, not found at: {0}", layer._layerPath);
									continue;
								}
								else
								{
									// Always check if its part of existing list so as not to add it again
									terrainLayerIndex = HEU_TerrainUtility.GetTerrainLayerIndex(terrainlayer, existingTerrainLayers);
								}
							}

							if (terrainlayer == null)
							{
								terrainlayer = new TerrainLayer();
								terrainLayerIndex = finalTerrainLayers.Count;
								finalTerrainLayers.Add(terrainlayer);
							}
							else
							{
								// 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 (layer._hasLayerAttributes && terrainLayerIndex >= 0)
								{
									// Copy the TerrainLayer file
									TerrainLayer prevTerrainLayer = terrainlayer;
									terrainlayer = HEU_AssetDatabase.CopyAndLoadAssetAtAnyPath(terrainlayer, outputTerrainpath, typeof(TerrainLayer), true) as TerrainLayer;
									if (terrainlayer != null)
									{
										// Update the TerrainLayer reference in the list with this copy
										finalTerrainLayers[terrainLayerIndex] = terrainlayer;

										// Store path for clean up later
										AddGeneratedOutputFilePath(HEU_AssetDatabase.GetAssetPath(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.
									}
								}
							}

							if (bSetTerrainLayerProperties)
							{
								if (!string.IsNullOrEmpty(layer._diffuseTexturePath))
								{
									terrainlayer.diffuseTexture = HEU_MaterialFactory.LoadTexture(layer._diffuseTexturePath);
								}
								if (terrainlayer.diffuseTexture == null)
								{
									terrainlayer.diffuseTexture = defaultTexture;
								}

								terrainlayer.diffuseRemapMin = Vector4.zero;
								terrainlayer.diffuseRemapMax = Vector4.one;

								if (!string.IsNullOrEmpty(layer._maskTexturePath))
								{
									terrainlayer.maskMapTexture = HEU_MaterialFactory.LoadTexture(layer._maskTexturePath);
								}

								terrainlayer.maskMapRemapMin = Vector4.zero;
								terrainlayer.maskMapRemapMax = Vector4.one;

								terrainlayer.metallic = layer._metallic;

								if (!string.IsNullOrEmpty(layer._normalTexturePath))
								{
									terrainlayer.normalMapTexture = HEU_MaterialFactory.LoadTexture(layer._normalTexturePath);
								}

								terrainlayer.normalScale = layer._normalScale;

								terrainlayer.smoothness = layer._smoothness;
								terrainlayer.specular = layer._specularColor;
								terrainlayer.tileOffset = layer._tileOffset;

								if (layer._tileSize.magnitude == 0f && terrainlayer.diffuseTexture != null)
								{
									// Use texture size if tile size is 0
									layer._tileSize = new Vector2(terrainlayer.diffuseTexture.width, terrainlayer.diffuseTexture.height);
								}
								terrainlayer.tileSize = layer._tileSize;
							}
						}
						terrainData.terrainLayers = finalTerrainLayers.ToArray();
					}

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

						HEU_LoadBufferVolumeLayer layer = terrainBuffers[t]._splatLayers[m];

						Texture2D diffuseTexture = null;
						if (!string.IsNullOrEmpty(layer._diffuseTexturePath))
						{
							diffuseTexture = HEU_MaterialFactory.LoadTexture(layer._diffuseTexturePath);
						}
						if (diffuseTexture == null)
						{
							diffuseTexture = defaultTexture;
						}
						splatPrototypes[m].texture = diffuseTexture;

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

						splatPrototypes[m].metallic = layer._metallic;
						splatPrototypes[m].smoothness = layer._smoothness;

						if (!string.IsNullOrEmpty(layer._normalTexturePath))
						{
							splatPrototypes[m].normalMap = HEU_MaterialFactory.LoadTexture(layer._normalTexturePath);
						}
					}
					terrainData.splatPrototypes = splatPrototypes;
#endif

					// Set the splatmaps
					if (terrainBuffers[t]._splatMaps != null)
					{
						// Set the alphamap size before setting the alphamaps to get correct scaling
						// The alphamap size comes from the first alphamap layer
						int alphamapResolution = terrainBuffers[t]._heightMapWidth;
						if (numLayers > 1)
						{
							alphamapResolution = terrainBuffers[t]._splatLayers[1]._heightMapWidth;
						}
						terrainData.alphamapResolution = alphamapResolution;

						terrainData.SetAlphamaps(0, 0, terrainBuffers[t]._splatMaps);
					}

					// Set the tree scattering
					if (terrainBuffers[t]._scatterTrees != null)
					{
						HEU_TerrainUtility.ApplyScatterTrees(terrainData, terrainBuffers[t]._scatterTrees);
					}

					// Set the detail layers
					if (terrainBuffers[t]._detailPrototypes != null)
					{
						HEU_TerrainUtility.ApplyDetailLayers(terrain, terrainData, terrainBuffers[t]._detailProperties,
							terrainBuffers[t]._detailPrototypes, terrainBuffers[t]._detailMaps);
					}

					terrainBuffers[t]._generatedOutput = generatedOutput;
					_generatedOutputs.Add(generatedOutput);

					SetOutputVisiblity(terrainBuffers[t]);
				}
			}
		}
		public static bool GenerateTerrainFromVolume(HEU_SessionBase session, ref HAPI_VolumeInfo volumeInfo, HAPI_NodeId geoID, HAPI_PartId partID, 
			GameObject gameObject, out TerrainData terrainData, out Vector3 volumePositionOffset)
		{
			terrainData = null;
			volumePositionOffset = Vector3.zero;

			if (volumeInfo.zLength == 1 && volumeInfo.tupleSize == 1)
			{
				// Heightfields will be converted to terrain in Unity.
				// Unity requires terrainData.heightmapResolution to be square power of two plus 1 (eg. 513, 257, 129, 65).
				// Houdini gives volumeInfo.xLength and volumeInfo.yLength which are the number of height values per dimension.
				// Note that volumeInfo.xLength and volumeInfo.yLength is equal to Houdini heightfield size / grid spacing.
				// The heightfield grid spacing is given as volumeTransformMatrix.scale but divided by 2 (grid spacing / 2 = volumeTransformMatrix.scale).
				// It is recommended to use grid spacing of 2.

				// Use the volumeInfo.transform to get the actual heightfield position and size.
				Matrix4x4 volumeTransformMatrix = HEU_HAPIUtility.GetMatrixFromHAPITransform(ref volumeInfo.transform, false);
				Vector3 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;
				float terrainSizeX = Mathf.Round((volumeInfo.xLength - 1) * gridSpacingX);
				float terrainSizeY = Mathf.Round((volumeInfo.yLength - 1) * gridSpacingY);

				//Debug.LogFormat("GS = {0},{1},{2}. SX = {1}. SY = {2}", gridSpacingX, gridSpacingY, terrainSizeX, terrainSizeY);

				//Debug.LogFormat("HeightField Pos:{0}, Scale:{1}", position, scale.ToString("{0.00}"));
				//Debug.LogFormat("HeightField tileSize:{0}, xLength:{1}, yLength:{2}", volumeInfo.tileSize.ToString("{0.00}"), volumeInfo.xLength.ToString("{0.00}"), volumeInfo.yLength.ToString("{0.00}"));
				//Debug.LogFormat("HeightField Terrain Size x:{0}, y:{1}", terrainSizeX.ToString("{0.00}"), terrainSizeY.ToString("{0.00}"));
				//Debug.LogFormat("HeightField minX={0}, minY={1}, minZ={2}", volumeInfo.minX.ToString("{0.00}"), volumeInfo.minY.ToString("{0.00}"), volumeInfo.minZ.ToString("{0.00}"));

				const int UNITY_MINIMUM_HEIGHTMAP_RESOLUTION = 33;
				if (terrainSizeX < UNITY_MINIMUM_HEIGHTMAP_RESOLUTION || terrainSizeY < UNITY_MINIMUM_HEIGHTMAP_RESOLUTION)
				{
					Debug.LogWarningFormat("Unity Terrain has a minimum heightmap resolution of {0}. This HDA heightmap size is {1}x{2}."
						+ "\nPlease resize the terrain to a value higher than this.",
						UNITY_MINIMUM_HEIGHTMAP_RESOLUTION, terrainSizeX, terrainSizeY);
					return false;
				}

				Terrain terrain = HEU_GeneralUtility.GetOrCreateComponent<Terrain>(gameObject);
				TerrainCollider collider = HEU_GeneralUtility.GetOrCreateComponent<TerrainCollider>(gameObject);
				
				if (terrain.terrainData == null)
				{
					terrain.terrainData = new TerrainData();
				}

				terrainData = terrain.terrainData;
				collider.terrainData = terrainData;

				// Heightmap resolution must be square power-of-two plus 1. 
				// Unity will automatically resize terrainData.heightmapResolution so need to handle the changed size (if Unity changed it).
				int heightMapResolution = volumeInfo.xLength;
				terrainData.heightmapResolution = heightMapResolution;
				int terrainResizedDelta = terrainData.heightmapResolution - heightMapResolution;
				if (terrainResizedDelta < 0)
				{
					Debug.LogWarningFormat("Note that Unity automatically resized terrain resolution to {0} from {1}. Use terrain size of power of two plus 1, and grid spacing of 2.", heightMapResolution, terrainData.heightmapResolution);
					heightMapResolution = terrainData.heightmapResolution;
				}
				else if(terrainResizedDelta > 0)
				{
					Debug.LogErrorFormat("Unsupported terrain size. Use terrain size of power of two plus 1, and grid spacing of 2. (delta = {0})", terrainResizedDelta);
					return false;
				}

				// Get the height values from Houdini and find the min and max height range.
				float minHeight = 0;
				float maxHeight = 0;
				float[] heightValues = null;
				if (!GetHeightfieldValues(session, volumeInfo.xLength, volumeInfo.yLength, geoID, partID, ref heightValues, ref minHeight, ref maxHeight))
				{
					return false;
				}

				const int UNITY_MAX_HEIGHT_RANGE = 65536;
				float heightRange = (maxHeight - minHeight);
				if (Mathf.RoundToInt(heightRange) > UNITY_MAX_HEIGHT_RANGE)
				{
					Debug.LogWarningFormat("Unity Terrain has maximum height range of {0}. This HDA height range is {1}, so it will be maxed out at {0}.\nPlease resize to within valid range!",
						UNITY_MAX_HEIGHT_RANGE, Mathf.RoundToInt(heightRange));
					heightRange = UNITY_MAX_HEIGHT_RANGE;
				}

				int mapWidth = volumeInfo.xLength;
				int mapHeight = volumeInfo.yLength;

				int paddingWidth = heightMapResolution - mapWidth;
				int paddingLeft = Mathf.CeilToInt(paddingWidth * 0.5f);
				int paddingRight = heightMapResolution - paddingLeft;
				//Debug.LogFormat("Padding: Width={0}, Left={1}, Right={2}", paddingWidth, paddingLeft, paddingRight);

				int paddingHeight = heightMapResolution - mapHeight;
				int paddingTop = Mathf.CeilToInt(paddingHeight * 0.5f);
				int paddingBottom = heightMapResolution - paddingTop;
				//Debug.LogFormat("Padding: Height={0}, Top={1}, Bottom={2}", paddingHeight, paddingTop, paddingBottom);

				// Set height values at centre of the terrain, with padding on the sides if we resized
				float[,] unityHeights = new float[heightMapResolution, heightMapResolution];
				for (int y = 0; y < heightMapResolution; ++y)
				{
					for (int x = 0; x < heightMapResolution; ++x)
					{	
						if (y >= paddingTop && y < (paddingBottom) && x >= paddingLeft && x < (paddingRight))
						{
							int ay = x - paddingLeft;
							int ax = y - paddingTop;

							// Unity expects normalized height values
							float h = heightValues[ay + ax * mapWidth] - minHeight;
							float f = h / heightRange;

							// Flip for right-hand to left-handed coordinate system
							int ix = x;
							int iy = heightMapResolution - (y + 1);

							// Unity expects height array indexing to be [y, x].
							unityHeights[ix, iy] = f;
						}
					}
				}

				terrainData.baseMapResolution = heightMapResolution;
				terrainData.alphamapResolution = heightMapResolution;

				//int detailResolution = heightMapResolution;
				// 128 is the maximum for resolutionPerPatch
				const int resolutionPerPatch = 128;
				terrainData.SetDetailResolution(resolutionPerPatch, resolutionPerPatch);

				// Note SetHeights must be called before setting size in next line, as otherwise
				// the internal terrain size will not change after setting the size.
				terrainData.SetHeights(0, 0, unityHeights);
				
				terrainData.size = new Vector3(terrainSizeX, heightRange, terrainSizeY);

				terrain.Flush();

				// Unity Terrain has origin at bottom left, whereas Houdini uses centre of terrain. 

				// Use volume bounds to set position offset when using split tiles
				float xmin, xmax, zmin, zmax, ymin, ymax, xcenter, ycenter, zcenter;
				session.GetVolumeBounds(geoID, partID, out xmin, out ymin, out zmin, out xmax, out ymax, out zmax, out xcenter,
					out ycenter, out zcenter);
				//Debug.LogFormat("xmin: {0}, xmax: {1}, ymin: {2}, ymax: {3}, zmin: {4}, zmax: {5}, xc: {6}, yc: {7}, zc: {8}",
				//	xmin, xmax, ymin, ymax, zmin, zmax, xcenter, ycenter, zcenter);

				// Offset position is based on size of heightfield
				float offsetX = (float)heightMapResolution / (float)mapWidth;
				float offsetZ = (float)heightMapResolution / (float)mapHeight;
				//Debug.LogFormat("offsetX: {0}, offsetZ: {1}", offsetX, offsetZ);

				//Debug.LogFormat("position.x: {0}, position.z: {1}", position.x, position.z);

				//volumePositionOffset = new Vector3(-position.x * offsetX, minHeight + position.y, position.z * offsetZ);
				volumePositionOffset = new Vector3((terrainSizeX + xmin) * offsetX, minHeight + position.y, zmin * offsetZ);

				return true;
			}
			else
			{
				Debug.LogWarning("Non-heightfield volume type not supported!");
			}

			return false;
		}
		private void GenerateTerrain(List<HEU_LoadBufferVolume> terrainBuffers)
		{
			Transform parent = this.gameObject.transform;

			int numVolumes = terrainBuffers.Count;
			for(int t = 0; t < numVolumes; ++t)
			{
				if (terrainBuffers[t]._heightMap != null)
				{
					GameObject newGameObject = new GameObject("heightfield_" + terrainBuffers[t]._tileIndex);
					Transform newTransform = newGameObject.transform;
					newTransform.parent = parent;

					HEU_GeneratedOutput generatedOutput = new HEU_GeneratedOutput();
					generatedOutput._outputData._gameObject = newGameObject;

					Terrain terrain = HEU_GeneralUtility.GetOrCreateComponent<Terrain>(newGameObject);
					TerrainCollider collider = HEU_GeneralUtility.GetOrCreateComponent<TerrainCollider>(newGameObject);

					if (!string.IsNullOrEmpty(terrainBuffers[t]._terrainDataPath))
					{
						terrain.terrainData = HEU_AssetDatabase.LoadAssetAtPath(terrainBuffers[t]._terrainDataPath, typeof(TerrainData)) as TerrainData;
						if (terrain.terrainData == null)
						{
							Debug.LogWarningFormat("TerrainData, set via attribute, not found at: {0}", terrainBuffers[t]._terrainDataPath);
						}
					}

					if (terrain.terrainData == null)
					{
						terrain.terrainData = new TerrainData();
					}
					TerrainData terrainData = terrain.terrainData;
					collider.terrainData = terrainData;

					HEU_TerrainUtility.SetTerrainMaterial(terrain);

#if UNITY_2018_3_OR_NEWER
					terrain.allowAutoConnect = true;
					// This has to be set after setting material
					terrain.drawInstanced = true;
#endif

					int heightMapSize = terrainBuffers[t]._heightMapWidth;

					terrainData.heightmapResolution = heightMapSize;
					if (terrainData.heightmapResolution != heightMapSize)
					{
						Debug.LogErrorFormat("Unsupported terrain size: {0}", heightMapSize);
						continue;
					}

					// The terrainData.baseMapResolution is not set here, but rather left to whatever default Unity uses
					// The terrainData.alphamapResolution is set later when setting the alphamaps.

					// 32 is the default for resolutionPerPatch
					const int detailResolution = 1024;
					const int resolutionPerPatch = 32;
					terrainData.SetDetailResolution(detailResolution, resolutionPerPatch);

					terrainData.SetHeights(0, 0, terrainBuffers[t]._heightMap);

					// Note that Unity uses a default height range of 600 when a flat terrain is created.
					// Without a non-zero value for the height range, user isn't able to draw heights.
					// Therefore, set 600 as the value if height range is currently 0 (due to flat heightfield).
					float heightRange = terrainBuffers[t]._heightRange;
					if (heightRange == 0)
					{
						heightRange = 600;
					}

					terrainData.size = new Vector3(terrainBuffers[t]._terrainSizeX, heightRange, terrainBuffers[t]._terrainSizeY);

					terrain.Flush();

					// Set position
					HAPI_Transform hapiTransformVolume = new HAPI_Transform(true);
					hapiTransformVolume.position[0] += terrainBuffers[t]._position[0];
					hapiTransformVolume.position[1] += terrainBuffers[t]._position[1];
					hapiTransformVolume.position[2] += terrainBuffers[t]._position[2];
					HEU_HAPIUtility.ApplyLocalTransfromFromHoudiniToUnity(ref hapiTransformVolume, newTransform);

					// Set layers
					Texture2D defaultTexture = HEU_VolumeCache.LoadDefaultSplatTexture();
					int numLayers = terrainBuffers[t]._layers.Count;

#if UNITY_2018_3_OR_NEWER

					// Create TerrainLayer for each heightfield layer.
					// Note that height and mask layers are ignored (i.e. not created as TerrainLayers).
					// Since height layer is first, only process layers from 2nd index onwards.
					if (numLayers > 1)
					{
						TerrainLayer[] terrainLayers = new TerrainLayer[numLayers - 1];
						for (int m = 1; m < numLayers; ++m)
						{
							TerrainLayer terrainlayer = null;

							HEU_LoadBufferVolumeLayer layer = terrainBuffers[t]._layers[m];

							// Look up TerrainLayer file via attribute if user has set it
							if (!string.IsNullOrEmpty(layer._layerPath))
							{
								terrainlayer = HEU_AssetDatabase.LoadAssetAtPath(layer._layerPath, typeof(TerrainLayer)) as TerrainLayer;
								if (terrainlayer == null)
								{
									Debug.LogWarningFormat("TerrainLayer, set via attribute, not found at: {0}", layer._layerPath);
									continue;
								}
							}

							if (terrainlayer == null)
							{
								terrainlayer = new TerrainLayer();
							}

							if (!string.IsNullOrEmpty(layer._diffuseTexturePath))
							{
								terrainlayer.diffuseTexture = HEU_MaterialFactory.LoadTexture(layer._diffuseTexturePath);
							}
							if (terrainlayer.diffuseTexture == null)
							{
								terrainlayer.diffuseTexture = defaultTexture;
							}

							terrainlayer.diffuseRemapMin = Vector4.zero;
							terrainlayer.diffuseRemapMax = Vector4.one;

							if (!string.IsNullOrEmpty(layer._maskTexturePath))
							{
								terrainlayer.maskMapTexture = HEU_MaterialFactory.LoadTexture(layer._maskTexturePath);
							}

							terrainlayer.maskMapRemapMin = Vector4.zero;
							terrainlayer.maskMapRemapMax = Vector4.one;

							terrainlayer.metallic = layer._metallic;

							if (!string.IsNullOrEmpty(layer._normalTexturePath))
							{
								terrainlayer.normalMapTexture = HEU_MaterialFactory.LoadTexture(layer._normalTexturePath);
							}

							terrainlayer.normalScale = layer._normalScale;

							terrainlayer.smoothness = layer._smoothness;
							terrainlayer.specular = layer._specularColor;
							terrainlayer.tileOffset = layer._tileOffset;

							if (layer._tileSize.magnitude == 0f && terrainlayer.diffuseTexture != null)
							{
								// Use texture size if tile size is 0
								layer._tileSize = new Vector2(terrainlayer.diffuseTexture.width, terrainlayer.diffuseTexture.height);
							}
							terrainlayer.tileSize = layer._tileSize;

							// Note index is m - 1 due to skipping height layer
							terrainLayers[m - 1] = terrainlayer;
						}
						terrainData.terrainLayers = terrainLayers;
					}

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

						HEU_LoadBufferVolumeLayer layer = terrainBuffers[t]._layers[m];

						Texture2D diffuseTexture = null;
						if (!string.IsNullOrEmpty(layer._diffuseTexturePath))
						{
							diffuseTexture = HEU_MaterialFactory.LoadTexture(layer._diffuseTexturePath);
						}
						if (diffuseTexture == null)
						{
							diffuseTexture = defaultTexture;
						}
						splatPrototypes[m].texture = diffuseTexture;

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

						splatPrototypes[m].metallic = layer._metallic;
						splatPrototypes[m].smoothness = layer._smoothness;

						if (!string.IsNullOrEmpty(layer._normalTexturePath))
						{
							splatPrototypes[m].normalMap = HEU_MaterialFactory.LoadTexture(layer._normalTexturePath);
						}
					}
					terrainData.splatPrototypes = splatPrototypes;
#endif

					// Set the splatmaps
					if (terrainBuffers[t]._splatMaps != null)
					{
						// Set the alphamap size before setting the alphamaps to get correct scaling
						// The alphamap size comes from the first alphamap layer
						int alphamapResolution = terrainBuffers[t]._heightMapWidth;
						if (numLayers > 1)
						{
							alphamapResolution = terrainBuffers[t]._layers[1]._heightMapWidth;
						}
						terrainData.alphamapResolution = alphamapResolution;

						terrainData.SetAlphamaps(0, 0, terrainBuffers[t]._splatMaps);
					}

					// Set the tree scattering
					if (terrainBuffers[t]._scatterTrees != null)
					{
						HEU_TerrainUtility.ApplyScatter(terrainData, terrainBuffers[t]._scatterTrees);
					}

					terrainBuffers[t]._generatedOutput = generatedOutput;
					_generatedOutputs.Add(generatedOutput);

					SetOutputVisiblity(terrainBuffers[t]);
				}
			}
		}
		/// <summary>
		/// Creates terrain from given volumeInfo for the given gameObject.
		/// If gameObject has a valid Terrain component, then it is reused.
		/// Similarly, if the Terrain component has a valid TerrainData, or if the given terrainData is valid, then it is used.
		/// Otherwise a new TerrainData is created and set to the Terrain.
		/// Populates the volumePositionOffset with the heightfield offset position.
		/// Returns true if successfully created the terrain, otherwise false.
		/// </summary>
		/// <param name="session">Houdini Engine session to query heightfield data from</param>
		/// <param name="volumeInfo">Volume info pertaining to the heightfield to generate the Terrain from</param>
		/// <param name="geoID">The geometry ID</param>
		/// <param name="partID">The part ID (height layer)</param>
		/// <param name="gameObject">The target GameObject containing the Terrain component</param>
		/// <param name="terrainData">A valid TerrainData to use, or if empty, a new one is created and populated</param>
		/// <param name="volumePositionOffset">Heightfield offset</param>
		/// <returns>True if successfully popupated the terrain</returns>
		public static bool GenerateTerrainFromVolume(HEU_SessionBase session, ref HAPI_VolumeInfo volumeInfo, HAPI_NodeId geoID, HAPI_PartId partID,
			GameObject gameObject, ref TerrainData terrainData, out Vector3 volumePositionOffset, ref Terrain terrain)
		{
			volumePositionOffset = Vector3.zero;

			if (volumeInfo.zLength == 1 && volumeInfo.tupleSize == 1)
			{
				// Heightfields will be converted to terrain in Unity.
				// Unity requires terrainData.heightmapResolution to be square power of two plus 1 (eg. 513, 257, 129, 65).
				// Houdini gives volumeInfo.xLength and volumeInfo.yLength which are the number of height values per dimension.
				// Note that volumeInfo.xLength and volumeInfo.yLength is equal to Houdini heightfield size / grid spacing.
				// The heightfield grid spacing is given as volumeTransformMatrix.scale but divided by 2 (grid spacing / 2 = volumeTransformMatrix.scale).
				// It is recommended to use grid spacing of 2.

				// Use the volumeInfo.transform to get the actual heightfield position and size.
				Matrix4x4 volumeTransformMatrix = HEU_HAPIUtility.GetMatrixFromHAPITransform(ref volumeInfo.transform, false);
				Vector3 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;
				float terrainSizeX = Mathf.Round((volumeInfo.xLength - 1) * gridSpacingX);
				float terrainSizeY = Mathf.Round((volumeInfo.yLength - 1) * gridSpacingY);

				// Test size
				//float terrainSizeX = Mathf.Round((volumeInfo.xLength) * gridSpacingX);
				//float terrainSizeY = Mathf.Round((volumeInfo.yLength) * gridSpacingY);

				//Debug.LogFormat("GS = {0},{1},{2}. SX = {1}. SY = {2}", gridSpacingX, gridSpacingY, terrainSizeX, terrainSizeY);

				//Debug.LogFormat("HeightField Pos:{0}, Scale:{1}", position, scale.ToString("{0.00}"));
				//Debug.LogFormat("HeightField tileSize:{0}, xLength:{1}, yLength:{2}", volumeInfo.tileSize.ToString("{0.00}"), volumeInfo.xLength.ToString("{0.00}"), volumeInfo.yLength.ToString("{0.00}"));
				//Debug.LogFormat("HeightField Terrain Size x:{0}, y:{1}", terrainSizeX.ToString("{0.00}"), terrainSizeY.ToString("{0.00}"));
				//Debug.LogFormat("HeightField minX={0}, minY={1}, minZ={2}", volumeInfo.minX.ToString("{0.00}"), volumeInfo.minY.ToString("{0.00}"), volumeInfo.minZ.ToString("{0.00}"));

				const int UNITY_MINIMUM_HEIGHTMAP_RESOLUTION = 33;
				if (terrainSizeX < UNITY_MINIMUM_HEIGHTMAP_RESOLUTION || terrainSizeY < UNITY_MINIMUM_HEIGHTMAP_RESOLUTION)
				{
					Debug.LogWarningFormat("Unity Terrain has a minimum heightmap resolution of {0}. This HDA heightmap size is {1}x{2}."
						+ "\nPlease resize the terrain to a value higher than this.",
						UNITY_MINIMUM_HEIGHTMAP_RESOLUTION, terrainSizeX, terrainSizeY);
					return false;
				}

				bool bNewTerrain = false;
				bool bNewTerrainData = false;
				terrain = gameObject.GetComponent<Terrain>();
				if (terrain == null)
				{
					terrain = gameObject.AddComponent<Terrain>();
					bNewTerrain = true;
				}

#if !HEU_TERRAIN_COLLIDER_DISABLED
				TerrainCollider collider = HEU_GeneralUtility.GetOrCreateComponent<TerrainCollider>(gameObject);
#endif

				// Look up terrain material, if specified, on the height layer
				string specifiedTerrainMaterialName = HEU_GeneralUtility.GetMaterialAttributeValueFromPart(session,
					geoID, partID);

				// This ensures to reuse existing terraindata, and only creates new if none exist or none provided
				if (terrain.terrainData == null)
				{
					if (terrainData == null)
					{
						terrainData = new TerrainData();
						bNewTerrainData = true;
					}

					terrain.terrainData = terrainData;
					SetTerrainMaterial(terrain, specifiedTerrainMaterialName);
				}

				terrainData = terrain.terrainData;

#if !HEU_TERRAIN_COLLIDER_DISABLED
				collider.terrainData = terrainData;
#endif

				if (bNewTerrain)
				{
#if UNITY_2018_3_OR_NEWER
					terrain.allowAutoConnect = true;
					// This has to be set after setting material
					terrain.drawInstanced = true;
#endif
				}

				// Heightmap resolution must be square power-of-two plus 1. 
				// Unity will automatically resize terrainData.heightmapResolution so need to handle the changed size (if Unity changed it).
				int heightMapResolution = volumeInfo.xLength;
				terrainData.heightmapResolution = heightMapResolution;
				int terrainResizedDelta = terrainData.heightmapResolution - heightMapResolution;
				if (terrainResizedDelta < 0)
				{
					Debug.LogWarningFormat("Note that Unity automatically resized terrain resolution to {0} from {1}. Use terrain size of power of two plus 1, and grid spacing of 2.", heightMapResolution, terrainData.heightmapResolution);
					heightMapResolution = terrainData.heightmapResolution;
				}
				else if (terrainResizedDelta > 0)
				{
					Debug.LogErrorFormat("Unsupported terrain size. Use terrain size of power of two plus 1, and grid spacing of 2. Given size is {0} but Unity resized it to {1}.", heightMapResolution, terrainData.heightmapResolution);
					return false;
				}

				int mapWidth = volumeInfo.xLength;
				int mapHeight = volumeInfo.yLength;

				// Get the converted height values from Houdini and find the min and max height range.
				float minHeight = 0;
				float maxHeight = 0;
				float heightRange = 0;
				bool bUseHeightRangeOverride = true;

				float[] normalizedHeights = GetNormalizedHeightmapFromPartWithMinMax(session, geoID, partID, 
					volumeInfo.xLength, volumeInfo.yLength, ref minHeight, ref maxHeight, ref heightRange, 
					bUseHeightRangeOverride);
				float[,] unityHeights = ConvertHeightMapHoudiniToUnity(heightMapResolution, heightMapResolution, normalizedHeights);

				// The terrainData.baseMapResolution is not set here, but rather left to whatever default Unity uses
				// The terrainData.alphamapResolution is set later when setting the alphamaps.

				if (bNewTerrainData)
				{
					// 32 is the default for resolutionPerPatch
					const int detailResolution = 1024;
					const int resolutionPerPatch = 32;
					terrainData.SetDetailResolution(detailResolution, resolutionPerPatch);
				}

				// Note SetHeights must be called before setting size in next line, as otherwise
				// the internal terrain size will not change after setting the size.
				terrainData.SetHeights(0, 0, unityHeights);

				// Note that Unity uses a default height range of 600 when a flat terrain is created.
				// Without a non-zero value for the height range, user isn't able to draw heights.
				// Therefore, set 600 as the value if height range is currently 0 (due to flat heightfield).
				if (heightRange == 0)
				{
					heightRange = terrainData.size.y > 1 ? terrainData.size.y : 600;
				}

				terrainData.size = new Vector3(terrainSizeX, heightRange, terrainSizeY);

				terrain.Flush();

				// Unity Terrain has origin at bottom left, whereas Houdini uses centre of terrain. 

				// Use volume bounds to set position offset when using split tiles
				float xmin, xmax, zmin, zmax, ymin, ymax, xcenter, ycenter, zcenter;
				session.GetVolumeBounds(geoID, partID, out xmin, out ymin, out zmin, out xmax, out ymax, out zmax, out xcenter,
					out ycenter, out zcenter);
				//Debug.LogFormat("xmin: {0}, xmax: {1}, ymin: {2}, ymax: {3}, zmin: {4}, zmax: {5}, xc: {6}, yc: {7}, zc: {8}",
				//	xmin, xmax, ymin, ymax, zmin, zmax, xcenter, ycenter, zcenter);

				// Offset position is based on size of heightfield
				float offsetX = (float)heightMapResolution / (float)mapWidth;
				float offsetZ = (float)heightMapResolution / (float)mapHeight;
				//Debug.LogFormat("offsetX: {0}, offsetZ: {1}", offsetX, offsetZ);

				// Use y position from attribute if user has set it
				float ypos = position.y + minHeight;
				float userYPos;
				if (HEU_GeneralUtility.GetAttributeFloatSingle(session, geoID, partID,
					HEU_Defines.DEFAULT_UNITY_HEIGHTFIELD_YPOS, out userYPos))
				{
					ypos = userYPos;
				}

				// TODO: revisit how the position is calculated
				volumePositionOffset = new Vector3((terrainSizeX + xmin) * offsetX, ypos, zmin * offsetZ);

				// Test position
				//volumePositionOffset = new Vector3(xcenter + mapWidth, ycenter, zcenter - mapHeight);

				return true;
			}
			else
			{
				Debug.LogWarning("Non-heightfield volume type not supported!");
			}

			return false;
		}
示例#5
0
		public static bool GenerateTerrainFromVolume(HEU_SessionBase session, ref HAPI_VolumeInfo volumeInfo, HAPI_NodeId geoID, HAPI_PartId partID, 
			GameObject gameObject, out TerrainData terrainData, out Vector3 volumePositionOffset)
		{
			terrainData = null;
			volumePositionOffset = Vector3.zero;

			if (volumeInfo.zLength == 1 && volumeInfo.tupleSize == 1)
			{
				// Heightfields will be converted to terrain in Unity.
				// Unity requires terrainData.heightmapResolution to be square power of two plus 1 (eg. 513, 257, 129, 65).
				// Houdini gives volumeInfo.xLength and volumeInfo.yLength which are the number of height values per dimension.
				// Note that volumeInfo.xLength and volumeInfo.yLength is equal to Houdini heightfield size / grid spacing.
				// The heightfield grid spacing is given as volumeTransformMatrix.scale but divided by 2 (grid spacing / 2 = volumeTransformMatrix.scale).

				// Number of heightfield values
				int totalHeightValues = volumeInfo.xLength * volumeInfo.yLength;

				// Use the volumeInfo.transform to get the actual heightfield position and size.
				Matrix4x4 volumeTransformMatrix = HEU_HAPIUtility.GetMatrixFromHAPITransform(ref volumeInfo.transform, false);
				Vector3 position = HEU_HAPIUtility.GetPosition(ref volumeTransformMatrix);
				Vector3 scale = HEU_HAPIUtility.GetScale(ref volumeTransformMatrix);

				// The scaled size is the real world size of the terrain in both Houdini and Unity.
				// Unity terrain uses this to scale up the height values we set.
				int scaledSizeX = Mathf.RoundToInt(volumeInfo.xLength * scale.x) * 2;
				int scaledSizeZ = Mathf.RoundToInt(volumeInfo.yLength * scale.z) * 2;

				//Debug.LogFormat("HeightField Pos:{0}, Scale:{1}", position, scale);
				//Debug.LogFormat("HeightField tileSize:{0}, xLength:{1}, yLength:{2}", volumeInfo.tileSize, volumeInfo.xLength, volumeInfo.yLength);
				//Debug.LogFormat("HeightField Scaled Size x:{0}, y:{1}", scaledSizeX, scaledSizeZ);
				//Debug.LogFormat("HeightField minX={0}, minY={1}, minZ={2}", volumeInfo.minX, volumeInfo.minY, volumeInfo.minZ);

				const int UNITY_MINIMUM_HEIGHTMAP_RESOLUTION = 33;
				if (scaledSizeX < UNITY_MINIMUM_HEIGHTMAP_RESOLUTION || scaledSizeZ < UNITY_MINIMUM_HEIGHTMAP_RESOLUTION)
				{
					Debug.LogWarningFormat("Unity Terrain has a minimum heightmap resolution of {0}. This HDA heightmap size is {1}x{2}."
						+ "\nPlease resize the terrain to a value higher than this.",
						UNITY_MINIMUM_HEIGHTMAP_RESOLUTION, scaledSizeX, scaledSizeZ);
					return false;
				}

				int mapWidth = volumeInfo.xLength;
				int mapHeight = volumeInfo.yLength;

				// Unity terrain requires (though not strictly) a square size that is power of two plus one,
				// so we calculate that from the given volumeInfo.xLength and volumeInfo.yLength.
				int squareSizePlusOne = mapWidth >= mapHeight ? mapWidth : mapHeight;
				if (!Mathf.IsPowerOfTwo(squareSizePlusOne - 1))
				{
					squareSizePlusOne = Mathf.NextPowerOfTwo(squareSizePlusOne - 1) + 1;
				}
				//Debug.LogFormat("MAPHEIGHT: {0}, SQUARE: {1}", mapHeight, squareSize);

				// If our valid square size is greater than half than the scaled true size, then the mapping will need
				// extra padding since we need to go up to the next power of two plus one.
				if ((squareSizePlusOne * 2) > scaledSizeX || (squareSizePlusOne * 2) > scaledSizeZ)
				{
					Debug.LogWarningFormat("The specified heightfield size will require extra padding when converting heightfield to terrain due to Houdini to Unity size differences!"
						+ "\nRecommended to use heightfield size that is power of two plus two (eg. 514, 258, 130, 66).");

					scaledSizeX = Mathf.RoundToInt(squareSizePlusOne * scale.x) * 2;
					scaledSizeZ = Mathf.RoundToInt(squareSizePlusOne * scale.z) * 2;
				}

				// Get the height values from Houdini and find the min and max height range.
				float[] heightValues = new float[totalHeightValues];
				bool bResult = session.GetHeightFieldData(geoID, partID, heightValues, 0, totalHeightValues);
				if (!bResult)
				{
					return false;
				}

				float minHeight = heightValues[0];
				float maxHeight = minHeight;
				for (int i = 0; i < totalHeightValues; ++i)
				{
					float f = heightValues[i];
					if (f > maxHeight)
					{
						maxHeight = f;
					}
					else if (f < minHeight)
					{
						minHeight = f;
					}
				}

				const int UNITY_MAX_HEIGHT_RANGE = 65536;
				float heightRange = (maxHeight - minHeight);
				if (Mathf.RoundToInt(heightRange) > UNITY_MAX_HEIGHT_RANGE)
				{
					Debug.LogWarningFormat("Unity Terrain has maximum height range of {0}. This HDA height range is {1}, so it will be maxed out at {0}.\nPlease resize to within valid range!",
						UNITY_MAX_HEIGHT_RANGE, Mathf.RoundToInt(heightRange));
					heightRange = UNITY_MAX_HEIGHT_RANGE;
				}

				int paddingWidth = squareSizePlusOne - mapWidth;
				int paddingLeft = Mathf.CeilToInt(paddingWidth * 0.5f);
				int paddingRight = squareSizePlusOne - paddingLeft;
				//Debug.LogFormat("Padding: Width={0}, Left={1}, Right={2}", paddingWidth, paddingLeft, paddingRight);

				int paddingHeight = squareSizePlusOne - mapHeight;
				int paddingTop = Mathf.CeilToInt(paddingHeight * 0.5f);
				int paddingBottom = squareSizePlusOne - paddingTop;
				//Debug.LogFormat("Padding: Height={0}, Top={1}, Bottom={2}", paddingHeight, paddingTop, paddingBottom);

				// Set height values at centre of the terrain, with padding on the sides if we resized
				float[,] unityHeights = new float[squareSizePlusOne, squareSizePlusOne];
				for (int y = 0; y < squareSizePlusOne; ++y)
				{
					for (int x = 0; x < squareSizePlusOne; ++x)
					{
						if (y >= paddingTop && y < (paddingBottom) && x >= paddingLeft && x < (paddingRight))
						{
							int ay = x - paddingLeft;
							int ax = y - paddingTop;

							// Unity expects normalized height values
							float h = heightValues[ay + ax * mapWidth] - minHeight;
							float f = h / heightRange;

							// Flip for right-hand to left-handed coordinate system
							int ix = x;
							int iy = squareSizePlusOne - (y + 1);

							// Unity expects height array indexing to be [y, x].
							unityHeights[ix, iy] = f;
						}
					}
				}

				terrainData = new TerrainData();

				// Heightmap resolution must be square power-of-two plus 1
				terrainData.heightmapResolution = squareSizePlusOne;

				// TODO: Pull these from plugin settings perhaps?
				terrainData.baseMapResolution = 1024;
				terrainData.alphamapResolution = 1024;

				int detailResolution = squareSizePlusOne - 1;
				// 128 is the maximum for resolutionPerPatch
				const int resolutionPerPatch = 128;
				terrainData.SetDetailResolution(detailResolution, resolutionPerPatch);

				// Note SetHeights must be called before setting size in next line, as otherwise
				// the internal terrain size will not change after setting the size.
				terrainData.SetHeights(0, 0, unityHeights);

				terrainData.size = new Vector3(scaledSizeX, heightRange, scaledSizeZ);

				Terrain terrain = HEU_GeneralUtility.GetOrCreateComponent<Terrain>(gameObject);
				TerrainCollider collider = HEU_GeneralUtility.GetOrCreateComponent<TerrainCollider>(gameObject);
				terrain.terrainData = terrainData;
				collider.terrainData = terrainData;

				terrain.Flush();

				// Unity Terrain has origin at bottom left, whereas
				// Houdini uses centre of terrain. We'll need to move the terrain half way back in X and half way up in Z

				// TODO: revisit to properly get volume position offset when splitting tiles
				float xmin, xmax, zmin, zmax, ymin, ymax, xcenter, ycenter, zcenter;
				session.GetVolumeBounds(geoID, partID, out xmin, out ymin, out zmin, out xmax, out ymax, out zmax, out xcenter,
					out ycenter, out zcenter);
				//Debug.LogFormat("xmin: {0}, xmax: {1}, ymin: {2}, ymax: {3}, zmin: {4}, zmax: {5}, xc: {6}, yc: {7}, zc: {8}",
				//	xmin, xmax, ymin, ymax, zmin, zmax, xcenter, ycenter, zcenter);

				// Offset position is based on size of heightfield
				float offsetX = (float)squareSizePlusOne / (float)mapWidth;
				float offsetZ = (float)squareSizePlusOne / (float)mapHeight;

				volumePositionOffset = new Vector3(-position.x * offsetX, minHeight + position.y, position.z * offsetZ);

				return true;
			}
			else
			{
				Debug.LogWarning("Non-heightfield volume type not supported!");
			}

			return false;
		}
示例#6
0
		private void GenerateTerrain(List<HEU_LoadBufferVolume> terrainBuffers)
		{
			Transform parent = this.gameObject.transform;

			int numVolues = terrainBuffers.Count;
			for(int t = 0; t < numVolues; ++t)
			{
				if (terrainBuffers[t]._heightMap != null)
				{
					GameObject newGameObject = new GameObject("heightfield_" + terrainBuffers[t]._tileIndex);
					Transform newTransform = newGameObject.transform;
					newTransform.parent = parent;

					HEU_GeneratedOutput generatedOutput = new HEU_GeneratedOutput();
					generatedOutput._outputData._gameObject = newGameObject;

					Terrain terrain = HEU_GeneralUtility.GetOrCreateComponent<Terrain>(newGameObject);
					TerrainCollider collider = HEU_GeneralUtility.GetOrCreateComponent<TerrainCollider>(newGameObject);

					terrain.terrainData = new TerrainData();
					TerrainData terrainData = terrain.terrainData;
					collider.terrainData = terrainData;

					int heightMapSize = terrainBuffers[t]._heightMapSize;

					terrainData.heightmapResolution = heightMapSize;
					if (terrainData.heightmapResolution != heightMapSize)
					{
						Debug.LogErrorFormat("Unsupported terrain size: {0}", heightMapSize);
						continue;
					}

					terrainData.baseMapResolution = heightMapSize;
					terrainData.alphamapResolution = heightMapSize;

					const int resolutionPerPatch = 128;
					terrainData.SetDetailResolution(resolutionPerPatch, resolutionPerPatch);

					terrainData.SetHeights(0, 0, terrainBuffers[t]._heightMap);

					terrainData.size = new Vector3(terrainBuffers[t]._terrainSizeX, terrainBuffers[t]._heightRange, terrainBuffers[t]._terrainSizeY);

					terrain.Flush();

					// Set position
					HAPI_Transform hapiTransformVolume = new HAPI_Transform(true);
					hapiTransformVolume.position[0] += terrainBuffers[t]._position[0];
					hapiTransformVolume.position[1] += terrainBuffers[t]._position[1];
					hapiTransformVolume.position[2] += terrainBuffers[t]._position[2];
					HEU_HAPIUtility.ApplyLocalTransfromFromHoudiniToUnity(ref hapiTransformVolume, newTransform);

					// Set layers
					Texture2D defaultTexture = HEU_VolumeCache.LoadDefaultSplatTexture();
					int numLayers = terrainBuffers[t]._layers.Count;

#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[numLayers];
					for (int m = 0; m < numLayers; ++m)
					{
						terrainLayers[m] = new TerrainLayer();

						HEU_LoadBufferVolumeLayer layer = terrainBuffers[t]._layers[m];

						if (!string.IsNullOrEmpty(layer._diffuseTexturePath))
						{
							// Using Resources.Load is much faster than AssetDatabase.Load
							//terrainLayers[m].diffuseTexture = HEU_MaterialFactory.LoadTexture(layer._diffuseTexturePath);
							terrainLayers[m].diffuseTexture = Resources.Load<Texture2D>(layer._diffuseTexturePath);
						}
						if (terrainLayers[m].diffuseTexture == null)
						{
							terrainLayers[m].diffuseTexture = defaultTexture;
						}

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

						if (!string.IsNullOrEmpty(layer._maskTexturePath))
						{
							// Using Resources.Load is much faster than AssetDatabase.Load
							//terrainLayers[m].maskMapTexture = HEU_MaterialFactory.LoadTexture(layer._maskTexturePath);
							terrainLayers[m].maskMapTexture = Resources.Load<Texture2D>(layer._maskTexturePath);
						}

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

						terrainLayers[m].metallic = layer._metallic;

						if (!string.IsNullOrEmpty(layer._normalTexturePath))
						{
							terrainLayers[m].normalMapTexture = HEU_MaterialFactory.LoadTexture(layer._normalTexturePath);
						}

						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 && terrainLayers[m].diffuseTexture != null)
						{
							// Use texture size if tile size is 0
							layer._tileSize = new Vector2(terrainLayers[m].diffuseTexture.width, terrainLayers[m].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[numLayers];
					for (int m = 0; m < numLayers; ++m)
					{
						splatPrototypes[m] = new SplatPrototype();

						HEU_LoadBufferVolumeLayer layer = terrainBuffers[t]._layers[m];

						Texture2D diffuseTexture = null;
						if (!string.IsNullOrEmpty(layer._diffuseTexturePath))
						{
							diffuseTexture = HEU_MaterialFactory.LoadTexture(layer._diffuseTexturePath);
						}
						if (diffuseTexture == null)
						{
							diffuseTexture = defaultTexture;
						}
						splatPrototypes[m].texture = diffuseTexture;

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

						splatPrototypes[m].metallic = layer._metallic;
						splatPrototypes[m].smoothness = layer._smoothness;

						if (!string.IsNullOrEmpty(layer._normalTexturePath))
						{
							splatPrototypes[m].normalMap = HEU_MaterialFactory.LoadTexture(layer._normalTexturePath);
						}
					}
					terrainData.splatPrototypes = splatPrototypes;
#endif

					terrainData.SetAlphamaps(0, 0, terrainBuffers[t]._splatMaps);

					//string assetPath = HEU_AssetDatabase.CreateAssetCacheFolder("terrainData");
					//AssetDatabase.CreateAsset(terrainData, assetPath);
					//Debug.Log("Created asset data at " + assetPath);

					terrainBuffers[t]._generatedOutput = generatedOutput;
					_generatedOutputs.Add(generatedOutput);

					SetOutputVisiblity(terrainBuffers[t]);
				}
			}
		}
示例#7
0
		public static bool GenerateMeshUsingGeoCache(HEU_SessionBase session, HEU_HoudiniAsset asset, GameObject gameObject,
			HEU_GenerateGeoCache geoCache, bool bGenerateUVs, bool bGenerateTangents, bool bPartInstanced)
		{
#if HEU_PROFILER_ON
			float generateMeshTime = Time.realtimeSinceStartup;
#endif

			string collisionGroupName = HEU_PluginSettings.CollisionGroupName;
			string renderCollisionGroupName = HEU_PluginSettings.RenderedCollisionGroupName;

			// Stores submesh data based on material key (ie. a submesh for each unique material)

			// Unity requires that if using multiple materials in the same GameObject, then we
			// need to create corresponding number of submeshes as materials.
			// So we'll create a submesh for each material in use. 
			// Each submesh will have a list of vertices and their attributes which
			// we'll collect in a helper class (HEU_MeshData).
			// Once we collected all the submesh data, we create a CombineInstance for each
			// submesh, then combine it while perserving the submeshes.
			Dictionary<int, HEU_MeshData> subMeshesMap = new Dictionary<int, HEU_MeshData>();

			string defaultMaterialName = HEU_HoudiniAsset.GenerateDefaultMaterialName(geoCache.GeoID, geoCache.PartID);
			int defaultMaterialKey = HEU_MaterialFactory.MaterialNameToKey(defaultMaterialName);

			int singleFaceUnityMaterialKey = HEU_Defines.HEU_INVALID_MATERIAL;
			int singleFaceHoudiniMaterialKey = HEU_Defines.HEU_INVALID_MATERIAL;

			// Now go through each group data and acquire the vertex data.
			// We'll create the collider mesh rightaway and assign to the gameobject.
			int numCollisionMeshes = 0;
			foreach (KeyValuePair<string, int[]> groupSplitFacesPair in geoCache._groupSplitVertexIndices)
			{
				string groupName = groupSplitFacesPair.Key;
				int[] groupVertexList = groupSplitFacesPair.Value;

				bool bIsCollidable = groupName.Contains(collisionGroupName);
				bool bIsRenderCollidable = groupName.Contains(renderCollisionGroupName);
				if (bIsCollidable || bIsRenderCollidable)
				{
					if (numCollisionMeshes > 0)
					{
						Debug.LogWarningFormat("More than 1 collision mesh detected for part {0}.\nOnly a single collision mesh is supported per part.", geoCache._partName);
					}

					if (geoCache._partInfo.type == HAPI_PartType.HAPI_PARTTYPE_BOX)
					{
						// Box collider

						HAPI_BoxInfo boxInfo = new HAPI_BoxInfo();
						if (session.GetBoxInfo(geoCache.GeoID, geoCache.PartID, ref boxInfo))
						{
							BoxCollider boxCollider = HEU_GeneralUtility.GetOrCreateComponent<BoxCollider>(gameObject);

							boxCollider.center = new Vector3(-boxInfo.center[0], boxInfo.center[1], boxInfo.center[2]);
							boxCollider.size = new Vector3(boxInfo.size[0] * 2f, boxInfo.size[1] * 2f, boxInfo.size[2] * 2f);
							// TODO: Should we apply the box info rotation here to the box collider?
							//		 If so, it should be in its own gameobject?
						}
					}
					else if (geoCache._partInfo.type == HAPI_PartType.HAPI_PARTTYPE_SPHERE)
					{
						// Sphere collider

						HAPI_SphereInfo sphereInfo = new HAPI_SphereInfo();
						if (session.GetSphereInfo(geoCache.GeoID, geoCache.PartID, ref sphereInfo))
						{
							SphereCollider sphereCollider = HEU_GeneralUtility.GetOrCreateComponent<SphereCollider>(gameObject);

							sphereCollider.center = new Vector3(-sphereInfo.center[0], sphereInfo.center[1], sphereInfo.center[2]);
							sphereCollider.radius = sphereInfo.radius;
						}
					}
					else
					{
						// Mesh collider

						List<Vector3> collisionVertices = new List<Vector3>();
						for (int v = 0; v < groupVertexList.Length; ++v)
						{
							int index = groupVertexList[v];
							if (index >= 0 && index < geoCache._posAttr.Length)
							{
								collisionVertices.Add(new Vector3(-geoCache._posAttr[index * 3], geoCache._posAttr[index * 3 + 1], geoCache._posAttr[index * 3 + 2]));
							}
						}

						int[] collisionIndices = new int[collisionVertices.Count];
						for (int i = 0; i < collisionIndices.Length; ++i)
						{
							collisionIndices[i] = i;
						}

						Mesh collisionMesh = new Mesh();
#if UNITY_2017_3_OR_NEWER
						collisionMesh.indexFormat = UnityEngine.Rendering.IndexFormat.UInt32;
#endif
						collisionMesh.name = groupName;
						collisionMesh.vertices = collisionVertices.ToArray();
						collisionMesh.triangles = collisionIndices;
						collisionMesh.RecalculateBounds();

						MeshCollider meshCollider = HEU_GeneralUtility.GetOrCreateComponent<MeshCollider>(gameObject);
						meshCollider.sharedMesh = collisionMesh;
					}

					numCollisionMeshes++;
				}


				if (bIsCollidable && !bIsRenderCollidable)
				{
					continue;
				}

				// After this point, we'll be only processing renderable geometry

				// Transfer indices for each attribute from the single large list into group lists

				float[] groupColorAttr = new float[0];
				HEU_GenerateGeoCache.TransferRegularPointAttributesToVertices(groupVertexList, ref geoCache._colorAttrInfo, geoCache._colorAttr, ref groupColorAttr);

				float[] groupAlphaAttr = new float[0];
				HEU_GenerateGeoCache.TransferRegularPointAttributesToVertices(groupVertexList, ref geoCache._alphaAttrInfo, geoCache._alphaAttr, ref groupAlphaAttr);

				float[] groupNormalAttr = new float[0];
				HEU_GenerateGeoCache.TransferRegularPointAttributesToVertices(groupVertexList, ref geoCache._normalAttrInfo, geoCache._normalAttr, ref groupNormalAttr);

				float[] groupTangentsAttr = new float[0];
				HEU_GenerateGeoCache.TransferRegularPointAttributesToVertices(groupVertexList, ref geoCache._tangentAttrInfo, geoCache._tangentAttr, ref groupTangentsAttr);

				float[] groupUVAttr = new float[0];
				HEU_GenerateGeoCache.TransferRegularPointAttributesToVertices(groupVertexList, ref geoCache._uvAttrInfo, geoCache._uvAttr, ref groupUVAttr);

				float[] groupUV2Attr = new float[0];
				HEU_GenerateGeoCache.TransferRegularPointAttributesToVertices(groupVertexList, ref geoCache._uv2AttrInfo, geoCache._uv2Attr, ref groupUV2Attr);

				float[] groupUV3Attr = new float[0];
				HEU_GenerateGeoCache.TransferRegularPointAttributesToVertices(groupVertexList, ref geoCache._uv3AttrInfo, geoCache._uv3Attr, ref groupUV3Attr);

				// Unity mesh creation requires # of vertices must equal # of attributes (color, normal, uvs).
				// HAPI gives us point indices. Since our attributes are via vertex, we need to therefore
				// create new indices of vertices that correspond to our attributes.

				// To reindex, we go through each index, add each attribute corresponding to that index to respective lists.
				// Then we set the index of where we added those attributes as the new index.

				int numIndices = groupVertexList.Length;
				for (int vertexIndex = 0; vertexIndex < numIndices; vertexIndex += 3)
				{
					// groupVertexList contains -1 for unused indices, and > 0 for used
					if (groupVertexList[vertexIndex] == -1)
					{
						continue;
					}

					int faceIndex = vertexIndex / 3;
					int faceMaterialID = geoCache._houdiniMaterialIDs[faceIndex];

					// Get the submesh ID for this face. Depends on whether it is a Houdini or Unity material.
					// Using default material as failsafe
					int submeshID = HEU_Defines.HEU_INVALID_MATERIAL;

					if (geoCache._unityMaterialAttrInfo.exists)
					{
						// This face might have a Unity or Substance material attribute. 
						// Formulate the submesh ID by combining the material attributes.

						if (geoCache._singleFaceUnityMaterial)
						{
							if (singleFaceUnityMaterialKey == HEU_Defines.HEU_INVALID_MATERIAL && geoCache._unityMaterialInfos.Count > 0)
							{
								// Use first material
								var unityMaterialMapEnumerator = geoCache._unityMaterialInfos.GetEnumerator();
								if (unityMaterialMapEnumerator.MoveNext())
								{
									singleFaceUnityMaterialKey = unityMaterialMapEnumerator.Current.Key;
								}
							}
							submeshID = singleFaceUnityMaterialKey;
						}
						else
						{
							int attrIndex = faceIndex;
							if (geoCache._unityMaterialAttrInfo.owner == HAPI_AttributeOwner.HAPI_ATTROWNER_PRIM || geoCache._unityMaterialAttrInfo.owner == HAPI_AttributeOwner.HAPI_ATTROWNER_POINT)
							{
								if (geoCache._unityMaterialAttrInfo.owner == HAPI_AttributeOwner.HAPI_ATTROWNER_POINT)
								{
									attrIndex = groupVertexList[vertexIndex];
								}

								string unityMaterialName = "";
								string substanceName = "";
								int substanceIndex = -1;
								submeshID = HEU_GenerateGeoCache.GetMaterialKeyFromAttributeIndex(geoCache, attrIndex, out unityMaterialName, out substanceName, out substanceIndex);
							}
							else
							{
								// (geoCache._unityMaterialAttrInfo.owner == HAPI_AttributeOwner.HAPI_ATTROWNER_DETAIL) should have been handled as geoCache._singleFaceMaterial above

								Debug.LogErrorFormat("Unity material attribute not supported for attribute type {0}!", geoCache._unityMaterialAttrInfo.owner);
							}
						}
					}

					if (submeshID == HEU_Defines.HEU_INVALID_MATERIAL)
					{
						// Check if has Houdini material assignment

						if (geoCache._houdiniMaterialIDs.Length > 0)
						{
							if (geoCache._singleFaceHoudiniMaterial)
							{
								if (singleFaceHoudiniMaterialKey == HEU_Defines.HEU_INVALID_MATERIAL)
								{
									singleFaceHoudiniMaterialKey = geoCache._houdiniMaterialIDs[0];
								}
								submeshID = singleFaceHoudiniMaterialKey;
							}
							else if (faceMaterialID > 0)
							{
								submeshID = faceMaterialID;
							}
						}

						if (submeshID == HEU_Defines.HEU_INVALID_MATERIAL)
						{
							// Use default material
							submeshID = defaultMaterialKey;
						}
					}

					if (!subMeshesMap.ContainsKey(submeshID))
					{
						// New submesh
						subMeshesMap.Add(submeshID, new HEU_MeshData());
					}

					HEU_MeshData subMeshData = subMeshesMap[submeshID];

					for (int triIndex = 0; triIndex < 3; ++triIndex)
					{
						int vertexTriIndex = vertexIndex + triIndex;
						int positionIndex = groupVertexList[vertexTriIndex];

						// Position
						Vector3 position = new Vector3(-geoCache._posAttr[positionIndex * 3 + 0], geoCache._posAttr[positionIndex * 3 + 1], geoCache._posAttr[positionIndex * 3 + 2]);
						subMeshData._vertices.Add(position);

						// Color
						if (geoCache._colorAttrInfo.exists)
						{
							Color tempColor = new Color();
							tempColor.r = Mathf.Clamp01(groupColorAttr[vertexTriIndex * geoCache._colorAttrInfo.tupleSize + 0]);
							tempColor.g = Mathf.Clamp01(groupColorAttr[vertexTriIndex * geoCache._colorAttrInfo.tupleSize + 1]);
							tempColor.b = Mathf.Clamp01(groupColorAttr[vertexTriIndex * geoCache._colorAttrInfo.tupleSize + 2]);

							if (geoCache._alphaAttrInfo.exists)
							{
								tempColor.a = Mathf.Clamp01(groupAlphaAttr[vertexTriIndex]);
							}
							else if (geoCache._colorAttrInfo.tupleSize == 4)
							{
								tempColor.a = Mathf.Clamp01(groupColorAttr[vertexTriIndex * geoCache._colorAttrInfo.tupleSize + 3]);
							}
							else
							{
								tempColor.a = 1f;
							}
							subMeshData._colors.Add(tempColor);
						}
						else
						{
							subMeshData._colors.Add(Color.white);
						}

						// Normal
						if (vertexTriIndex < groupNormalAttr.Length)
						{
							// Flip the x
							Vector3 normal = new Vector3(-groupNormalAttr[vertexTriIndex * 3 + 0], groupNormalAttr[vertexTriIndex * 3 + 1], groupNormalAttr[vertexTriIndex * 3 + 2]);
							subMeshData._normals.Add(normal);
						}
						else
						{
							// We'll be calculating normals later
							subMeshData._normals.Add(Vector3.zero);
						}

						// UV1
						if (vertexTriIndex < groupUVAttr.Length)
						{
							Vector2 uv = new Vector2(groupUVAttr[vertexTriIndex * 2 + 0], groupUVAttr[vertexTriIndex * 2 + 1]);
							subMeshData._UVs.Add(uv);
						}

						// UV2
						if (vertexTriIndex < groupUV2Attr.Length)
						{
							Vector2 uv = new Vector2(groupUV2Attr[vertexTriIndex * 2 + 0], groupUV2Attr[vertexTriIndex * 2 + 1]);
							subMeshData._UV2s.Add(uv);
						}

						// UV3
						if (vertexTriIndex < groupUV3Attr.Length)
						{
							Vector2 uv = new Vector2(groupUV3Attr[vertexTriIndex * 2 + 0], groupUV3Attr[vertexTriIndex * 2 + 1]);
							subMeshData._UV3s.Add(uv);
						}

						// Tangents
						if (bGenerateTangents && vertexTriIndex < groupTangentsAttr.Length)
						{
							Vector4 tangent = Vector4.zero;
							if (geoCache._tangentAttrInfo.tupleSize == 4)
							{
								tangent = new Vector4(-groupTangentsAttr[vertexTriIndex * 4 + 0], groupTangentsAttr[vertexTriIndex * 4 + 1], groupTangentsAttr[vertexTriIndex * 4 + 2], groupTangentsAttr[vertexTriIndex * 4 + 3]);
							}
							else if (geoCache._tangentAttrInfo.tupleSize == 3)
							{
								tangent = new Vector4(-groupTangentsAttr[vertexTriIndex * 3 + 0], groupTangentsAttr[vertexTriIndex * 3 + 1], groupTangentsAttr[vertexTriIndex * 3 + 2], 1);
							}

							subMeshData._tangents.Add(tangent);
						}

						subMeshData._indices.Add(subMeshData._vertices.Count - 1);
						//Debug.LogFormat("Submesh index mat {0} count {1}", faceMaterialID, subMeshData._indices.Count);
					}

					if (!geoCache._normalAttrInfo.exists)
					{
						// To generate normals after all the submeshes have been defined, we
						// calculate and store each triangle normal, along with the list
						// of connected vertices for each vertex

						int triIndex = subMeshData._indices.Count - 3;
						int i1 = subMeshData._indices[triIndex + 0];
						int i2 = subMeshData._indices[triIndex + 1];
						int i3 = subMeshData._indices[triIndex + 2];

						// Triangle normal
						Vector3 p1 = subMeshData._vertices[i2] - subMeshData._vertices[i1];
						Vector3 p2 = subMeshData._vertices[i3] - subMeshData._vertices[i1];
						Vector3 normal = Vector3.Cross(p1, p2).normalized;
						subMeshData._triangleNormals.Add(normal);
						int normalIndex = subMeshData._triangleNormals.Count - 1;

						// Connected vertices
						geoCache._sharedNormalIndices[groupVertexList[vertexIndex + 0]].Add(new VertexEntry(submeshID, i1, normalIndex));
						geoCache._sharedNormalIndices[groupVertexList[vertexIndex + 1]].Add(new VertexEntry(submeshID, i2, normalIndex));
						geoCache._sharedNormalIndices[groupVertexList[vertexIndex + 2]].Add(new VertexEntry(submeshID, i3, normalIndex));
					}
				}
			}

			int numSubmeshes = subMeshesMap.Keys.Count;

			bool bGenerated = false;
			if (numSubmeshes > 0)
			{
				if (!geoCache._normalAttrInfo.exists)
				{
					// Normal calculation
					// Go throuch each vertex for the entire geometry and calculate the normal vector based on connected
					// vertices. This includes vertex connections between submeshes so we should get smooth transitions across submeshes.

					int numSharedNormals = geoCache._sharedNormalIndices.Length;
					for (int a = 0; a < numSharedNormals; ++a)
					{
						for (int b = 0; b < geoCache._sharedNormalIndices[a].Count; ++b)
						{
							Vector3 sumNormal = new Vector3();
							VertexEntry leftEntry = geoCache._sharedNormalIndices[a][b];
							HEU_MeshData leftSubMesh = subMeshesMap[leftEntry._meshKey];

							List<VertexEntry> rightList = geoCache._sharedNormalIndices[a];
							for (int c = 0; c < rightList.Count; ++c)
							{
								VertexEntry rightEntry = rightList[c];
								HEU_MeshData rightSubMesh = subMeshesMap[rightEntry._meshKey];

								if (leftEntry._vertexIndex == rightEntry._vertexIndex)
								{
									sumNormal += rightSubMesh._triangleNormals[rightEntry._normalIndex];
								}
								else
								{
									float dot = Vector3.Dot(leftSubMesh._triangleNormals[leftEntry._normalIndex],
										rightSubMesh._triangleNormals[rightEntry._normalIndex]);
									if (dot >= geoCache._cosineThreshold)
									{
										sumNormal += rightSubMesh._triangleNormals[rightEntry._normalIndex];
									}
								}
							}

							leftSubMesh._normals[leftEntry._vertexIndex] = sumNormal.normalized;
						}
					}
				}


				// Go through each valid submesh data and upload into a CombineInstance for combining.
				// Each CombineInstance represents a submesh in the final mesh.
				// And each submesh in that final mesh corresponds to a material.

				// Filter out only the submeshes with valid geometry
				List<Material> validMaterials = new List<Material>();
				List<int> validSubmeshes = new List<int>();

				Dictionary<int, HEU_MaterialData> assetMaterialMap = asset.GetMaterialDataMap();

				foreach (KeyValuePair<int, HEU_MeshData> meshPair in subMeshesMap)
				{
					HEU_MeshData meshData = meshPair.Value;
					if (meshData._indices.Count > 0)
					{
						int materialKey = meshPair.Key;

						// Find the material or create it
						HEU_MaterialData materialData = null;

						HEU_UnityMaterialInfo unityMaterialInfo = null;
						if (geoCache._unityMaterialInfos.TryGetValue(materialKey, out unityMaterialInfo))
						{
							if (!assetMaterialMap.TryGetValue(materialKey, out materialData))
							{
								// Create the material
								materialData = asset.CreateUnitySubstanceMaterialData(materialKey, unityMaterialInfo._unityMaterialPath, unityMaterialInfo._substancePath, unityMaterialInfo._substanceIndex);
								assetMaterialMap.Add(materialData._materialKey, materialData);
							}
						}
						else if (!assetMaterialMap.TryGetValue(materialKey, out materialData))
						{
							if (materialKey == defaultMaterialKey)
							{
								materialData = asset.GetOrCreateDefaultMaterialInCache(session, geoCache.GeoID, geoCache.PartID, false);
							}
							else
							{
								materialData = asset.CreateHoudiniMaterialData(session, materialKey, geoCache.GeoID, geoCache.PartID);
							}
						}

						if (materialData != null)
						{
							validSubmeshes.Add(meshPair.Key);
							validMaterials.Add(materialData._material);

							if (materialData != null && bPartInstanced)
							{
								// Handle GPU instancing on material for instanced meshes

								if (materialData._materialSource != HEU_MaterialData.Source.UNITY && materialData._materialSource != HEU_MaterialData.Source.SUBSTANCE)
								{
									// Always enable GPU instancing for material generated from Houdini
									HEU_MaterialFactory.EnableGPUInstancing(materialData._material);
								}
							}
						}
					}
				}

				int validNumSubmeshes = validSubmeshes.Count;
				CombineInstance[] meshCombiner = new CombineInstance[validNumSubmeshes];
				for (int submeshIndex = 0; submeshIndex < validNumSubmeshes; ++submeshIndex)
				{
					HEU_MeshData submesh = subMeshesMap[validSubmeshes[submeshIndex]];

					CombineInstance combine = new CombineInstance();
					combine.mesh = new Mesh();
#if UNITY_2017_3_OR_NEWER
					combine.mesh.indexFormat = UnityEngine.Rendering.IndexFormat.UInt32;
#endif

					combine.mesh.SetVertices(submesh._vertices);

					combine.mesh.SetIndices(submesh._indices.ToArray(), MeshTopology.Triangles, 0);

					if (submesh._colors.Count > 0)
					{
						combine.mesh.SetColors(submesh._colors);
					}

					if (submesh._normals.Count > 0)
					{
						combine.mesh.SetNormals(submesh._normals);
					}

					if (submesh._tangents.Count > 0)
					{
						combine.mesh.SetTangents(submesh._tangents);
					}

					if (bGenerateUVs)
					{
						// TODO: revisit to test this out
						Vector2[] generatedUVs = HEU_GeometryUtility.GeneratePerTriangle(combine.mesh);
						if (generatedUVs != null)
						{
							combine.mesh.uv = generatedUVs;
						}
					}
					else if (submesh._UVs.Count > 0)
					{
						combine.mesh.SetUVs(0, submesh._UVs);
					}

					if (submesh._UV2s.Count > 0)
					{
						combine.mesh.SetUVs(1, submesh._UV2s);
					}

					if (submesh._UV3s.Count > 0)
					{
						combine.mesh.SetUVs(2, submesh._UV3s);
					}

					combine.transform = Matrix4x4.identity;
					combine.mesh.RecalculateBounds();

					//Debug.LogFormat("Number of submeshes {0}", combine.mesh.subMeshCount);

					meshCombiner[submeshIndex] = combine;
				}

				// Geometry data
				MeshFilter meshFilter = HEU_GeneralUtility.GetOrCreateComponent<MeshFilter>(gameObject);
				meshFilter.sharedMesh = new Mesh();
#if UNITY_2017_3_OR_NEWER
				meshFilter.sharedMesh.indexFormat = UnityEngine.Rendering.IndexFormat.UInt32;
#endif
				meshFilter.sharedMesh.name = geoCache._partName + "_mesh";
				meshFilter.sharedMesh.CombineMeshes(meshCombiner, false, false);

				meshFilter.sharedMesh.RecalculateBounds();

				if (!geoCache._tangentAttrInfo.exists && bGenerateTangents)
				{
					HEU_GeometryUtility.CalculateMeshTangents(meshFilter.sharedMesh);
				}

				meshFilter.sharedMesh.UploadMeshData(true);

				// Render data
				MeshRenderer meshRenderer = HEU_GeneralUtility.GetOrCreateComponent<MeshRenderer>(gameObject);
				meshRenderer.sharedMaterials = validMaterials.ToArray();

				bGenerated = true;
			}

#if HEU_PROFILER_ON
			Debug.LogFormat("GENERATE MESH TIME:: {0}", (Time.realtimeSinceStartup - generateMeshTime));
#endif

			return bGenerated;
		}