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