private float[] GetHeightfield(HEU_SessionBase session, HEU_VolumeData volumeData, int terrainSize)
		{
			int xLength = volumeData._volumeInfo.xLength;
			int yLength = volumeData._volumeInfo.yLength;

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

			float[] heightValues = new float[totalHeightValues];
			bool bResult = session.GetHeightFieldData(volumeData._partData.ParentGeoNode.GeoID, volumeData._partData.PartID, heightValues, 0, totalHeightValues);
			if (!bResult)
			{
				Debug.LogErrorFormat("Unable to get heightfield data from part {0}", volumeData._partData.PartName);
				return heightValues;
			}

			// Convert to terrain size
			if(xLength == terrainSize && yLength == terrainSize)
			{
				return heightValues;
			}
			else
			{
				int paddingWidth = terrainSize - xLength;
				int paddingLeft = Mathf.CeilToInt(paddingWidth * 0.5f);
				int paddingRight = terrainSize - paddingLeft;
				//Debug.LogFormat("Padding: Width={0}, Left={1}, Right={2}", paddingWidth, paddingLeft, paddingRight);

				int paddingHeight = terrainSize - yLength;
				int paddingTop = Mathf.CeilToInt(paddingHeight * 0.5f);
				int paddingBottom = terrainSize - 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[] resizedHeightValues = new float[terrainSize * terrainSize];
				for (int y = 0; y < terrainSize; ++y)
				{
					for (int x = 0; x < terrainSize; ++x)
					{
						if (y >= paddingTop && y < (paddingBottom) && x >= paddingLeft && x < (paddingRight))
						{
							int ay = x - paddingLeft;
							int ax = y - paddingTop;

							//float f = heightValues[ay + ax * xLength];

							// 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;
						}
					}
				}

				return resizedHeightValues;
			}
		}
Example #2
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;
		}