/// <summary> /// Open given session in a new Houdini instance. /// </summary> /// <param name="session">Session to open. If null, will use default session.</param> /// <returns>True if successfully loaded session</returns> public static bool OpenSessionInHoudini(HEU_SessionBase session = null) { if (session == null || !session.IsSessionValid()) { session = GetOrCreateDefaultSession(); if (session == null || !session.IsSessionValid()) { session.SetSessionErrorMsg("No valid session found. Unable to open session in Houdini!", true); return false; } } string HIPName = string.Format("hscene_{0}.hip", System.IO.Path.GetRandomFileName().Replace(".", "")); string HIPPath = Application.temporaryCachePath + HEU_Platform.DirectorySeparatorStr + HIPName; if (!session.SaveHIPFile(HIPPath, false)) { session.SetSessionErrorMsg("Unable to save session to .hip file at: " + HIPPath, true); return false; } Debug.Log("Saved session to " + HIPPath); string HoudiniPath = HEU_PluginSettings.HoudiniDebugLaunchPath; #if UNITY_EDITOR_OSX // On macOS, need to find the actual executable, which is within one of .app folders // that Houdini ships with, depending on the installation type. // HoudiniPath should by default be pointing to the HFS (Houdini install) folder. // Or user should have selected one of the .app folders within. // If not set to .app, then set it based on what app is available if (!HoudiniPath.EndsWith(".app", System.StringComparison.InvariantCulture)) { string[] appNames = { "FX", "Core", "Indie", "Apprentice", "Indie", "Indie Steam Edition" }; string tryPath; foreach(string name in appNames) { tryPath = HEU_Platform.BuildPath(HoudiniPath, string.Format("Houdini {0} {1}.app", name, HEU_HoudiniVersion.HOUDINI_VERSION_STRING)); if (HEU_Platform.DoesPathExist(tryPath)) { HoudiniPath = tryPath; break; } } } if (HoudiniPath.EndsWith(".app", System.StringComparison.InvariantCulture)) { // Get the executable name inside the .app, but the executable // name is based on the license type, so need to query the // license type and map it to executable name: // Houdini Apprenctice 18.0.100 // Houdini Core 18.0.100 // Houdini FX 18.0.100 // Houdini Indie 18.0.100 // Houdini Indie Steam Edition 18.0.100 //HoudiniPath = "/Applications/Houdini/Houdini18.0.100/Houdini Indie 18.0.100.app"; string hexecutable = ""; string pattern = @"(.*)/Houdini (.*) (.*).app$"; Regex reg = new Regex(pattern); Match match = reg.Match(HoudiniPath); if (match.Success && match.Groups.Count > 2) { switch(match.Groups[2].Value) { case "Apprentice": hexecutable = "happrentice"; break; case "Core": hexecutable = "houdinicore"; break; case "FX": hexecutable = "houdini"; break; case "Indie": hexecutable = "hindie"; break; case "Indie Steam Edition": hexecutable = "hindie.steam"; break; default: break; } } HoudiniPath += "/Contents/MacOS/" + hexecutable; } #endif var HoudiniProcess = new System.Diagnostics.Process(); HoudiniProcess.StartInfo.FileName = HoudiniPath; HoudiniProcess.StartInfo.Arguments = string.Format("\"{0}\"", HIPPath); if (!HoudiniProcess.Start()) { session.SetSessionErrorMsg("Unable to start Houdini. Check that the Houdini Debug Exectable path is valid in Plugin Settings.", true); HEU_EditorUtility.RevealInFinder(HIPPath); return false; } return true; }
public static void ExportAssetsToGeoFiles(HEU_HoudiniAssetRoot[] rootAssets) { // Open a Dialog to get user settings: // -directory to write to // -file name with extension (determines file format) string exportExt = "bgeo.sc"; List <HEU_GeoNode> outputGeoNodes = new List <HEU_GeoNode>(); int numNodes = 0; int numAssets = rootAssets.Length; if (numAssets == 0) { return; } string exportDir = EditorSaveFolderPanel("Export Geo to Folder", HEU_PluginSettings.LastExportPath, ""); if (string.IsNullOrEmpty(exportDir)) { return; } // Save latest folder choice HEU_PluginSettings.LastExportPath = exportDir; if (string.IsNullOrEmpty(exportExt)) { Debug.LogErrorFormat("Export extension cannot be empty."); return; } if (!HEU_Platform.DoesDirectoryExist(exportDir) && HEU_Platform.CreateDirectory(exportDir)) { Debug.LogErrorFormat("Error creating directory at {0}.", exportDir); return; } for (int i = 0; i < numAssets; ++i) { if (rootAssets[i] != null && rootAssets[i]._houdiniAsset != null) { HEU_HoudiniAsset asset = rootAssets[i]._houdiniAsset; HEU_SessionBase session = asset.GetAssetSession(true); if (session == null || !session.IsSessionValid()) { continue; } if (string.IsNullOrEmpty(asset.AssetName)) { Debug.LogErrorFormat("Unable to export output of asset at {0} due to empty name.", asset.AssetPath); continue; } outputGeoNodes.Clear(); asset.GetOutputGeoNodes(outputGeoNodes); numNodes = outputGeoNodes.Count; for (int j = 0; j < numNodes; ++j) { string exportPath = string.Format("{0}/{1}_{2}.{3}", exportDir, asset.RootGameObject.name, outputGeoNodes[j].GeoName, exportExt); if (!session.SaveGeoToFile(outputGeoNodes[j].GeoID, exportPath)) { Debug.LogErrorFormat("Failed to export output geo of asset with path: {0}", exportPath); } else { Debug.LogFormat("Exported output geo {0} of {1} at: {2}", outputGeoNodes[j].GeoName, asset.RootGameObject.name, exportPath); } } } } }
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); #if !HEU_TERRAIN_COLLIDER_DISABLED TerrainCollider collider = HEU_GeneralUtility.GetOrCreateComponent <TerrainCollider>(newGameObject); #endif // The TerrainData and TerrainLayer files needs to be saved out if we create them. // Try user specified path, otherwise use the cache folder string exportTerrainDataPath = terrainBuffers[t]._terrainDataExportPath; if (string.IsNullOrEmpty(exportTerrainDataPath)) { // This creates the relative folder path from the Asset's cache folder: {assetCache}/{geo name}/Terrain/Tile{tileIndex}/... exportTerrainDataPath = HEU_Platform.BuildPath(outputTerrainpath, HEU_Defines.HEU_FOLDER_TERRAIN, HEU_Defines.HEU_FOLDER_TILE + terrainBuffers[t]._tileIndex); } bool bFullExportTerrainDataPath = HEU_Platform.DoesFileExist(exportTerrainDataPath); 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); } if (bFullExportTerrainDataPath) { terrain.terrainData = HEU_AssetDatabase.CopyAndLoadAssetAtGivenPath(sourceTerrainData, exportTerrainDataPath, typeof(TerrainData)) as TerrainData; } else { terrain.terrainData = HEU_AssetDatabase.CopyUniqueAndLoadAssetAtAnyPath(sourceTerrainData, exportTerrainDataPath, 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(); if (bFullExportTerrainDataPath) { string folderPath = HEU_Platform.GetFolderPath(exportTerrainDataPath, true); HEU_AssetDatabase.CreatePathWithFolders(folderPath); HEU_AssetDatabase.CreateAsset(terrain.terrainData, exportTerrainDataPath); } else { string assetPathName = "TerrainData" + HEU_Defines.HEU_EXT_ASSET; HEU_AssetDatabase.CreateObjectInAssetCacheFolder(terrain.terrainData, exportTerrainDataPath, null, assetPathName, typeof(TerrainData)); } } TerrainData terrainData = terrain.terrainData; #if !HEU_TERRAIN_COLLIDER_DISABLED collider.terrainData = terrainData; #endif 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) { // Copy the TerrainLayer file TerrainLayer prevTerrainLayer = terrainlayer; terrainlayer = HEU_AssetDatabase.CopyAndLoadAssetAtAnyPath(terrainlayer, outputTerrainpath, typeof(TerrainLayer), true) as TerrainLayer; if (terrainlayer != null) { if (terrainLayerIndex >= 0) { // Update the TerrainLayer reference in the list with this copy finalTerrainLayers[terrainLayerIndex] = terrainlayer; } else { // Newly added terrainLayerIndex = finalTerrainLayers.Count; finalTerrainLayers.Add(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. } } else { // Could be a layer in Assets/ but not part of existing layers in TerrainData terrainLayerIndex = finalTerrainLayers.Count; finalTerrainLayers.Add(terrainlayer); bSetTerrainLayerProperties = false; } } 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 string AppendPrefabPath(string inAssetCacheFolder, string assetName) { string prefabPath = HEU_Platform.BuildPath(inAssetCacheFolder, assetName); return prefabPath + ".prefab"; }
public static string AppendMaterialsPathToAssetFolder(string inAssetCacheFolder) { return HEU_Platform.BuildPath(inAssetCacheFolder, "Materials"); }
public static string AppendMeshesPathToAssetFolder(string inAssetCacheFolder) { return HEU_Platform.BuildPath(inAssetCacheFolder, HEU_Defines.HEU_FOLDER_MESHES); }
public static string AppendMaterialsPathToAssetFolder(string inAssetCacheFolder) { return HEU_Platform.BuildPath(inAssetCacheFolder, HEU_Defines.HEU_FOLDER_MATERIALS); }
public void GenerateTerrainWithAlphamaps(HEU_SessionBase session, HEU_HoudiniAsset houdiniAsset, bool bRebuild) { if(_layers == null || _layers.Count == 0) { Debug.LogError("Unable to generate terrain due to lack of heightfield layers!"); return; } HEU_VolumeLayer heightLayer = _layers[0]; HAPI_VolumeInfo heightVolumeInfo = new HAPI_VolumeInfo(); bool bResult = session.GetVolumeInfo(_ownerNode.GeoID, heightLayer._part.PartID, ref heightVolumeInfo); if (!bResult) { Debug.LogErrorFormat("Unable to get volume info for height layer: {0}!", heightLayer._layerName); return; } // Special handling of volume cache presets. It is applied here (if exists) because it might pertain to TerrainData that exists // in the AssetDatabase. If we don't apply here but rather create a new one, the existing file will get overwritten. // Applying the preset here for terrain ensures the TerrainData is reused. // Get the volume preset for this part HEU_VolumeCachePreset volumeCachePreset = houdiniAsset.GetVolumeCachePreset(_ownerNode.ObjectNode.ObjectName, _ownerNode.GeoName, TileIndex); if (volumeCachePreset != null) { ApplyPreset(volumeCachePreset); // Remove it so that it doesn't get applied when doing the recook step houdiniAsset.RemoveVolumeCachePreset(volumeCachePreset); } // The TerrainData and TerrainLayer files needs to be saved out if we create them. This creates the relative folder // path from the Asset's cache folder: {assetCache}/{geo name}/Terrain/Tile{tileIndex}/... string relativeFolderPath = HEU_Platform.BuildPath(_ownerNode.GeoName, HEU_Defines.HEU_FOLDER_TERRAIN, HEU_Defines.HEU_FOLDER_TILE + TileIndex); if (bRebuild) { // For full rebuild, re-create the TerrainData instead of using previous _terrainData = null; } //Debug.Log("Generating Terrain with AlphaMaps: " + (_terrainData != null ? _terrainData.name : "NONE")); TerrainData terrainData = _terrainData; Vector3 terrainOffsetPosition = Vector3.zero; // Look up TerrainData file via attribute if user has set it string terrainDataFile = HEU_GeneralUtility.GetAttributeStringValueSingle(session, _ownerNode.GeoID, heightLayer._part.PartID, HEU_Defines.DEFAULT_UNITY_HEIGHTFIELD_TERRAINDATA_FILE_ATTR, HAPI_AttributeOwner.HAPI_ATTROWNER_PRIM); if (!string.IsNullOrEmpty(terrainDataFile)) { TerrainData loadedTerrainData = HEU_AssetDatabase.LoadAssetAtPath(terrainDataFile, typeof(TerrainData)) as TerrainData; if (loadedTerrainData == null) { Debug.LogWarningFormat("TerrainData, set via attribute, not found at: {0}", terrainDataFile); } else { // In the case that the specified TerrainData belongs to another Terrain (i.e. input Terrain), // make a copy of it and store it in our cache. Note that this overwrites existing TerrainData in our cache // because the workflow is such that attributes will always override local setting. string bakedTerrainPath = houdiniAsset.GetValidAssetCacheFolderPath(); bakedTerrainPath = HEU_Platform.BuildPath(bakedTerrainPath, relativeFolderPath); terrainData = HEU_AssetDatabase.CopyAndLoadAssetAtAnyPath(loadedTerrainData, bakedTerrainPath, typeof(TerrainData), true) as TerrainData; if (terrainData == null) { Debug.LogErrorFormat("Unable to copy TerrainData from {0} for generating Terrain.", terrainDataFile); } } } Terrain terrain = null; // Generate the terrain and terrain data from the height layer. This applies height values. bResult = HEU_TerrainUtility.GenerateTerrainFromVolume(session, ref heightVolumeInfo, heightLayer._part.ParentGeoNode.GeoID, heightLayer._part.PartID, heightLayer._part.OutputGameObject, ref terrainData, out terrainOffsetPosition, ref terrain); if (!bResult || terrainData == null) { return; } if (_terrainData != terrainData) { _terrainData = terrainData; heightLayer._part.SetTerrainData(terrainData, relativeFolderPath); } heightLayer._part.SetTerrainOffsetPosition(terrainOffsetPosition); int terrainSize = terrainData.heightmapResolution; // Now process TerrainLayers and alpha maps // First, preprocess all layers to get heightfield arrays, converted to proper size List<float[]> normalizedHeightfields = new List<float[]>(); // Corresponding list of HF volume layers to process as splatmaps List<HEU_VolumeLayer> terrainLayersToProcess = new List<HEU_VolumeLayer>(); List<int[,]> convertedDetailMaps = new List<int[,]>(); List<HEU_DetailPrototype> detailPrototypes = new List<HEU_DetailPrototype>(); int numLayers = _layers.Count; float minHeight = 0; float maxHeight = 0; float heightRange = 0; // This skips the height layer, and processes all other layers. // Note that mask shouldn't be part of _layers at this point. // The layers are normalized and split into detail and terrain layers. for(int i = 1; i < numLayers; ++i) { if (_layers[i]._layerType == HFLayerType.DETAIL) { if (_detailProperties == null) { _detailProperties = new HEU_DetailProperties(); } // Convert to detail map, and add to list to set later int[,] normalizedDetail = HEU_TerrainUtility.GetDetailMapFromPart( session, _ownerNode.GeoID, _layers[i]._part.PartID, out _detailProperties._detailResolution); if (normalizedDetail != null && normalizedDetail.Length > 0) { convertedDetailMaps.Add(normalizedDetail); detailPrototypes.Add(_layers[i]._detailPrototype); } } else { // Convert to normalized heightfield float[] normalizedHF = HEU_TerrainUtility.GetNormalizedHeightmapFromPartWithMinMax( session, _ownerNode.GeoID, _layers[i]._part.PartID, _layers[i]._xLength, _layers[i]._yLength, ref minHeight, ref maxHeight, ref heightRange, false); if (normalizedHF != null && normalizedHF.Length > 0) { normalizedHeightfields.Add(normalizedHF); terrainLayersToProcess.Add(_layers[i]); } } } int numTerrainLayersToProcess = terrainLayersToProcess.Count; HAPI_NodeId geoID; HAPI_PartId partID; Texture2D defaultTexture = LoadDefaultSplatTexture(); #if UNITY_2018_3_OR_NEWER // Create or update the terrain layers based on heightfield layers. // 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); // This holds the alpha map indices for each layer that will be added to the TerrainData. // The alpha maps could be a mix of existing and new values, so need to know which to use // Initially set to use existing alpha maps, then override later on if specified via HF layers List<int> alphaMapIndices = new List<int>(); for (int a = 0; a < existingTerrainLayers.Length; ++a) { // Negative indices for existing alpha map (offset by -1) alphaMapIndices.Add(-a - 1); } bool bNewTerrainLayer = false; HEU_VolumeLayer layer = null; TerrainLayer terrainLayer = null; bool bSetTerrainLayerProperties = true; for (int m = 0; m < numTerrainLayersToProcess; ++m) { bNewTerrainLayer = false; bSetTerrainLayerProperties = true; layer = terrainLayersToProcess[m]; geoID = _ownerNode.GeoID; partID = layer._part.PartID; terrainLayer = null; int terrainLayerIndex = -1; // The TerrainLayer attribute overrides existing TerrainLayer. So if its set, load and use it. string terrainLayerFile = HEU_GeneralUtility.GetAttributeStringValueSingle(session, geoID, partID, HEU_Defines.DEFAULT_UNITY_HEIGHTFIELD_TERRAINLAYER_FILE_ATTR, HAPI_AttributeOwner.HAPI_ATTROWNER_PRIM); if (!string.IsNullOrEmpty(terrainLayerFile)) { terrainLayer = HEU_AssetDatabase.LoadAssetAtPath(terrainLayerFile, typeof(TerrainLayer)) as TerrainLayer; if (terrainLayer == null) { Debug.LogWarningFormat("TerrainLayer, set via attribute, not found at: {0}", terrainLayerFile); // Not earlying out or skipping this layer due to error because we want to keep proper indexing // by creating a new TerrainLayer. } else { // TerrainLayer loaded from attribute. // It could be an existing TerrainLayer that is already part of finalTerrainLayers // or could be a new one which needs to be added. // If its a different TerrainLayer than existing, update the finalTerrainLayers, and index. if (layer._terrainLayer != null && layer._terrainLayer != terrainLayer) { terrainLayerIndex = HEU_TerrainUtility.GetTerrainLayerIndex(layer._terrainLayer, existingTerrainLayers); if (terrainLayerIndex >= 0) { finalTerrainLayers[terrainLayerIndex] = terrainLayer; } } if (terrainLayerIndex == -1) { // Always check if its part of existing list so as not to add it again terrainLayerIndex = HEU_TerrainUtility.GetTerrainLayerIndex(terrainLayer, existingTerrainLayers); } } } // No terrain layer specified, so try using existing if we have it if (terrainLayer == null) { terrainLayerIndex = HEU_TerrainUtility.GetTerrainLayerIndex(layer._terrainLayer, existingTerrainLayers); if (terrainLayerIndex >= 0) { // Note the terrainLayerIndex is same for finalTerrainLayers as existingTerrainLayers terrainLayer = existingTerrainLayers[terrainLayerIndex]; } } // Still not found, so just create a new one if (terrainLayer == null) { terrainLayer = new TerrainLayer(); terrainLayer.name = layer._layerName; //Debug.LogFormat("Created new TerrainLayer with name: {0} ", terrainLayer.name); bNewTerrainLayer = true; } if (terrainLayerIndex == -1) { // Adding to the finalTerrainLayers if this is indeed a newly created or loaded TerrainLayer // (i.e. isn't already part of the TerrainLayers for this Terrain). // Save this layer's index for later on if we make a copy. terrainLayerIndex = finalTerrainLayers.Count; finalTerrainLayers.Add(terrainLayer); // Positive index for alpha map from heightfield (starting at 1) alphaMapIndices.Add(m + 1); } else { // Positive index for alpha map from heightfield (starting at 1) alphaMapIndices[terrainLayerIndex] = m + 1; } // 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 (!bNewTerrainLayer && layer._hasLayerAttributes) { string bakedTerrainPath = houdiniAsset.GetValidAssetCacheFolderPath(); bakedTerrainPath = HEU_Platform.BuildPath(bakedTerrainPath, relativeFolderPath); TerrainLayer prevTerrainLayer = terrainLayer; terrainLayer = HEU_AssetDatabase.CopyAndLoadAssetAtAnyPath(terrainLayer, bakedTerrainPath, typeof(TerrainLayer), true) as TerrainLayer; if (terrainLayer != null) { // Update the TerrainLayer reference in the list with this copy finalTerrainLayers[terrainLayerIndex] = 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. } } // Now override layer properties if they have been set via attributes if (bSetTerrainLayerProperties) { LoadLayerPropertiesFromAttributes(session, geoID, partID, terrainLayer, bNewTerrainLayer, defaultTexture); } if (bNewTerrainLayer) { // In order to retain the new TerrainLayer, it must be saved to the AssetDatabase. Object savedObject = null; string layerFileNameWithExt = terrainLayer.name; if (!layerFileNameWithExt.EndsWith(HEU_Defines.HEU_EXT_TERRAINLAYER)) { layerFileNameWithExt += HEU_Defines.HEU_EXT_TERRAINLAYER; } houdiniAsset.AddToAssetDBCache(layerFileNameWithExt, terrainLayer, relativeFolderPath, ref savedObject); } // Store reference layer._terrainLayer = terrainLayer; } // Get existing alpha maps so we can reuse the values if needed float[,,] existingAlphaMaps = terrainData.GetAlphamaps(0, 0, terrainData.alphamapWidth, terrainData.alphamapHeight); terrainData.terrainLayers = finalTerrainLayers.ToArray(); int numTotalAlphaMaps = finalTerrainLayers.Count; #else // Create or update the SplatPrototype based on heightfield layers. // Need to create or reuse SplatPrototype for each layer in heightfield, representing the textures. SplatPrototype[] existingSplats = terrainData.splatPrototypes; // A full rebuild clears out existing splats, but a regular cook keeps them. List<SplatPrototype> finalSplats = new List<SplatPrototype>(existingSplats); // This holds the alpha map indices for each layer that will be added to the TerrainData // The alpha maps could be a mix of existing and new values, so need to know which to use List<int> alphaMapIndices = new List<int>(); // Initially set to use existing alpha maps, then override later on if specified via HF layers. for (int a = 0; a < existingSplats.Length; ++a) { // Negative indices for existing alpha map (offset by -1) alphaMapIndices.Add(-a - 1); } bool bNewSplat = false; HEU_VolumeLayer layer = null; SplatPrototype splatPrototype = null; for (int m = 0; m < numTerrainLayersToProcess; ++m) { bNewSplat = false; layer = terrainLayersToProcess[m]; geoID = _ownerNode.GeoID; partID = layer._part.PartID; // Try to find existing SplatPrototype for reuse. But not for full rebuild. splatPrototype = null; if (layer._splatPrototypeIndex >= 0 && layer._splatPrototypeIndex < existingSplats.Length) { splatPrototype = existingSplats[layer._splatPrototypeIndex]; // Positive index for alpha map from heightfield (starting at 1) alphaMapIndices[layer._splatPrototypeIndex] = m + 1; } if (splatPrototype == null) { splatPrototype = new SplatPrototype(); layer._splatPrototypeIndex = finalSplats.Count; finalSplats.Add(splatPrototype); // Positive index for alpha map from heightfield (starting at 1) alphaMapIndices.Add(m + 1); } // Now override splat properties if they have been set via attributes LoadLayerPropertiesFromAttributes(session, geoID, partID, splatPrototype, bNewSplat, defaultTexture); } // On regular cook, get existing alpha maps so we can reuse the values if needed. float[,,] existingAlphaMaps = terrainData.GetAlphamaps(0, 0, terrainData.alphamapWidth, terrainData.alphamapHeight); terrainData.splatPrototypes = finalSplats.ToArray(); int numTotalAlphaMaps = finalSplats.Count; #endif // Set alpha maps by combining with existing alpha maps, and appending new heightfields float[,,] alphamap = null; if (numTotalAlphaMaps > 0 && terrainLayersToProcess.Count > 0) { // Convert the heightfields into alpha maps with layer strengths float[] strengths = new float[terrainLayersToProcess.Count]; for (int m = 0; m < terrainLayersToProcess.Count; ++m) { strengths[m] = terrainLayersToProcess[m]._strength; } alphamap = HEU_TerrainUtility.AppendConvertedHeightFieldToAlphaMap( terrainLayersToProcess[0]._xLength, terrainLayersToProcess[0]._yLength, existingAlphaMaps, normalizedHeightfields, strengths, alphaMapIndices); // Update the alphamap resolution to the actual size of the first // heightfield layer used for the alphamaps. // Setting the size before setting the alphamas applies proper scaling. int alphamapResolution = terrainLayersToProcess[0]._xLength; terrainData.alphamapResolution = alphamapResolution; terrainData.SetAlphamaps(0, 0, alphamap); } // Scattering - trees and details HEU_TerrainUtility.ApplyScatterTrees(terrainData, _scatterTrees); HEU_TerrainUtility.ApplyDetailLayers(terrain, terrainData, _detailProperties, detailPrototypes, convertedDetailMaps); // If the layers were writen out, this saves the asset DB. Otherwise user has to save it themselves. // Not 100% sure this is needed, but without this the editor doesn't know the terrain asset has been updated // and therefore doesn't import and show the terrain layer. HEU_AssetDatabase.SaveAssetDatabase(); }
public static Texture2D RenderAndExtractImageToTexture(HEU_SessionBase session, HAPI_MaterialInfo materialInfo, HAPI_ParmId textureParmID, string textureName, string assetCacheFolderPath, bool isNormalMap, bool invertTexture = false) { //HEU_Logger.LogFormat("Rendering texture {0} with name {1} for material {2} at path {3}", textureParmID, textureName, materialInfo.nodeId, assetCacheFolderPath); Texture2D texture = null; // First we get Houdini to render the texture to an image buffer, then query the buffer over HAPI // Next we convert to PNG, and write out to file in our Assets directory // The reason for querying as a buffer is to workaround a bug with ExtractHoudiniImageToTextureFile // Note: intentionly ignoring any errors as sometimes there aren't any textures if (session.RenderTextureToImage(materialInfo.nodeId, textureParmID, false)) { texture = HEU_MaterialFactory.ExtractHoudiniImageToTextureRaw(session, materialInfo, "C A"); if (texture != null) { texture.name = textureName; // Note: Should I make this a plugin option for roughness? if (invertTexture) { Color[] pixels = texture.GetPixels(); for (int i = 0; i < pixels.Length; i++) { pixels[i].r = 1 - pixels[i].r; pixels[i].g = 1 - pixels[i].g; pixels[i].b = 1 - pixels[i].b; } texture.SetPixels(pixels); texture.Apply(); } // Get the Textures folder in the assetCacheFolderPath. Make sure it exists. assetCacheFolderPath = HEU_AssetDatabase.AppendTexturesPathToAssetFolder(assetCacheFolderPath); HEU_AssetDatabase.CreatePathWithFolders(assetCacheFolderPath); // We are defaulting to PNG here if no extension already set. This forces it to use PNG format below. if (!textureName.EndsWith(".png") && !textureName.EndsWith(".jpg") && !textureName.EndsWith(".tga") && !textureName.EndsWith(".exr")) { textureName = textureName + ".png"; } string textureFileName = HEU_Platform.BuildPath(assetCacheFolderPath, string.Format("{0}", textureName)); byte[] encodedBytes; if (textureName.EndsWith(".jpg")) { encodedBytes = texture.EncodeToJPG(); } else if (textureName.EndsWith(".tga")) { encodedBytes = texture.EncodeToTGA(); } else if (textureName.EndsWith(".exr")) { encodedBytes = texture.EncodeToEXR(); } else // Use PNG otherwise { encodedBytes = texture.EncodeToPNG(); } HEU_Platform.WriteBytes(textureFileName, encodedBytes); // Re-import for project to recognize the new texture file HEU_AssetDatabase.ImportAsset(textureFileName, HEU_AssetDatabase.HEU_ImportAssetOptions.Default); if (isNormalMap) { HEU_EditorUtility.SetTextureToNormalMap(textureFileName); } // Load the new texture file texture = HEU_AssetDatabase.LoadAssetAtPath(textureFileName, typeof(Texture2D)) as Texture2D; } //texture = HEU_MaterialFactory.ExtractHoudiniImageToTextureFile(session, materialInfo, "C A", assetCacheFolderPath); } return texture; }
/// <summary> /// Given relative path to an asset (with Assets/ or Packages/ in the path), this returns the full path to it. /// </summary> /// <param name="inPath">Relative path to parse</param> /// <returns>Returns full path to asset, or null if invalid input path</returns> public static string GetAssetFullPath(string inPath) { return HEU_Platform.GetFullPath(inPath); }
public static bool LoadToolFromJsonString(string json) { // Get environment variable for tool path string envValue = HEU_Platform.GetEnvironmentValue(HEU_Defines.HEU_PATH_KEY_TOOL); string envKey = string.Format("<{0}>", HEU_Defines.HEU_PATH_KEY_TOOL); if (!string.IsNullOrEmpty(json)) { HEU_ShelfToolData toolData = JsonUtility.FromJson<HEU_ShelfToolData>(json); if(toolData != null && !string.IsNullOrEmpty(toolData.name) && !string.IsNullOrEmpty(toolData.assetPath) && !string.IsNullOrEmpty(toolData.iconPath)) { // Make sure this tool targets Unity (must have "all" or "unity" set in target field) bool bCompatiple = false; if(toolData.target != null) { int numTargets = toolData.target.Length; for(int i = 0; i < numTargets; ++i) { if (toolData.target[i].Equals(TARGET_ALL)) { bCompatiple = true; break; } else if(toolData.target[i].Equals(TARGET_UNITY)) { bCompatiple = true; break; } } } if (bCompatiple) { toolData.assetPath = toolData.assetPath.Replace(HEU_Defines.HEU_PATH_KEY_PROJECT + "/", ""); toolData.iconPath = toolData.iconPath.Replace(HEU_Defines.HEU_PATH_KEY_PROJECT + "/", ""); if(toolData.assetPath.Contains(envKey)) { if(string.IsNullOrEmpty(envValue)) { Debug.LogErrorFormat("Environment value {0} used but not set in environment.", HEU_Defines.HEU_PATH_KEY_TOOL); } else { toolData.assetPath = toolData.assetPath.Replace(envKey, envValue); } } if (toolData.iconPath.Contains(envKey)) { if (string.IsNullOrEmpty(envValue)) { Debug.LogErrorFormat("Environment value {0} used but not set in environment.", HEU_Defines.HEU_PATH_KEY_TOOL); } else { toolData.iconPath = toolData.iconPath.Replace(envKey, envValue); } } _tools.Add(toolData); Debug.LogFormat("Added tool: {0}", toolData.name); } } return true; } return false; }
/// <summary> /// Create the given object inside the asset cache folder path, with relative folder path. /// Depending on type, it might store in a subfolder for organizational purposes. /// </summary> /// <param name="objectToCreate">The object to create inside the asset cache</param> /// <param name="assetCacheRoot">The target path in the asset cache</param> /// <param name="relativeFolderPath">If not null or empty, the relative path to append to the assetCacheRoot. /// Otherwise uses type of asset to get subfolder name.</param> /// <param name="assetFileName">The asset's file name</param> /// <param name="type">The type of asset</param> /// <param name="bOverwriteExisting">Whether or not to overwrite if there is an existing file</param> public static void CreateObjectInAssetCacheFolder(Object objectToCreate, string assetCacheRoot, string relativeFolderPath, string assetFileName, System.Type type, bool bOverwriteExisting) { #if UNITY_EDITOR Debug.Assert(!string.IsNullOrEmpty(assetCacheRoot), "Must give valid assetCacheFolderPath to create object at"); string subFolderPath = assetCacheRoot; if (!string.IsNullOrEmpty(relativeFolderPath)) { subFolderPath = HEU_Platform.BuildPath(subFolderPath, relativeFolderPath); } else { if (type == typeof(Mesh)) { subFolderPath = AppendMeshesPathToAssetFolder(assetCacheRoot); } else if (type == typeof(Material)) { subFolderPath = AppendMaterialsPathToAssetFolder(assetCacheRoot); } else if (type == typeof(TerrainData) #if UNITY_2018_3_OR_NEWER || (type == typeof(TerrainLayer)) #else || (type == typeof(SplatPrototype)) #endif ) { subFolderPath = AppendTerrainPathToAssetFolder(assetCacheRoot); } } // Make sure subfolders exist HEU_AssetDatabase.CreatePathWithFolders(subFolderPath); // Add file name string finalAssetPath = HEU_Platform.BuildPath(subFolderPath, assetFileName); if (HEU_Platform.DoesFileExist(finalAssetPath) && !bOverwriteExisting) { finalAssetPath = AssetDatabase.GenerateUniqueAssetPath(finalAssetPath); } if (AssetDatabase.Contains(objectToCreate)) { AssetDatabase.SaveAssets(); } else { AssetDatabase.CreateAsset(objectToCreate, finalAssetPath); } // Commented out AssetDatabase.Refresh() below because its slow and seems to be unnecessary. // Leaving it commented in case need to revisit due to problems with asset creation. //RefreshAssetDatabase(); #else // TODO RUNTIME: AssetDatabase is not supported at runtime. Do we need to support this for runtime? HEU_Logger.LogWarning(HEU_Defines.HEU_USERMSG_NONEDITOR_NOT_SUPPORTED); #endif }
/// <summary> /// Returns true if file or folder at inPath exists. Supports inPath with environment mapped values /// such as <HFS> or $. /// </summary> /// <param name="inPath">Path to check. Could be either file or folder.</param> /// <returns>True if file or folder exists</returns> public static bool DoesMappedPathExist(string inPath) { string realPath = HEU_PluginStorage.Instance.ConvertEnvKeyedPathToReal(inPath); return(HEU_Platform.DoesPathExist(realPath)); }
public static string AppendTerrainPathToAssetFolder(string inAssetCacheFolder) { return HEU_Platform.BuildPath(inAssetCacheFolder, "Terrain"); }
public void OnGUI() { if (!_initializedUI) { // Creating of UI elements must happen in OnGUI InitializeUIElements(); } bool bChanged = false; Color originalBGColor = GUI.backgroundColor; bool bRequiresLoad = !HEU_ShelfTools.AreShelvesLoaded(); if(!bRequiresLoad) { // Sanity check that textures are still valid. When scene changes, these get invalidated. if (_guiContents != null && _guiContents.Length > 0) { bRequiresLoad = (_guiContents[0].image == null); } } if(bRequiresLoad) { LoadShelves(); } int numTools = 0; using (new EditorGUILayout.VerticalScope()) { using (new EditorGUILayout.VerticalScope(EditorStyles.helpBox)) { if (HEU_ShelfTools.AreShelvesLoaded()) { int currentShelfIndex = HEU_ShelfTools.GetCurrentShelfIndex(); HEU_Shelf shelf = null; using (new EditorGUILayout.HorizontalScope()) { GUILayout.FlexibleSpace(); if (GUILayout.Button(_addButton, _buttonStyle, GUILayout.MaxWidth(_buttonWidth), GUILayout.MaxHeight(_buttonHeight))) { string newShelfPath = UnityEditor.EditorUtility.OpenFolderPanel("Add Shelf Folder", "", ""); if (!string.IsNullOrEmpty(newShelfPath) && HEU_Platform.DoesDirectoryExist(newShelfPath)) { AddNewShelfWindow(newShelfPath); bChanged = true; } } } using (new EditorGUILayout.HorizontalScope()) { GUILayout.Label("Active Shelf"); int newShelfIndex = EditorGUILayout.Popup(currentShelfIndex, _shelfNames, _popupStyle); if (currentShelfIndex != newShelfIndex) { // Change shelf currentShelfIndex = newShelfIndex; HEU_ShelfTools.SetCurrentShelf(currentShelfIndex); SelectShelf(currentShelfIndex); } shelf = HEU_ShelfTools.GetShelf(currentShelfIndex); numTools = shelf._tools.Count; using (new EditorGUI.DisabledGroupScope(shelf._defaultShelf)) { if (GUILayout.Button(_removeButton, _buttonStyle, GUILayout.MaxWidth(_buttonWidth))) { HEU_ShelfTools.RemoveShelf(currentShelfIndex); HEU_ShelfTools.SaveShelf(); HEU_ShelfTools.SetReloadShelves(); bChanged = true; } } } HEU_EditorUI.DrawSeparator(); if (!bChanged) { using (EditorGUILayout.ScrollViewScope scroll = new EditorGUILayout.ScrollViewScope(_toolButtonScrollPos)) { if (numTools > 0) { int numXElements = numTools < _toolGridXElements ? numTools : _toolGridXElements; _selectedToolIndex = GUILayout.SelectionGrid(_selectedToolIndex, _guiContents, numXElements, _toolGridStyle); } else { EditorGUILayout.LabelField("No tools found!"); } _toolButtonScrollPos = scroll.scrollPosition; } } } } bool bValidSelection = (_selectedToolIndex >= 0 && _selectedToolIndex < numTools); using (new EditorGUI.DisabledGroupScope(!bValidSelection)) { if(!bValidSelection) { _applyButton.text = "Select a Tool!"; } else { GameObject[] selectedObjects = HEU_EditorUtility.GetSelectedObjects(); if(selectedObjects.Length == 0) { _applyButton.text = "Create Tool (no input selected)!"; } else { _applyButton.text = "Create Tool (selected objects as inputs)!"; } } if (GUILayout.Button(_applyButton, _buttonStyle, GUILayout.MaxHeight(_buttonHeight))) { ProcessUserSelection(_selectedToolIndex); } } } }
/// <summary> /// Load the saved plugin settings from disk. /// </summary> /// <returns>True if successfully loaded.</returns> public bool LoadPluginData() { // First check if settings pref file exists string settingsFilePath = SettingsFilePath(); if (!HEU_Platform.DoesFileExist(settingsFilePath)) { // Try reading from EditorPrefs to see if this is still using the old method return ReadFromEditorPrefs(); } // Open file and read each line to create the settings entry using (StreamReader reader = new StreamReader(settingsFilePath)) { // Must match first line string line = reader.ReadLine(); if (string.IsNullOrEmpty(line) || !line.Equals(PluginSettingsLine1)) { Debug.LogWarningFormat("Unable to load Plugin settings file. {0} should have line 1: {1}", settingsFilePath, PluginSettingsLine1); return false; } // Must match 2nd line line = reader.ReadLine(); if (string.IsNullOrEmpty(line) || !line.StartsWith(PluginSettingsLine2)) { Debug.LogWarningFormat("Unable to load Plugin settings file. {0} should start line 2 with: {1}", settingsFilePath, PluginSettingsLine2); return false; } Dictionary<string, StoreData> storeMap = new Dictionary<string, StoreData>(); string keyStr; string typeStr; string valueStr; DataType dataType; // "key(type)=value" System.Text.RegularExpressions.Regex regex = new System.Text.RegularExpressions.Regex(@"^(\w+)\((\w+)\)=(.*)"); while ((line = reader.ReadLine()) != null) { System.Text.RegularExpressions.Match match = regex.Match(line); if (match.Success && match.Groups.Count >= 4) { keyStr = match.Groups[1].Value; typeStr = match.Groups[2].Value; valueStr = match.Groups[3].Value; if (!string.IsNullOrEmpty(keyStr) && !string.IsNullOrEmpty(typeStr) && !string.IsNullOrEmpty(valueStr)) { try { dataType = (DataType)System.Enum.Parse(typeof(DataType), typeStr); StoreData store = new StoreData(); store._type = dataType; store._valueStr = valueStr; storeMap.Add(keyStr, store); } catch (System.Exception ex) { Debug.LogErrorFormat("Invalid data type found in settings: {0}. Exception: {1}", typeStr, ex.ToString()); } } } } _dataMap = storeMap; } return true; }
/// <summary> /// Loads a copy of the srcAsset, or if copy is not found, creates the copy and loads it. /// The copy is expected to be located at newAssetFolderPath/relativePath. /// If relativePath is null or empty, uses the srcAsset type to acquire the subfolder if the type requires it. /// </summary> /// <param name="srcAsset">Source asset whose copy will be loaded (and created if no copy exists).</param> /// <param name="copyAssetFolder">Asset's root folder to look for the copy or create in</param> /// <param name="relativePath">If not null or empty, the relative path to append to the newAssetFolderPath. /// Otherwise uses type of asset to subfolder name.</param> /// <param name="type">Type of asset</param> /// <returns>Returns loaded copy if exists or created, otherwise null</returns> public static Object CopyAndLoadAssetWithRelativePath(Object srcAsset, string copyAssetFolder, string relativePath, System.Type type, bool bOverwriteExisting) { #if UNITY_EDITOR string srcAssetPath = GetAssetPath(srcAsset); if (!string.IsNullOrEmpty(srcAssetPath) && IsPathInAssetCache(srcAssetPath)) { string copyAssetFullPath = copyAssetFolder; if (!string.IsNullOrEmpty(relativePath)) { copyAssetFullPath = HEU_Platform.BuildPath(copyAssetFullPath, relativePath); } else { if (type == typeof(Material)) { copyAssetFullPath = AppendMaterialsPathToAssetFolder(copyAssetFolder); } else if (type == typeof(Texture)) { copyAssetFullPath = AppendTexturesPathToAssetFolder(copyAssetFolder); } else if (type == typeof(Mesh)) { copyAssetFullPath = AppendMeshesPathToAssetFolder(copyAssetFolder); } else if (type == typeof(TerrainData) #if UNITY_2018_3_OR_NEWER || (type == typeof(TerrainLayer)) #else || (type == typeof(SplatPrototype)) #endif ) { copyAssetFullPath = AppendTerrainPathToAssetFolder(copyAssetFolder); } } CreatePathWithFolders(copyAssetFullPath); string fileName = HEU_Platform.GetFileName(srcAssetPath); string newAssetPath = HEU_Platform.BuildPath(copyAssetFullPath, fileName); if ((!bOverwriteExisting && HEU_Platform.DoesFileExist(newAssetPath)) || CopyAsset(srcAssetPath, newAssetPath)) { // Refresh database as otherwise we won't be able to load it in the next line. SaveAndRefreshDatabase(); return LoadAssetAtPath(newAssetPath, type); } else { Debug.LogErrorFormat("Failed to copy and load asset from {0} to {1}!", srcAssetPath, newAssetPath); } } return null; #else // TODO RUNTIME: AssetDatabase is not supported at runtime. Do we need to support this for runtime? Debug.LogWarning(HEU_Defines.HEU_USERMSG_NONEDITOR_NOT_SUPPORTED); return null; #endif }
/// <summary> /// Return Houdini Engine installation and session information. /// Tries to use existing or creates new session to find information. /// </summary> /// <returns>String containing installation and session information.</returns> public static string GetHoudiniEngineInstallationInfo() { #if HOUDINIENGINEUNITY_ENABLED StringBuilder sb = new StringBuilder(); sb.AppendFormat("Required Houdini Version: {0}.{1}.{2}\nRequired Houdini Engine Version: {3}.{4}.{5}\n\n", HEU_HoudiniVersion.HOUDINI_MAJOR, HEU_HoudiniVersion.HOUDINI_MINOR, HEU_HoudiniVersion.HOUDINI_BUILD, HEU_HoudiniVersion.HOUDINI_ENGINE_MAJOR, HEU_HoudiniVersion.HOUDINI_ENGINE_MINOR, HEU_HoudiniVersion.HOUDINI_ENGINE_API); // Check if existing session is valid, or create a new session. Then query installation information. HEU_SessionBase session = HEU_SessionManager.GetDefaultSession(); if (session != null && session.IsSessionValid()) { int hMajor = session.GetEnvInt(HAPI_EnvIntType.HAPI_ENVINT_VERSION_HOUDINI_MAJOR); int hMinor = session.GetEnvInt(HAPI_EnvIntType.HAPI_ENVINT_VERSION_HOUDINI_MINOR); int hBuild = session.GetEnvInt(HAPI_EnvIntType.HAPI_ENVINT_VERSION_HOUDINI_BUILD); int heuPatch = session.GetEnvInt(HAPI_EnvIntType.HAPI_ENVINT_VERSION_HOUDINI_PATCH); int heuMajor = session.GetEnvInt(HAPI_EnvIntType.HAPI_ENVINT_VERSION_HOUDINI_ENGINE_MAJOR); int heuMinor = session.GetEnvInt(HAPI_EnvIntType.HAPI_ENVINT_VERSION_HOUDINI_ENGINE_MINOR); int heuAPI = session.GetEnvInt(HAPI_EnvIntType.HAPI_ENVINT_VERSION_HOUDINI_ENGINE_API); sb.AppendFormat("Installed Houdini Version: {0}.{1}.{2}{3}\n", hMajor, hMinor, hBuild, (heuPatch > 0) ? "." + heuPatch.ToString() : ""); sb.AppendFormat("Installed Houdini Engine Version: {0}.{1}.{2}\n\n", heuMajor, heuMinor, heuAPI); sb.AppendFormat("Houdini Binaries Path: {0}\n", HEU_Platform.GetHoudiniEnginePath() + HEU_HoudiniVersion.HAPI_BIN_PATH); sb.AppendFormat("Unity Plugin Version: {0}\n\n", HEU_HoudiniVersion.UNITY_PLUGIN_VERSION); HEU_SessionData sessionData = session.GetSessionData(); if (sessionData != null) { sb.AppendFormat("Session ID: {0}\n", sessionData.SessionID); sb.AppendFormat("Session Type: {0}\n", sessionData.SessionType); sb.AppendFormat("Process ID: {0}\n", sessionData.ProcessID); if (sessionData.SessionType == HAPI_SessionType.HAPI_SESSION_THRIFT) { sb.AppendFormat("Pipe name: {0}\n", sessionData.PipeName); } sb.AppendLine(); } sb.Append("License Type Acquired: "); HAPI_License license = HEU_SessionManager.GetCurrentLicense(false); switch (license) { case HAPI_License.HAPI_LICENSE_NONE: sb.Append("None\n"); break; case HAPI_License.HAPI_LICENSE_HOUDINI_ENGINE: sb.Append("Houdini Engine\n"); break; case HAPI_License.HAPI_LICENSE_HOUDINI: sb.Append("Houdini (Escape)\n"); break; case HAPI_License.HAPI_LICENSE_HOUDINI_FX: sb.Append("Houdini FX\n"); break; case HAPI_License.HAPI_LICENSE_HOUDINI_ENGINE_INDIE: sb.Append("Houdini Engine Indie"); break; case HAPI_License.HAPI_LICENSE_HOUDINI_INDIE: sb.Append("Houdini Indie\n"); break; default: sb.Append("Unknown\n"); break; } } else // Unable to establish a session { sb.AppendLine("Unable to detect Houdini Engine installation."); sb.AppendLine("License Type Acquired: Unknown\n"); if (session != null) { sb.AppendLine("Failure possibly due to: " + session.GetLastSessionError()); } } sb.AppendLine(); sb.Append("PATH: \n" + GetEnvironmentPath()); Debug.Log(sb.ToString()); return(sb.ToString()); #else return(""); #endif }
public static string AppendTexturesPathToAssetFolder(string inAssetCacheFolder) { return HEU_Platform.BuildPath(inAssetCacheFolder, HEU_Defines.HEU_FOLDER_TEXTURES); }
public static void LoadShelves() { bool bSaveShelf = false; _shelves.Clear(); // Always add the default shelf HEU_Shelf defaultShelf = AddShelf(HEU_Defines.HEU_HENGINE_SHIPPED_SHELF, HEU_Defines.HEU_HENGINE_TOOLS_SHIPPED_FOLDER); if (defaultShelf == null) { return; } defaultShelf._defaultShelf = true; List<string> shelfEntries = HEU_PluginSettings.HEngineToolsShelves; if (shelfEntries == null || shelfEntries.Count == 0) { shelfEntries = new List<string>(); } // Convert shelf path + name to actual shelf objects int numShelves = shelfEntries.Count; for (int i = 0; i < numShelves; i++) { string shelfName = ""; string shelfPath = ""; GetSplitShelfEntry(shelfEntries[i], out shelfName, out shelfPath); // Ignore default shelf because we added it already if (shelfPath.Equals(HEU_Defines.HEU_HENGINE_TOOLS_SHIPPED_FOLDER)) { continue; } if (!string.IsNullOrEmpty(shelfName) && !string.IsNullOrEmpty(shelfPath)) { HEU_Shelf newShelf = new HEU_Shelf(); newShelf._shelfName = shelfName; newShelf._shelfPath = shelfPath; _shelves.Add(newShelf); } else { HEU_Logger.LogWarningFormat("Found invalid shelf with entry: {0}", shelfEntries[i]); shelfEntries.RemoveAt(i); i--; bSaveShelf = true; } } foreach (HEU_Shelf shelf in _shelves) { string realShelfPath = HEU_HAPIUtility.GetRealPathFromHFSPath(shelf._shelfPath); if (!HEU_Platform.DoesPathExist(realShelfPath)) { HEU_Logger.LogWarningFormat("Shelf path does not exist: {0}", realShelfPath); } else { bool bShelfLoaded = LoadToolsFromDirectory(realShelfPath, out shelf._tools); if (!bShelfLoaded) { HEU_Logger.LogWarningFormat("Failed to load shelf {0} at path {1}", shelf._shelfName, realShelfPath); } } } _currentSelectedShelf = HEU_PluginSettings.HEngineShelfSelectedIndex; if (_currentSelectedShelf < 0 || _currentSelectedShelf >= _shelves.Count) { _currentSelectedShelf = 0; HEU_PluginSettings.HEngineShelfSelectedIndex = _currentSelectedShelf; } if (bSaveShelf) { SaveShelf(); } _shelvesLoaded = true; }
public static string AppendTerrainPathToAssetFolder(string inAssetCacheFolder) { return HEU_Platform.BuildPath(inAssetCacheFolder, HEU_Defines.HEU_FOLDER_TERRAIN); }
public static HEU_ShelfToolData LoadToolFromJsonString(string json, string jsonFilePath) { //HEU_Logger.Log("Loading json: " + jsonFilePath); // Get environment variable for tool path string envValue = HEU_Platform.GetEnvironmentValue(HEU_Defines.HEU_PATH_KEY_TOOL); string envKey = string.Format("<{0}>", HEU_Defines.HEU_PATH_KEY_TOOL); HEU_ShelfToolData toolData = null; if (!string.IsNullOrEmpty(json)) { try { JSONNode jsonShelfNode = JSON.Parse(json); if (jsonShelfNode != null) { bool isObject = jsonShelfNode.IsObject; bool isArray = jsonShelfNode.IsArray; toolData = new HEU_ShelfToolData(); toolData._name = jsonShelfNode["name"]; toolData._toolType = (HEU_ShelfToolData.ToolType)System.Enum.Parse(typeof(HEU_ShelfToolData.ToolType), jsonShelfNode["toolType"]); toolData._toolTip = jsonShelfNode["toolTip"]; toolData._iconPath = jsonShelfNode["iconPath"]; toolData._assetPath = jsonShelfNode["assetPath"]; toolData._helpURL = jsonShelfNode["helpURL"]; JSONArray targetArray = jsonShelfNode["target"].AsArray; if (targetArray != null) { int targetCount = targetArray.Count; toolData._targets = new string[targetCount]; for (int j = 0; j < targetCount; ++j) { toolData._targets[j] = targetArray[j]; } } } } catch (System.Exception ex) { HEU_Logger.LogErrorFormat("Exception when trying to parse shelf json file at path: {0}. Exception: {1}", jsonFilePath, ex.ToString()); return null; } toolData._jsonPath = jsonFilePath; if (toolData != null && !string.IsNullOrEmpty(toolData._name)) { // Make sure this tool targets Unity (must have "all" or "unity" set in target field) bool bCompatiple = false; if (toolData._targets != null) { int numTargets = toolData._targets.Length; for (int i = 0; i < numTargets; ++i) { if (toolData._targets[i].Equals(TARGET_ALL) || toolData._targets[i].Equals(TARGET_UNITY)) { bCompatiple = true; break; } } } if (bCompatiple) { if (!string.IsNullOrEmpty(toolData._assetPath)) { toolData._assetPath = toolData._assetPath.Replace(HEU_Defines.HEU_PATH_KEY_PROJECT + "/", ""); if (toolData._assetPath.Contains(envKey)) { if (string.IsNullOrEmpty(envValue)) { HEU_Logger.LogErrorFormat("Environment value {0} used but not set in environment.", HEU_Defines.HEU_PATH_KEY_TOOL); } else { toolData._assetPath = toolData._assetPath.Replace(envKey, envValue); } } } else { toolData._assetPath = GetToolAssetPath(toolData, toolData._assetPath); } string realPath = HEU_PluginStorage.Instance.ConvertEnvKeyedPathToReal(toolData._assetPath); if (!HEU_Platform.DoesFileExist(realPath)) { HEU_Logger.LogErrorFormat("Houdini Engine shelf tool at {0} does not exist!", realPath); return null; } if (!string.IsNullOrEmpty(toolData._iconPath)) { toolData._iconPath = toolData._iconPath.Replace(HEU_Defines.HEU_PATH_KEY_PROJECT + "/", ""); if (toolData._iconPath.Contains(envKey)) { if (string.IsNullOrEmpty(envValue)) { HEU_Logger.LogErrorFormat("Environment value {0} used but not set in environment.", HEU_Defines.HEU_PATH_KEY_TOOL); } else { toolData._iconPath = toolData._iconPath.Replace(envKey, envValue); } } } else { toolData._iconPath = GetToolIconPath(toolData, toolData._iconPath); } return toolData; } } } return null; }
/// <summary> /// Do the geometry loading in Houdini in a thread. /// Creates a file node, loads the bgeo, then retrives the geometry into local buffers. /// </summary> protected override void DoWork() { _loadData._loadStatus = HEU_LoadData.LoadStatus.STARTED; //Debug.LogFormat("DoWork: Loading {0}", _filePath); if (_session == null || !_session.IsSessionValid()) { SetLog(HEU_LoadData.LoadStatus.ERROR, "Invalid session!"); return; } // Check file path if (!HEU_Platform.DoesPathExist(_filePath)) { SetLog(HEU_LoadData.LoadStatus.ERROR, string.Format("File not found at {0}", _filePath)); return; } #if true // Make sure file type is supported //if (!_filePath.EndsWith(".bgeo") && !_filePath.EndsWith(".bgeo.sc")) //{ // SetLog(HEU_LoadData.LoadStatus.ERROR, string.Format("Only .bgeo or .bgeo.sc files are supported.")); // return; //} // Create file SOP if (_loadData._fileNodeID == HEU_Defines.HEU_INVALID_NODE_ID) { if (!CreateFileNode(out _loadData._fileNodeID)) { SetLog(HEU_LoadData.LoadStatus.ERROR, string.Format("Unable to create file node in Houdini.")); return; } } Sleep(); HAPI_NodeId displayNodeID = GetDisplayNodeID(_loadData._fileNodeID); if (displayNodeID == HEU_Defines.HEU_INVALID_NODE_ID) { SetLog(HEU_LoadData.LoadStatus.ERROR, string.Format("Unable to get display node of file geo node.")); return; } // Set the file parameter if (!SetFileParm(displayNodeID, _filePath)) { SetLog(HEU_LoadData.LoadStatus.ERROR, string.Format("Unable to set file path parm.")); return; } // Cooking it will load the bgeo if (!_session.CookNode(_loadData._fileNodeID, false)) { SetLog(HEU_LoadData.LoadStatus.ERROR, string.Format("Unable to cook node.")); return; } // Wait until cooking has finished bool bResult = true; HAPI_State statusCode = HAPI_State.HAPI_STATE_STARTING_LOAD; while (bResult && statusCode > HAPI_State.HAPI_STATE_MAX_READY_STATE) { bResult = _session.GetStatus(HAPI_StatusType.HAPI_STATUS_COOK_STATE, out statusCode); Sleep(); } // Check cook results for any errors if (statusCode == HAPI_State.HAPI_STATE_READY_WITH_COOK_ERRORS || statusCode == HAPI_State.HAPI_STATE_READY_WITH_FATAL_ERRORS) { string statusString = _session.GetStatusString(HAPI_StatusType.HAPI_STATUS_COOK_RESULT, HAPI_StatusVerbosity.HAPI_STATUSVERBOSITY_ERRORS); SetLog(HEU_LoadData.LoadStatus.ERROR, string.Format("Cook failed: {0}.", statusString)); return; } #else // Load the file using HAPI_LoadGeoFromFile if (_loadData._fileNodeID == HEU_Defines.HEU_INVALID_NODE_ID) { Debug.Log("Creating file node with path: " + _filePath); if (!_session.CreateNode(-1, "SOP/file", "loadfile", true, out _loadData._fileNodeID)) { SetLog(HEU_LoadData.LoadStatus.ERROR, string.Format("Unable to create geo SOP in Houdini.")); return; } if (!_session.LoadGeoFromFile(_loadData._fileNodeID, _filePath)) { SetLog(HEU_LoadData.LoadStatus.ERROR, string.Format("Unable to create file node in Houdini.")); return; } } HAPI_NodeId displayNodeID = GetDisplayNodeID(_loadData._fileNodeID); #endif // Note that object instancing is not supported. Instancers currently supported are // part and point instancing. // Get the various types of geometry (parts) from the display node List<HAPI_PartInfo> meshParts = new List<HAPI_PartInfo>(); List<HAPI_PartInfo> volumeParts = new List<HAPI_PartInfo>(); List<HAPI_PartInfo> instancerParts = new List<HAPI_PartInfo>(); List<HAPI_PartInfo> curveParts = new List<HAPI_PartInfo>(); if (!QueryParts(displayNodeID, ref meshParts, ref volumeParts, ref instancerParts, ref curveParts)) { SetLog(HEU_LoadData.LoadStatus.ERROR, string.Format("Unable to query parts on node.")); return; } Sleep(); // Create Unity mesh buffers if (!GenerateMeshBuffers(_session, displayNodeID, meshParts, _generateOptions._splitPoints, _generateOptions._useLODGroups, _generateOptions._generateUVs, _generateOptions._generateTangents, _generateOptions._generateNormals, out _loadData._meshBuffers)) { SetLog(HEU_LoadData.LoadStatus.ERROR, string.Format("Unable to generate mesh data from parts.")); return; } // Create Unity terrain buffers if (!GenerateTerrainBuffers(_session, displayNodeID, volumeParts, out _loadData._terrainBuffers)) { SetLog(HEU_LoadData.LoadStatus.ERROR, string.Format("Unable to generate terrain data from volume parts.")); return; } // Create instancers (should come after normal geometry has been generated above) if (!GenerateInstancerBuffers(_session, displayNodeID, instancerParts, out _loadData._instancerBuffers)) { SetLog(HEU_LoadData.LoadStatus.ERROR, string.Format("Unable to generate data from instancer parts.")); return; } SetLog(HEU_LoadData.LoadStatus.SUCCESS, "Completed!"); }
private bool DrawDetailsGeneral() { bool bChanged = false; { string oldPath = HEU_PluginSettings.AssetCachePath; EditorGUILayout.LabelField(new GUIContent("Houdini Asset Cache Path:", "Files generated by this plugin will be stored in this folder path relative to Assets/.")); string newPath = EditorGUILayout.TextField("", oldPath); if (!newPath.Equals(oldPath)) { HEU_PluginSettings.AssetCachePath = newPath; bChanged = true; } } HEU_EditorUI.DrawSeparator(); { string oldPath = HEU_PluginSettings.HoudiniInstallPath; string fileExt = ""; EditorGUILayout.LabelField(new GUIContent("Override Houdini Install Path:", "Set a specific Houdini installation to use for this plugin. The plugin's default version of Houdini will be ignored.")); using (new EditorGUILayout.HorizontalScope()) { string newPath = EditorGUILayout.DelayedTextField(oldPath); GUIStyle buttonStyle = HEU_EditorUI.GetNewButtonStyle_MarginPadding(0, 0); if (GUILayout.Button("...", buttonStyle, GUILayout.Width(30), GUILayout.Height(18))) { string panelMsg = "Select Houdini Install Path"; #if UNITY_EDITOR_OSX panelMsg += " (.app)"; #endif string openFilePath = UnityEditor.EditorUtility.OpenFolderPanel(panelMsg, newPath, fileExt); if (!string.IsNullOrEmpty(openFilePath)) { newPath = openFilePath; } } if (!newPath.Equals(oldPath)) { string msgPath = !string.IsNullOrEmpty(newPath) ? newPath : HEU_Platform.GetHoudiniEngineDefaultPath(); string confirmMsg = string.Format( "Change the Houdini install path?\n" + " New path: {0}\n\n" + "You will need to restart Unity to use this path!", msgPath); bool result = HEU_EditorUtility.DisplayDialog("Houdini Install Path Changed", confirmMsg, "Confirm", "Cancel"); if (result) { HEU_PluginSettings.HoudiniInstallPath = newPath; bChanged = true; } } } #if UNITY_EDITOR_OSX GUIStyle labelStyle = new GUIStyle(GUI.skin.label); labelStyle.wordWrap = true; EditorGUILayout.LabelField(" On macOS, you'll need to select the path to the .app folder.\n E.g. /Applications/Houdini/Houdini16.5.616/Houdini Core 16.5.616.app", labelStyle); #endif } HEU_EditorUI.DrawSeparator(); { string oldPath = HEU_PluginSettings.HoudiniDebugLaunchPath; string fileExt = ""; EditorGUILayout.LabelField(new GUIContent("Houdini Debug Executable:", "Set Houdini executable to launch when opening debug scenes.")); using (new EditorGUILayout.HorizontalScope()) { string newPath = EditorGUILayout.DelayedTextField(oldPath); GUIStyle buttonStyle = HEU_EditorUI.GetNewButtonStyle_MarginPadding(0, 0); if (GUILayout.Button("...", buttonStyle, GUILayout.Width(30), GUILayout.Height(18))) { string panelMsg = "Select Houdini Executable"; #if UNITY_EDITOR_OSX panelMsg += " (.app)"; #endif string openFilePath = UnityEditor.EditorUtility.OpenFilePanel(panelMsg, newPath, fileExt); if (!string.IsNullOrEmpty(openFilePath)) { newPath = openFilePath; } } if (!newPath.Equals(oldPath)) { HEU_PluginSettings.HoudiniDebugLaunchPath = newPath; bChanged = true; } } #if UNITY_EDITOR_OSX GUIStyle labelStyle = new GUIStyle(GUI.skin.label); labelStyle.wordWrap = true; EditorGUILayout.LabelField(" On macOS, you'll need to select the path to the .app folder.\n E.g. /Applications/Houdini/Houdini16.5.616/Houdini Core 16.5.616.app", labelStyle); #endif } HEU_EditorUI.DrawSeparator(); { bool oldValue = HEU_PluginSettings.UseFullPathNamesForOutput; bool newValue = HEU_EditorUI.DrawToggleLeft(oldValue, "Use Full Path Names For Output"); if (!newValue.Equals(oldValue)) { HEU_PluginSettings.UseFullPathNamesForOutput = newValue; bChanged = true; } } HEU_EditorUI.DrawSeparator(); { bool oldValue = HEU_PluginSettings.SetCurrentThreadToInvariantCulture; bool newValue = HEU_EditorUI.DrawToggleLeft(oldValue, "Set Current Thread To Invariant Culture", "Enabling this sets to use InvariantCutulre which fixes locale-specific parsing issues such as using comma instead of dot for decimals."); if (!newValue.Equals(oldValue)) { HEU_PluginSettings.SetCurrentThreadToInvariantCulture = newValue; bChanged = true; } } return bChanged; }