/// <summary> /// Create the main heightfield network for input. /// </summary> /// <param name="session"></param> /// <param name="idt"></param> /// <returns>True if successfully created the network</returns> public bool CreateHeightFieldInputNode(HEU_SessionBase session, HEU_InputDataTerrain idt) { idt._heightfieldNodeID = HEU_Defines.HEU_INVALID_NODE_ID; idt._heightNodeID = HEU_Defines.HEU_INVALID_NODE_ID; idt._maskNodeID = HEU_Defines.HEU_INVALID_NODE_ID; idt._mergeNodeID = HEU_Defines.HEU_INVALID_NODE_ID; // Create the HeightField node network bool bResult = session.CreateHeightfieldInputNode(idt._parentNodeID, idt._heightFieldName, idt._numPointsX, idt._numPointsY, idt._voxelSize, out idt._heightfieldNodeID, out idt._heightNodeID, out idt._maskNodeID, out idt._mergeNodeID); if (!bResult || idt._heightfieldNodeID == HEU_Defines.HEU_INVALID_NODE_ID || idt._heightNodeID == HEU_Defines.HEU_INVALID_NODE_ID || idt._maskNodeID == HEU_Defines.HEU_INVALID_NODE_ID || idt._mergeNodeID == HEU_Defines.HEU_INVALID_NODE_ID) { Debug.LogError("Failed to create new heightfield node in Houdini session!"); return false; } if (!session.CookNode(idt._heightNodeID, false)) { Debug.LogError("New input node failed to cook!"); return false; } return true; }
/// <summary> /// Create the main heightfield network for input. /// </summary> /// <param name="session"></param> /// <param name="idt"></param> /// <returns>True if successfully created the network</returns> public bool CreateHeightFieldInputNode(HEU_SessionBase session, HEU_InputDataTerrain idt) { idt._heightfieldNodeID = HEU_Defines.HEU_INVALID_NODE_ID; idt._heightNodeID = HEU_Defines.HEU_INVALID_NODE_ID; idt._maskNodeID = HEU_Defines.HEU_INVALID_NODE_ID; idt._mergeNodeID = HEU_Defines.HEU_INVALID_NODE_ID; idt._parentNodeID = HEU_Defines.HEU_INVALID_NODE_ID; // Heightfields should have its own objects. // Create the HeightField node network bool bResult = session.CreateHeightFieldInput(idt._parentNodeID, idt._heightFieldName, idt._numPointsX, idt._numPointsY, idt._voxelSize, HAPI_HeightFieldSampling.HAPI_HEIGHTFIELD_SAMPLING_CORNER, out idt._heightfieldNodeID, out idt._heightNodeID, out idt._maskNodeID, out idt._mergeNodeID); if (!bResult || idt._heightfieldNodeID == HEU_Defines.HEU_INVALID_NODE_ID || idt._heightNodeID == HEU_Defines.HEU_INVALID_NODE_ID || idt._maskNodeID == HEU_Defines.HEU_INVALID_NODE_ID || idt._mergeNodeID == HEU_Defines.HEU_INVALID_NODE_ID) { HEU_Logger.LogError("Failed to create new heightfield node in Houdini session!"); return(false); } if (!session.CookNode(idt._heightNodeID, false)) { HEU_Logger.LogError("New input node failed to cook!"); return(false); } return(true); }
/// <summary> /// Generates heightfield/terrain data from the given object relevant for uploading to Houdini. /// </summary> /// <param name="inputObject"></param> /// <returns>Valid input object or null if given object is not supported</returns> public HEU_InputDataTerrain GenerateTerrainDataFromGameObject(GameObject inputObject) { HEU_InputDataTerrain inputData = null; Terrain terrain = inputObject.GetComponent <Terrain>(); if (terrain != null) { TerrainData terrainData = terrain.terrainData; Vector3 terrainSize = terrainData.size; if (terrainSize.x != terrainSize.z) { Debug.LogError("Only square sized terrains are supported for input! Change to square size and try again."); return(null); } inputData = new HEU_InputDataTerrain(); inputData._inputObject = inputObject; inputData._terrain = terrain; inputData._terrainData = terrainData; // Height values in Unity are normalized between 0 and 1, so this height scale // will multiply them before uploading to Houdini. inputData._heightScale = terrainSize.y; // Terrain heightMapResolution is the pixel resolution, which we set to the number of voxels // by dividing the terrain size with it. In Houdini, this is the Grid Spacing. inputData._voxelSize = terrainSize.x / inputData._terrainData.heightmapResolution; // This is the number of heightfield voxels on each dimension. inputData._numPointsX = Mathf.RoundToInt(inputData._terrainData.heightmapResolution * inputData._voxelSize); inputData._numPointsY = Mathf.RoundToInt(inputData._terrainData.heightmapResolution * inputData._voxelSize); Matrix4x4 transformMatrix = inputObject.transform.localToWorldMatrix; //HAPI_TransformEuler transformEuler = HEU_HAPIUtility.GetHAPITransformFromMatrix(ref transformMatrix); // Volume transform used for all heightfield layers inputData._transform = new HAPI_Transform(false); // Unity terrain pivots are at bottom left, but Houdini uses centered heightfields so // apply local position offset by half sizes and account for coordinate change inputData._transform.position[0] = terrainSize.z * 0.5f; inputData._transform.position[1] = -terrainSize.x * 0.5f; inputData._transform.position[2] = 0; // Volume scale controls final size, but requires to be divided by 2 inputData._transform.scale[0] = terrainSize.x * 0.5f; inputData._transform.scale[1] = terrainSize.z * 0.5f; inputData._transform.scale[2] = 0.5f; inputData._transform.rotationQuaternion[0] = 0f; inputData._transform.rotationQuaternion[1] = 0f; inputData._transform.rotationQuaternion[2] = 0f; inputData._transform.rotationQuaternion[3] = 1f; } return(inputData); }
/// <summary> /// Creates a heightfield network inside the same object as connectNodeID. /// Uploads the terrain data from inputObject into the new heightfield network, incuding /// all terrain layers/alphamaps. /// </summary> /// <param name="session">Session that connectNodeID exists in</param> /// <param name="connectNodeID">The node to connect the network to. Most likely a SOP/merge node</param> /// <param name="inputObject">The gameobject containing the Terrain components</param> /// <param name="inputNodeID">The created heightfield network node ID</param> /// <returns>True if created network and uploaded heightfield data.</returns> public override bool CreateInputNodeWithDataUpload(HEU_SessionBase session, HAPI_NodeId connectNodeID, GameObject inputObject, out HAPI_NodeId inputNodeID) { inputNodeID = HEU_Defines.HEU_INVALID_NODE_ID; // Create input node, cook it, then upload the geometry data if (!HEU_HAPIUtility.IsNodeValidInHoudini(session, connectNodeID)) { Debug.LogError("Connection node is invalid."); return false; } HEU_InputDataTerrain idt = GenerateTerrainDataFromGameObject(inputObject); if (idt == null) { return false; } HAPI_NodeId parentNodeID = HEU_HAPIUtility.GetParentNodeID(session, connectNodeID); idt._parentNodeID = parentNodeID; if (!CreateHeightFieldInputNode(session, idt)) { return false; } if (!UploadHeightValuesWithTransform(session, idt)) { return false; } inputNodeID = idt._heightfieldNodeID; if (!UploadAlphaMaps(session, idt)) { return false; } if (!session.CookNode(inputNodeID, false)) { Debug.LogError("New input node failed to cook!"); return false; } return true; }
private bool SetMaskLayer(HEU_SessionBase session, HEU_InputDataTerrain idt, ref HAPI_VolumeInfo baseVolumeInfo) { int sizeX = idt._terrainData.alphamapWidth; int sizeY = idt._terrainData.alphamapHeight; int totalSize = sizeX * sizeY; float[] maskValues = new float[totalSize]; if (!SetHeightFieldData(session, idt._maskNodeID, 0, maskValues, HEU_Defines.HAPI_HEIGHTFIELD_LAYERNAME_MASK, ref baseVolumeInfo)) { return(false); } if (!session.CommitGeo(idt._maskNodeID)) { Debug.LogError("Failed to commit volume layer 'mask'"); return(false); } return(true); }
/// <summary> /// Upload the alphamaps (terrain layers) into heightfield network. /// Note that this skips the base layer. /// </summary> /// <param name="session"></param> /// <param name="idt"></param> /// <returns></returns> public bool UploadAlphaMaps(HEU_SessionBase session, HEU_InputDataTerrain idt) { bool bResult = true; int alphaLayers = idt._terrainData.alphamapLayers; // Skip the base layer if (alphaLayers < 1) { return true; } int sizeX = idt._terrainData.alphamapWidth; int sizeY = idt._terrainData.alphamapHeight; int totalSize = sizeX * sizeY; float[,,] alphaMaps = idt._terrainData.GetAlphamaps(0, 0, sizeX, sizeY); float[][] alphaMapsConverted = new float[alphaLayers - 1][]; // Convert the alphamap layers to double arrays. // Note that we're skipping the base alpha map. for (int m = 0; m < alphaLayers - 1; ++m) { alphaMapsConverted[m] = new float[totalSize]; for (int j = 0; j < sizeY; j++) { for (int i = 0; i < sizeX; i++) { // Flip for coordinate system change float h = alphaMaps[i, (sizeY - j - 1), m + 1]; alphaMapsConverted[m][i + j * sizeX] = h; } } } // Create volume layers for all non-base alpha maps and upload values. for (int m = 0; m < alphaLayers - 1; ++m) { string layerName = "unity_alphamap_" + m + 1; HAPI_NodeId alphaLayerID = HEU_Defines.HEU_INVALID_NODE_ID; if (!session.CreateHeightfieldInputVolumeNode(idt._heightfieldNodeID, out alphaLayerID, layerName, Mathf.RoundToInt(sizeX * idt._voxelSize), Mathf.RoundToInt(sizeY * idt._voxelSize), idt._voxelSize)) { bResult = false; Debug.LogError("Failed to create input volume node for layer " + layerName); break; } if (!SetHeightFieldData(session, alphaLayerID, 0, alphaMapsConverted[m], layerName)) { bResult = false; break; } if (!session.CommitGeo(alphaLayerID)) { bResult = false; Debug.LogError("Failed to commit volume layer " + layerName); break; } // Connect to the merge node but starting from index 1 since index 0 is height layer if (!session.ConnectNodeInput(idt._mergeNodeID, m + 2, alphaLayerID, 0)) { bResult = false; Debug.LogError("Unable to connect new volume node for layer " + layerName); break; } } return bResult; }
/// <summary> /// Upload the base height layer into heightfield network. /// </summary> /// <param name="session"></param> /// <param name="idt"></param> /// <returns></returns> public bool UploadHeightValuesWithTransform(HEU_SessionBase session, HEU_InputDataTerrain idt) { // Get Geo, Part, and Volume infos HAPI_GeoInfo geoInfo = new HAPI_GeoInfo(); if (!session.GetGeoInfo(idt._heightNodeID, ref geoInfo)) { Debug.LogError("Unable to get geo info from heightfield node!"); return false; } HAPI_PartInfo partInfo = new HAPI_PartInfo(); if (!session.GetPartInfo(geoInfo.nodeId, 0, ref partInfo)) { Debug.LogError("Unable to get part info from heightfield node!"); return false; } HAPI_VolumeInfo volumeInfo = new HAPI_VolumeInfo(); if (!session.GetVolumeInfo(idt._heightNodeID, 0, ref volumeInfo)) { Debug.LogError("Unable to get volume info from heightfield node!"); return false; } if (volumeInfo.xLength != Mathf.RoundToInt(idt._numPointsX / idt._voxelSize) || volumeInfo.yLength != Mathf.RoundToInt(idt._numPointsY / idt._voxelSize) || idt._terrainData.heightmapResolution != volumeInfo.xLength || idt._terrainData.heightmapResolution != volumeInfo.yLength) { Debug.LogError("Created heightfield in Houdini differs in voxel size from input terrain!"); return false; } // Update volume infos, and set it. This is required. volumeInfo.tileSize = 1; volumeInfo.type = HAPI_VolumeType.HAPI_VOLUMETYPE_HOUDINI; volumeInfo.storage = HAPI_StorageType.HAPI_STORAGETYPE_FLOAT; volumeInfo.transform = idt._transform; volumeInfo.minX = 0; volumeInfo.minY = 0; volumeInfo.minZ = 0; volumeInfo.tupleSize = 1; volumeInfo.tileSize = 1; volumeInfo.hasTaper = false; volumeInfo.xTaper = 0f; volumeInfo.yTaper = 0f; if (!session.SetVolumeInfo(idt._heightNodeID, partInfo.id, ref volumeInfo)) { Debug.LogError("Unable to set volume info on input heightfield node!"); return false; } // Now set the height data float[,] heights = idt._terrainData.GetHeights(0, 0, volumeInfo.xLength, volumeInfo.yLength); int sizeX = heights.GetLength(0); int sizeY = heights.GetLength(1); int totalSize = sizeX * sizeY; // Convert to single array float[] heightsArr = new float[totalSize]; for (int j = 0; j < sizeY; j++) { for (int i = 0; i < sizeX; i++) { // Flip for coordinate system change float h = heights[i, (sizeY - j - 1)]; heightsArr[i + j * sizeX] = h * idt._heightScale; } } // Set the base height layer if (!session.SetHeightFieldData(idt._heightNodeID, 0, "height", heightsArr, 0, totalSize)) { Debug.LogError("Unable to set height values on input heightfield node!"); return false; } if (!session.CommitGeo(idt._heightNodeID)) { Debug.LogError("Unable to commit geo on input heightfield node!"); return false; } return true; }
/// <summary> /// Creates a heightfield network inside the same object as connectNodeID. /// Uploads the terrain data from inputObject into the new heightfield network, incuding /// all terrain layers/alphamaps. /// </summary> /// <param name="session">Session that connectNodeID exists in</param> /// <param name="connectNodeID">The node to connect the network to. Most likely a SOP/merge node</param> /// <param name="inputObject">The gameobject containing the Terrain components</param> /// <param name="inputNodeID">The created heightfield network node ID</param> /// <returns>True if created network and uploaded heightfield data.</returns> public override bool CreateInputNodeWithDataUpload(HEU_SessionBase session, HAPI_NodeId connectNodeID, GameObject inputObject, out HAPI_NodeId inputNodeID) { inputNodeID = HEU_Defines.HEU_INVALID_NODE_ID; // Create input node, cook it, then upload the geometry data if (!HEU_HAPIUtility.IsNodeValidInHoudini(session, connectNodeID)) { Debug.LogError("Connection node is invalid."); return(false); } HEU_InputDataTerrain idt = GenerateTerrainDataFromGameObject(inputObject); if (idt == null) { return(false); } HAPI_NodeId parentNodeID = HEU_HAPIUtility.GetParentNodeID(session, connectNodeID); idt._parentNodeID = parentNodeID; if (!CreateHeightFieldInputNode(session, idt)) { return(false); } HAPI_VolumeInfo volumeInfo = new HAPI_VolumeInfo(); if (!UploadHeightValuesWithTransform(session, idt, ref volumeInfo)) { return(false); } inputNodeID = idt._heightfieldNodeID; bool bMaskSet = false; if (!UploadAlphaMaps(session, idt, ref volumeInfo, out bMaskSet)) { return(false); } if (!bMaskSet) { // While the default HF created by the input node also creates a default mask layer, // we still need to set the mask layer's transform. So this uses the base VolumeInfo // to do just that. if (!SetMaskLayer(session, idt, ref volumeInfo)) { return(false); } } if (!session.CookNode(inputNodeID, false)) { Debug.LogError("New input node failed to cook!"); return(false); } return(true); }
/// <summary> /// Generates heightfield/terrain data from the given object relevant for uploading to Houdini. /// </summary> /// <param name="inputObject"></param> /// <returns>Valid input object or null if given object is not supported</returns> public HEU_InputDataTerrain GenerateTerrainDataFromGameObject(GameObject inputObject) { HEU_InputDataTerrain inputData = null; Terrain terrain = inputObject.GetComponent <Terrain>(); if (terrain != null) { TerrainData terrainData = terrain.terrainData; Vector3 terrainSize = terrainData.size; if (terrainSize.x != terrainSize.z) { Debug.LogError("Only square sized terrains are supported for input! Change to square size and try again."); return(null); } inputData = new HEU_InputDataTerrain(); inputData._inputObject = inputObject; inputData._terrain = terrain; inputData._terrainData = terrainData; // Height values in Unity are normalized between 0 and 1, so this height scale // will multiply them before uploading to Houdini. inputData._heightScale = terrainSize.y; // Terrain heightMapResolution is the pixel resolution, which we set to the number of voxels // by dividing the terrain size with it. In Houdini, this is the Grid Spacing. inputData._voxelSize = terrainSize.x / inputData._terrainData.heightmapResolution; // Adding voxel size here to account for the corner sampling. // In HEU_TerrainUtility::GenerateTerrainFromVolume we subtract 1 to // account for corner sampling as well. This ensures maintaining the size // when roundtripping the terrain. float hfSizeX = terrainSize.x + inputData._voxelSize; float hfSizeY = terrainSize.z + inputData._voxelSize; // Subtract 1 from size otherwise idt._terrainData.GetHeights fails with size out of bounds. // This is the number of heightfield voxels on each dimension. inputData._numPointsX = Mathf.RoundToInt(inputData._terrainData.heightmapResolution * inputData._voxelSize - inputData._voxelSize); inputData._numPointsY = Mathf.RoundToInt(inputData._terrainData.heightmapResolution * inputData._voxelSize - inputData._voxelSize); Matrix4x4 transformMatrix = inputObject.transform.localToWorldMatrix; // Volume transform used for all heightfield layers inputData._transform = new HAPI_Transform(false); // Unity terrain pivots are at bottom left, but Houdini uses centered heightfields so // apply local position offset by half sizes and account for coordinate change. // Subtract 1 to offset the overlap inputData._transform.position[0] = (hfSizeY - inputData._voxelSize) * 0.5f; inputData._transform.position[1] = -(hfSizeX - inputData._voxelSize) * 0.5f; inputData._transform.position[2] = 0; // Volume scale controls final size, but requires to be divided by 2 inputData._transform.scale[0] = hfSizeX * 0.5f; inputData._transform.scale[1] = hfSizeY * 0.5f; inputData._transform.scale[2] = 0.5f; inputData._transform.rotationQuaternion[0] = 0f; inputData._transform.rotationQuaternion[1] = 0f; inputData._transform.rotationQuaternion[2] = 0f; inputData._transform.rotationQuaternion[3] = 1f; } return(inputData); }
/// <summary> /// Upload the alphamaps (TerrainLayers) into heightfield network. /// </summary> /// <param name="session"></param> /// <param name="idt"></param> /// <param name="baseVolumeInfo">The valid base height HAPI_VolumeInfo</param> /// <param name="bMaskSet">This is set to true if a mask layer was uploaded</param> /// <returns>True if successfully uploaded all layers</returns> public bool UploadAlphaMaps(HEU_SessionBase session, HEU_InputDataTerrain idt, ref HAPI_VolumeInfo baseVolumeInfo, out bool bMaskSet) { bool bResult = true; bMaskSet = false; int alphaLayers = idt._terrainData.alphamapLayers; if (alphaLayers < 1) { return(bResult); } int sizeX = idt._terrainData.alphamapWidth; int sizeY = idt._terrainData.alphamapHeight; int totalSize = sizeX * sizeY; float[,,] alphaMaps = idt._terrainData.GetAlphamaps(0, 0, sizeX, sizeY); float[][] alphaMapsConverted = new float[alphaLayers][]; // Convert the alphamap layers to double arrays. for (int m = 0; m < alphaLayers; ++m) { alphaMapsConverted[m] = new float[totalSize]; for (int j = 0; j < sizeY; j++) { for (int i = 0; i < sizeX; i++) { // Flip for coordinate system change float h = alphaMaps[i, (sizeY - j - 1), m]; alphaMapsConverted[m][i + j * sizeX] = h; } } } // Create volume layers for all alpha maps and upload values. bool bMaskLayer = false; int inputLayerIndex = 1; for (int m = 0; m < alphaLayers; ++m) { #if UNITY_2018_3_OR_NEWER string layerName = idt._terrainData.terrainLayers[m].name; #else string layerName = "unity_alphamap_" + m + 1; #endif // The Unity layer name could contain '.terrainlayer' and spaces. Remove them because Houdini doesn't allow // spaces, and the extension isn't necessary. layerName = layerName.Replace(" ", "_"); int extIndex = layerName.LastIndexOf(HEU_Defines.HEU_EXT_TERRAINLAYER); if (extIndex > 0) { layerName = layerName.Remove(extIndex); } //Debug.Log("Processing terrain layer: " + layerName); HAPI_NodeId alphaLayerID = HEU_Defines.HEU_INVALID_NODE_ID; if (layerName.Equals(HEU_Defines.HAPI_HEIGHTFIELD_LAYERNAME_HEIGHT)) { // Skip height (base) layer (since it has been uploaded already) continue; } else if (layerName.Equals(HEU_Defines.HAPI_HEIGHTFIELD_LAYERNAME_MASK)) { //Debug.Log("Mask layer found! Skipping creating the HF."); bMaskSet = true; bMaskLayer = true; alphaLayerID = idt._maskNodeID; } else { bMaskLayer = false; if (!session.CreateHeightfieldInputVolumeNode(idt._heightfieldNodeID, out alphaLayerID, layerName, Mathf.RoundToInt(sizeX * idt._voxelSize), Mathf.RoundToInt(sizeY * idt._voxelSize), idt._voxelSize)) { bResult = false; Debug.LogError("Failed to create input volume node for layer " + layerName); break; } } //Debug.Log("Uploading terrain layer: " + layerName); if (!SetHeightFieldData(session, alphaLayerID, 0, alphaMapsConverted[m], layerName, ref baseVolumeInfo)) { bResult = false; break; } #if UNITY_2018_3_OR_NEWER SetTerrainLayerAttributesToHeightField(session, alphaLayerID, 0, idt._terrainData.terrainLayers[m]); #endif if (!session.CommitGeo(alphaLayerID)) { bResult = false; Debug.LogError("Failed to commit volume layer " + layerName); break; } if (!bMaskLayer) { // Connect to the merge node but starting from index 1 since index 0 is height layer if (!session.ConnectNodeInput(idt._mergeNodeID, inputLayerIndex + 1, alphaLayerID, 0)) { bResult = false; Debug.LogError("Unable to connect new volume node for layer " + layerName); break; } inputLayerIndex++; } } return(bResult); }
/// <summary> /// Upload the base height layer into heightfield network. /// </summary> /// <param name="session"></param> /// <param name="idt"></param> /// <returns></returns> public bool UploadHeightValuesWithTransform(HEU_SessionBase session, HEU_InputDataTerrain idt, ref HAPI_VolumeInfo volumeInfo) { // Get Geo, Part, and Volume infos HAPI_GeoInfo geoInfo = new HAPI_GeoInfo(); if (!session.GetGeoInfo(idt._heightNodeID, ref geoInfo)) { HEU_Logger.LogError("Unable to get geo info from heightfield node!"); return(false); } HAPI_PartInfo partInfo = new HAPI_PartInfo(); if (!session.GetPartInfo(geoInfo.nodeId, 0, ref partInfo)) { HEU_Logger.LogError("Unable to get part info from heightfield node!"); return(false); } volumeInfo = new HAPI_VolumeInfo(); if (!session.GetVolumeInfo(idt._heightNodeID, 0, ref volumeInfo)) { HEU_Logger.LogError("Unable to get volume info from heightfield node!"); return(false); } if ((volumeInfo.xLength - 1) != Mathf.RoundToInt(idt._numPointsX / idt._voxelSize) || (volumeInfo.yLength - 1) != Mathf.RoundToInt(idt._numPointsY / idt._voxelSize) || idt._terrainData.heightmapResolution != volumeInfo.xLength || idt._terrainData.heightmapResolution != volumeInfo.yLength) { HEU_Logger.LogWarning("Created heightfield in Houdini differs in voxel size from input terrain! Terrain may require resampling."); } // Update volume infos, and set it. This is required. volumeInfo.tileSize = 1; volumeInfo.type = HAPI_VolumeType.HAPI_VOLUMETYPE_HOUDINI; volumeInfo.storage = HAPI_StorageType.HAPI_STORAGETYPE_FLOAT; volumeInfo.transform = idt._transform; volumeInfo.minX = 0; volumeInfo.minY = 0; volumeInfo.minZ = 0; volumeInfo.tupleSize = 1; volumeInfo.tileSize = 1; volumeInfo.hasTaper = false; volumeInfo.xTaper = 0f; volumeInfo.yTaper = 0f; if (!session.SetVolumeInfo(idt._heightNodeID, partInfo.id, ref volumeInfo)) { HEU_Logger.LogError("Unable to set volume info on input heightfield node!"); return(false); } // Now set the height data float[,] heights = idt._terrainData.GetHeights(0, 0, idt._terrainData.heightmapResolution, idt._terrainData.heightmapResolution); int sizeX = heights.GetLength(0); int sizeY = heights.GetLength(1); int totalSize = sizeX * sizeY; // Convert to single array float[] heightsArr = new float[totalSize]; for (int j = 0; j < sizeY; j++) { for (int i = 0; i < sizeX; i++) { // Flip for coordinate system change float h = heights[i, (sizeY - j - 1)]; heightsArr[i + j * sizeX] = h * idt._heightScale; } } if (volumeInfo.xLength != volumeInfo.yLength) { HEU_Logger.LogError("Error: Houdini heightmap must be square!"); return(false); } if (idt._terrainData.heightmapResolution != volumeInfo.xLength) { // Resize heightsArr to idt._terrainData.heightmapResolution HEU_Logger.LogWarningFormat("Attempting to resize landscape from ({0}x{1}) to ({2}x{3})", idt._terrainData.heightmapResolution, idt._terrainData.heightmapResolution, volumeInfo.xLength, volumeInfo.xLength); heightsArr = HEU_TerrainUtility.ResampleData(heightsArr, idt._terrainData.heightmapResolution, idt._terrainData.heightmapResolution, volumeInfo.xLength, volumeInfo.xLength); sizeX = volumeInfo.xLength; sizeY = volumeInfo.yLength; totalSize = sizeX * sizeY; } // Set the base height layer if (!session.SetHeightFieldData(idt._heightNodeID, 0, HEU_Defines.HAPI_HEIGHTFIELD_LAYERNAME_HEIGHT, heightsArr, 0, totalSize)) { HEU_Logger.LogError("Unable to set height values on input heightfield node!"); return(false); } SetTerrainDataAttributesToHeightField(session, geoInfo.nodeId, 0, idt._terrainData); SetTreePrototypes(session, geoInfo.nodeId, 0, idt._terrainData); if (!session.CommitGeo(idt._heightNodeID)) { HEU_Logger.LogError("Unable to commit geo on input heightfield node!"); return(false); } return(true); }