public static void ExecuteTool(int toolSlot) { if (_currentSelectedShelf < 0 && _currentSelectedShelf >= _shelves.Count) { Debug.LogWarning("Invalid shelf selected. Unable to apply tool."); return; } if (toolSlot < 0 || toolSlot >= _shelves[_currentSelectedShelf]._tools.Count) { Debug.LogWarning("Invalid tool selected. Unable to apply tool."); return; } HEU_ShelfToolData toolData = _shelves[_currentSelectedShelf]._tools[toolSlot]; GameObject[] selectedObjects = HEU_EditorUtility.GetSelectedObjects(); string assetPath = toolData._assetPath; if (toolData._toolType == HEU_ShelfToolData.ToolType.GENERATOR) { Matrix4x4 targetMatrix = HEU_EditorUtility.GetSelectedObjectsMeanTransform(); Vector3 position = HEU_HAPIUtility.GetPosition(ref targetMatrix); Quaternion rotation = HEU_HAPIUtility.GetQuaternion(ref targetMatrix); Vector3 scale = HEU_HAPIUtility.GetScale(ref targetMatrix); scale = Vector3.one; ExecuteToolGenerator(toolData._name, assetPath, position, rotation, scale); } else if (selectedObjects.Length == 0) { ExecuteToolNoInput(toolData._name, assetPath); } else if (toolData._toolType == HEU_ShelfToolData.ToolType.OPERATOR_SINGLE) { ExecuteToolOperatorSingle(toolData._name, assetPath, selectedObjects); } else if (toolData._toolType == HEU_ShelfToolData.ToolType.OPERATOR_MULTI) { ExecuteToolOperatorMultiple(toolData._name, assetPath, selectedObjects); } else if (toolData._toolType == HEU_ShelfToolData.ToolType.BATCH) { ExecuteToolBatch(toolData._name, assetPath, selectedObjects); } }
public static void ExecuteTool(int toolSlot) { if(toolSlot < 0 || toolSlot >= _tools.Count) { Debug.LogWarning("Invalid tool selection!"); return; } HEU_ShelfToolData toolData = _tools[toolSlot]; if(toolData.toolType == HEU_ShelfToolData.ToolType.GENARATOR) { Matrix4x4 targetMatrix = HEU_EditorUtility.GetSelectedObjectsMeanTransform(); Vector3 position = HEU_HAPIUtility.GetPosition(ref targetMatrix); Quaternion rotation = HEU_HAPIUtility.GetQuaternion(ref targetMatrix); Vector3 scale = HEU_HAPIUtility.GetScale(ref targetMatrix); scale = Vector3.one; ExecuteToolGenerator(toolData.name, toolData.assetPath, position, rotation, scale); } else if(toolData.toolType == HEU_ShelfToolData.ToolType.OPERATOR_SINGLE) { GameObject[] selectedObjects = HEU_EditorUtility.GetSelectedObjects(); ExecuteToolOperatorSingle(toolData.name, toolData.assetPath, selectedObjects); } else if (toolData.toolType == HEU_ShelfToolData.ToolType.OPERATOR_MULTI) { GameObject[] selectedObjects = HEU_EditorUtility.GetSelectedObjects(); ExecuteToolOperatorMultiple(toolData.name, toolData.assetPath, selectedObjects); } else if (toolData.toolType == HEU_ShelfToolData.ToolType.BATCH) { GameObject[] selectedObjects = HEU_EditorUtility.GetSelectedObjects(); ExecuteToolBatch(toolData.name, toolData.assetPath, selectedObjects); } }
public static Matrix4x4 GetSelectedObjectsMeanTransform() { Matrix4x4 meanTransformMatrix = Matrix4x4.identity; #if UNITY_EDITOR Transform[] selectedTransforms = Selection.GetTransforms(SelectionMode.Unfiltered); int numTransforms = selectedTransforms.Length; if (numTransforms > 0) { meanTransformMatrix = selectedTransforms[0].localToWorldMatrix; for (int i = 1; i < numTransforms; ++i) { meanTransformMatrix *= selectedTransforms[i].localToWorldMatrix; } Vector3 position = HEU_HAPIUtility.GetPosition(ref meanTransformMatrix); position /= (float)numTransforms; HEU_HAPIUtility.SetMatrixPosition(ref meanTransformMatrix, ref position); } #endif return(meanTransformMatrix); }
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; }
public bool GenerateTerrainBuffers(HEU_SessionBase session, HAPI_NodeId nodeID, List<HAPI_PartInfo> volumeParts, List<HAPI_PartInfo> scatterInstancerParts, out List<HEU_LoadBufferVolume> volumeBuffers) { volumeBuffers = null; if (volumeParts.Count == 0) { return true; } volumeBuffers = new List<HEU_LoadBufferVolume>(); int numParts = volumeParts.Count; for (int i = 0; i < numParts; ++i) { HAPI_VolumeInfo volumeInfo = new HAPI_VolumeInfo(); bool bResult = session.GetVolumeInfo(nodeID, volumeParts[i].id, ref volumeInfo); if (!bResult || volumeInfo.tupleSize != 1 || volumeInfo.zLength != 1 || volumeInfo.storage != HAPI_StorageType.HAPI_STORAGETYPE_FLOAT) { SetLog(HEU_LoadData.LoadStatus.ERROR, "This heightfield is not supported. Please check documentation."); return false; } if (volumeInfo.xLength != volumeInfo.yLength) { SetLog(HEU_LoadData.LoadStatus.ERROR, "Non-square sized terrain not supported."); return false; } string volumeName = HEU_SessionManager.GetString(volumeInfo.nameSH, session); bool bHeightPart = volumeName.Equals(HEU_Defines.HAPI_HEIGHTFIELD_LAYERNAME_HEIGHT); bool bMaskPart = volumeName.Equals(HEU_Defines.HAPI_HEIGHTFIELD_LAYERNAME_MASK); //Debug.LogFormat("Part name: {0}, GeoName: {1}, Volume Name: {2}, Display: {3}", part.PartName, geoNode.GeoName, volumeName, geoNode.Displayable); // Ignoring mask layer because it is Houdini-specific (same behaviour as regular HDA terrain generation) if (bMaskPart) { continue; } HEU_LoadBufferVolumeLayer layer = new HEU_LoadBufferVolumeLayer(); layer._layerName = volumeName; layer._partID = volumeParts[i].id; layer._heightMapWidth = volumeInfo.xLength; layer._heightMapHeight = volumeInfo.yLength; Matrix4x4 volumeTransformMatrix = HEU_HAPIUtility.GetMatrixFromHAPITransform(ref volumeInfo.transform, false); layer._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; layer._terrainSizeX = Mathf.Round((volumeInfo.xLength - 1) * gridSpacingX); layer._terrainSizeY = Mathf.Round((volumeInfo.yLength - 1) * gridSpacingY); // Get volume bounds for calculating position offset session.GetVolumeBounds(nodeID, volumeParts[i].id, out layer._minBounds.x, out layer._minBounds.y, out layer._minBounds.z, out layer._maxBounds.x, out layer._maxBounds.y, out layer._maxBounds.z, out layer._center.x, out layer._center.y, out layer._center.z); // Look up TerrainLayer file via attribute if user has set it layer._layerPath = HEU_GeneralUtility.GetAttributeStringValueSingle(session, nodeID, volumeParts[i].id, HEU_Defines.DEFAULT_UNITY_HEIGHTFIELD_TERRAINLAYER_FILE_ATTR, HAPI_AttributeOwner.HAPI_ATTROWNER_PRIM); LoadStringFromAttribute(session, nodeID, volumeParts[i].id, HEU_Defines.DEFAULT_UNITY_HEIGHTFIELD_TEXTURE_DIFFUSE_ATTR, ref layer._diffuseTexturePath); LoadStringFromAttribute(session, nodeID, volumeParts[i].id, HEU_Defines.DEFAULT_UNITY_HEIGHTFIELD_TEXTURE_MASK_ATTR, ref layer._maskTexturePath); LoadStringFromAttribute(session, nodeID, volumeParts[i].id, HEU_Defines.DEFAULT_UNITY_HEIGHTFIELD_TEXTURE_NORMAL_ATTR, ref layer._normalTexturePath); LoadFloatFromAttribute(session, nodeID, volumeParts[i].id, HEU_Defines.DEFAULT_UNITY_HEIGHTFIELD_NORMAL_SCALE_ATTR, ref layer._normalScale); LoadFloatFromAttribute(session, nodeID, volumeParts[i].id, HEU_Defines.DEFAULT_UNITY_HEIGHTFIELD_METALLIC_ATTR, ref layer._metallic); LoadFloatFromAttribute(session, nodeID, volumeParts[i].id, HEU_Defines.DEFAULT_UNITY_HEIGHTFIELD_SMOOTHNESS_ATTR, ref layer._smoothness); LoadLayerColorFromAttribute(session, nodeID, volumeParts[i].id, HEU_Defines.DEFAULT_UNITY_HEIGHTFIELD_SPECULAR_ATTR, ref layer._specularColor); LoadLayerVector2FromAttribute(session, nodeID, volumeParts[i].id, HEU_Defines.DEFAULT_UNITY_HEIGHTFIELD_TILE_OFFSET_ATTR, ref layer._tileOffset); LoadLayerVector2FromAttribute(session, nodeID, volumeParts[i].id, HEU_Defines.DEFAULT_UNITY_HEIGHTFIELD_TILE_SIZE_ATTR, ref layer._tileSize); // Get the height values from Houdini along with the min and max height range. layer._normalizedHeights = HEU_TerrainUtility.GetNormalizedHeightmapFromPartWithMinMax(_session, nodeID, volumeParts[i].id, volumeInfo.xLength, volumeInfo.yLength, ref layer._minHeight, ref layer._maxHeight, ref layer._heightRange); // Get the tile index, if it exists, for this part HAPI_AttributeInfo tileAttrInfo = new HAPI_AttributeInfo(); int[] tileAttrData = new int[0]; HEU_GeneralUtility.GetAttribute(session, nodeID, volumeParts[i].id, HEU_Defines.HAPI_HEIGHTFIELD_TILE_ATTR, ref tileAttrInfo, ref tileAttrData, session.GetAttributeIntData); int tileIndex = 0; if (tileAttrInfo.exists && tileAttrData.Length == 1) { tileIndex = tileAttrData[0]; } // Add layer based on tile index if (tileIndex >= 0) { HEU_LoadBufferVolume volumeBuffer = null; for(int j = 0; j < volumeBuffers.Count; ++j) { if (volumeBuffers[j]._tileIndex == tileIndex) { volumeBuffer = volumeBuffers[j]; break; } } if (volumeBuffer == null) { volumeBuffer = new HEU_LoadBufferVolume(); volumeBuffer.InitializeBuffer(volumeParts[i].id, volumeName, false, false); volumeBuffer._tileIndex = tileIndex; volumeBuffers.Add(volumeBuffer); } if (bHeightPart) { // Height layer always first layer volumeBuffer._layers.Insert(0, layer); volumeBuffer._heightMapWidth = layer._heightMapWidth; volumeBuffer._heightMapHeight = layer._heightMapHeight; volumeBuffer._terrainSizeX = layer._terrainSizeX; volumeBuffer._terrainSizeY = layer._terrainSizeY; volumeBuffer._heightRange = (layer._maxHeight - layer._minHeight); // Look up TerrainData file path via attribute if user has set it volumeBuffer._terrainDataPath = HEU_GeneralUtility.GetAttributeStringValueSingle(session, nodeID, volumeBuffer._id, HEU_Defines.DEFAULT_UNITY_HEIGHTFIELD_TERRAINDATA_FILE_ATTR, HAPI_AttributeOwner.HAPI_ATTROWNER_PRIM); // Load the TreePrototype buffers List<HEU_TreePrototypeInfo> treePrototypeInfos = HEU_TerrainUtility.GetTreePrototypeInfosFromPart(session, nodeID, volumeBuffer._id); if (treePrototypeInfos != null) { if (volumeBuffer._scatterTrees == null) { volumeBuffer._scatterTrees = new HEU_VolumeScatterTrees(); } volumeBuffer._scatterTrees._treePrototypInfos = treePrototypeInfos; } } else { volumeBuffer._layers.Add(layer); } } Sleep(); } // Each volume buffer is a self contained terrain tile foreach(HEU_LoadBufferVolume volumeBuffer in volumeBuffers) { List<HEU_LoadBufferVolumeLayer> layers = volumeBuffer._layers; //Debug.LogFormat("Heightfield: tile={0}, layers={1}", tile._tileIndex, layers.Count); int heightMapWidth = volumeBuffer._heightMapWidth; int heightMapHeight = volumeBuffer._heightMapHeight; int numLayers = layers.Count; if (numLayers > 0) { // Convert heightmap values from Houdini to Unity volumeBuffer._heightMap = HEU_TerrainUtility.ConvertHeightMapHoudiniToUnity(heightMapWidth, heightMapHeight, layers[0]._normalizedHeights); Sleep(); // Convert splatmap values from Houdini to Unity. // Start at 2nd index since height is strictly for height values (not splatmap). List<float[]> heightFields = new List<float[]>(); for(int m = 1; m < numLayers; ++m) { heightFields.Add(layers[m]._normalizedHeights); } // The number of maps are the number of splatmaps (ie. non height/mask layers) int numMaps = heightFields.Count; if (numMaps > 0) { // Using the first splatmap size for all splatmaps volumeBuffer._splatMaps = HEU_TerrainUtility.ConvertHeightFieldToAlphaMap(layers[1]._heightMapWidth, layers[1]._heightMapHeight, heightFields); } else { volumeBuffer._splatMaps = null; } volumeBuffer._position = new Vector3((volumeBuffer._terrainSizeX + volumeBuffer._layers[0]._minBounds.x), volumeBuffer._layers[0]._minHeight + volumeBuffer._layers[0]._position.y, volumeBuffer._layers[0]._minBounds.z); } } // Process the scatter instancer parts to get the scatter data for (int i = 0; i < scatterInstancerParts.Count; ++i) { // Find the terrain tile (use primitive attr). Assume 0 tile if not set (i.e. not split into tiles) int terrainTile = 0; HAPI_AttributeInfo tileAttrInfo = new HAPI_AttributeInfo(); int[] tileAttrData = new int[0]; if (HEU_GeneralUtility.GetAttribute(session, nodeID, scatterInstancerParts[i].id, HEU_Defines.HAPI_HEIGHTFIELD_TILE_ATTR, ref tileAttrInfo, ref tileAttrData, session.GetAttributeIntData)) { if (tileAttrData != null && tileAttrData.Length > 0) { terrainTile = tileAttrData[0]; } } // Find the volume layer associated with this part using the terrain tile index HEU_LoadBufferVolume volumeBuffer = GetLoadBufferVolumeFromTileIndex(terrainTile, volumeBuffers); if (volumeBuffer == null) { continue; } HEU_TerrainUtility.PopulateScatterInfo(session, nodeID, scatterInstancerParts[i].id, scatterInstancerParts[i].pointCount, ref volumeBuffer._scatterTrees); } return true; }
/// <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; }
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; }
public bool GenerateTerrainBuffers(HEU_SessionBase session, HAPI_NodeId nodeID, List<HAPI_PartInfo> volumeParts, out List<HEU_LoadBufferVolume> volumeBuffers) { volumeBuffers = null; if (volumeParts.Count == 0) { return true; } volumeBuffers = new List<HEU_LoadBufferVolume>(); int numParts = volumeParts.Count; for (int i = 0; i < numParts; ++i) { HAPI_VolumeInfo volumeInfo = new HAPI_VolumeInfo(); bool bResult = session.GetVolumeInfo(nodeID, volumeParts[i].id, ref volumeInfo); if (!bResult || volumeInfo.tupleSize != 1 || volumeInfo.zLength != 1 || volumeInfo.storage != HAPI_StorageType.HAPI_STORAGETYPE_FLOAT) { SetLog(HEU_LoadData.LoadStatus.ERROR, "This heightfield is not supported. Please check documentation."); return false; } if (volumeInfo.xLength != volumeInfo.yLength) { SetLog(HEU_LoadData.LoadStatus.ERROR, "Non-square sized terrain not supported."); return false; } string volumeName = HEU_SessionManager.GetString(volumeInfo.nameSH, session); bool bHeightPart = volumeName.Equals("height"); //Debug.LogFormat("Part name: {0}, GeoName: {1}, Volume Name: {2}, Display: {3}", part.PartName, geoNode.GeoName, volumeName, geoNode.Displayable); HEU_LoadBufferVolumeLayer layer = new HEU_LoadBufferVolumeLayer(); layer._layerName = volumeName; layer._partID = volumeParts[i].id; layer._heightMapSize = volumeInfo.xLength; Matrix4x4 volumeTransformMatrix = HEU_HAPIUtility.GetMatrixFromHAPITransform(ref volumeInfo.transform, false); layer._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; layer._terrainSizeX = Mathf.Round((volumeInfo.xLength - 1) * gridSpacingX); layer._terrainSizeY = Mathf.Round((volumeInfo.yLength - 1) * gridSpacingY); // Get volume bounds for calculating position offset session.GetVolumeBounds(nodeID, volumeParts[i].id, out layer._minBounds.x, out layer._minBounds.y, out layer._minBounds.z, out layer._maxBounds.x, out layer._maxBounds.y, out layer._maxBounds.z, out layer._center.x, out layer._center.y, out layer._center.z); LoadStringFromAttribute(session, nodeID, volumeParts[i].id, HEU_Defines.DEFAULT_UNITY_HEIGHTFIELD_TEXTURE_DIFFUSE_ATTR, ref layer._diffuseTexturePath); LoadStringFromAttribute(session, nodeID, volumeParts[i].id, HEU_Defines.DEFAULT_UNITY_HEIGHTFIELD_TEXTURE_MASK_ATTR, ref layer._maskTexturePath); LoadStringFromAttribute(session, nodeID, volumeParts[i].id, HEU_Defines.DEFAULT_UNITY_HEIGHTFIELD_TEXTURE_NORMAL_ATTR, ref layer._normalTexturePath); LoadFloatFromAttribute(session, nodeID, volumeParts[i].id, HEU_Defines.DEFAULT_UNITY_HEIGHTFIELD_NORMAL_SCALE_ATTR, ref layer._normalScale); LoadFloatFromAttribute(session, nodeID, volumeParts[i].id, HEU_Defines.DEFAULT_UNITY_HEIGHTFIELD_METALLIC_ATTR, ref layer._metallic); LoadFloatFromAttribute(session, nodeID, volumeParts[i].id, HEU_Defines.DEFAULT_UNITY_HEIGHTFIELD_SMOOTHNESS_ATTR, ref layer._smoothness); LoadLayerColorFromAttribute(session, nodeID, volumeParts[i].id, HEU_Defines.DEFAULT_UNITY_HEIGHTFIELD_SPECULAR_ATTR, ref layer._specularColor); LoadLayerVector2FromAttribute(session, nodeID, volumeParts[i].id, HEU_Defines.DEFAULT_UNITY_HEIGHTFIELD_TILE_OFFSET_ATTR, ref layer._tileOffset); LoadLayerVector2FromAttribute(session, nodeID, volumeParts[i].id, HEU_Defines.DEFAULT_UNITY_HEIGHTFIELD_TILE_SIZE_ATTR, ref layer._tileSize); // Get the height values from Houdini and find the min and max height range. if (!HEU_GeometryUtility.GetHeightfieldValues(session, volumeInfo.xLength, volumeInfo.yLength, nodeID, volumeParts[i].id, ref layer._rawHeights, ref layer._minHeight, ref layer._maxHeight)) { return false; } // TODO: Tried to replace above with this, but it flattens the heights //layer._rawHeights = HEU_GeometryUtility.GetHeightfieldFromPart(_session, nodeID, volumeParts[i].id, "part", volumeInfo.xLength); // Get the tile index, if it exists, for this part HAPI_AttributeInfo tileAttrInfo = new HAPI_AttributeInfo(); int[] tileAttrData = new int[0]; HEU_GeneralUtility.GetAttribute(session, nodeID, volumeParts[i].id, "tile", ref tileAttrInfo, ref tileAttrData, session.GetAttributeIntData); int tileIndex = 0; if (tileAttrInfo.exists && tileAttrData.Length == 1) { tileIndex = tileAttrData[0]; } // Add layer based on tile index if (tileIndex >= 0) { HEU_LoadBufferVolume volumeBuffer = null; for(int j = 0; j < volumeBuffers.Count; ++j) { if (volumeBuffers[j]._tileIndex == tileIndex) { volumeBuffer = volumeBuffers[j]; break; } } if (volumeBuffer == null) { volumeBuffer = new HEU_LoadBufferVolume(); volumeBuffer.InitializeBuffer(volumeParts[i].id, volumeName, false, false); volumeBuffer._tileIndex = tileIndex; volumeBuffers.Add(volumeBuffer); } if (bHeightPart) { // Height layer always first layer volumeBuffer._layers.Insert(0, layer); volumeBuffer._heightMapSize = layer._heightMapSize; volumeBuffer._terrainSizeX = layer._terrainSizeX; volumeBuffer._terrainSizeY = layer._terrainSizeY; volumeBuffer._heightRange = (layer._maxHeight - layer._minHeight); } else { volumeBuffer._layers.Add(layer); } } Sleep(); } // Each volume buffer is a self contained terrain tile foreach(HEU_LoadBufferVolume volumeBuffer in volumeBuffers) { List<HEU_LoadBufferVolumeLayer> layers = volumeBuffer._layers; //Debug.LogFormat("Heightfield: tile={0}, layers={1}", tile._tileIndex, layers.Count); int heightMapSize = volumeBuffer._heightMapSize; int numLayers = layers.Count; if (numLayers > 0) { // Convert heightmap values from Houdini to Unity volumeBuffer._heightMap = HEU_GeometryUtility.ConvertHeightMapHoudiniToUnity(heightMapSize, layers[0]._rawHeights, layers[0]._minHeight, layers[0]._maxHeight); Sleep(); // Convert splatmap values from Houdini to Unity. List<float[]> heightFields = new List<float[]>(); for(int m = 1; m < numLayers; ++m) { heightFields.Add(layers[m]._rawHeights); } volumeBuffer._splatMaps = HEU_GeometryUtility.ConvertHeightSplatMapHoudiniToUnity(heightMapSize, heightFields); volumeBuffer._position = new Vector3((volumeBuffer._terrainSizeX + volumeBuffer._layers[0]._minBounds.x), volumeBuffer._layers[0]._minHeight + volumeBuffer._layers[0]._position.y, volumeBuffer._layers[0]._minBounds.z); } } return true; }