public static void CopyPrototype(HEU_DetailPrototype srcProto, HEU_DetailPrototype destProto) { destProto._bendFactor = srcProto._bendFactor; destProto._dryColor = srcProto._dryColor; destProto._healthyColor = srcProto._healthyColor; destProto._maxHeight = srcProto._maxHeight; destProto._maxWidth = srcProto._maxWidth; destProto._minHeight = srcProto._minHeight; destProto._minWidth = srcProto._minWidth; destProto._noiseSpread = srcProto._noiseSpread; destProto._renderMode = srcProto._renderMode; }
/// <summary> /// Fill up the given detailPrototype with values from the specified heightfield part. /// </summary> /// <param name="session">Houdini Engine session to query</param> /// <param name="geoID">The geometry ID in Houdini</param> /// <param name="partID">The part ID in Houdini</param> /// <param name="detailPrototype">The detail prototype object to populate</param> public static void PopulateDetailPrototype(HEU_SessionBase session, HAPI_NodeId geoID, HAPI_PartId partID, ref HEU_DetailPrototype detailPrototype) { // Get the detail prototype properties from attributes on this layer if (detailPrototype == null) { detailPrototype = new HEU_DetailPrototype(); } HAPI_AttributeInfo prefabAttrInfo = new HAPI_AttributeInfo(); string[] prefabPaths = HEU_GeneralUtility.GetAttributeStringData(session, geoID, partID, HEU_Defines.HEIGHTFIELD_DETAIL_PROTOTYPE_PREFAB, ref prefabAttrInfo); if (prefabAttrInfo.exists && prefabPaths.Length >= 1) { detailPrototype._prototypePrefab = prefabPaths[0]; } HAPI_AttributeInfo textureAttrInfo = new HAPI_AttributeInfo(); string[] texturePaths = HEU_GeneralUtility.GetAttributeStringData(session, geoID, partID, HEU_Defines.HEIGHTFIELD_DETAIL_PROTOTYPE_TEXTURE, ref textureAttrInfo); if (textureAttrInfo.exists && texturePaths.Length >= 1) { detailPrototype._prototypeTexture = texturePaths[0]; } float fvalue = 0; if (HEU_GeneralUtility.GetAttributeFloatSingle(session, geoID, partID, HEU_Defines.HEIGHTFIELD_DETAIL_PROTOTYPE_BENDFACTOR, out fvalue)) { detailPrototype._bendFactor = fvalue; } Color color = Color.white; if (HEU_GeneralUtility.GetAttributeColorSingle(session, geoID, partID, HEU_Defines.HEIGHTFIELD_DETAIL_PROTOTYPE_DRYCOLOR, ref color)) { detailPrototype._dryColor = color; } if (HEU_GeneralUtility.GetAttributeColorSingle(session, geoID, partID, HEU_Defines.HEIGHTFIELD_DETAIL_PROTOTYPE_HEALTHYCOLOR, ref color)) { detailPrototype._healthyColor = color; } if (HEU_GeneralUtility.GetAttributeFloatSingle(session, geoID, partID, HEU_Defines.HEIGHTFIELD_DETAIL_PROTOTYPE_MAXHEIGHT, out fvalue)) { detailPrototype._maxHeight = fvalue; } if (HEU_GeneralUtility.GetAttributeFloatSingle(session, geoID, partID, HEU_Defines.HEIGHTFIELD_DETAIL_PROTOTYPE_MAXWIDTH, out fvalue)) { detailPrototype._maxWidth = fvalue; } if (HEU_GeneralUtility.GetAttributeFloatSingle(session, geoID, partID, HEU_Defines.HEIGHTFIELD_DETAIL_PROTOTYPE_MINHEIGHT, out fvalue)) { detailPrototype._minHeight = fvalue; } if (HEU_GeneralUtility.GetAttributeFloatSingle(session, geoID, partID, HEU_Defines.HEIGHTFIELD_DETAIL_PROTOTYPE_MINWIDTH, out fvalue)) { detailPrototype._minWidth = fvalue; } if (HEU_GeneralUtility.GetAttributeFloatSingle(session, geoID, partID, HEU_Defines.HEIGHTFIELD_DETAIL_PROTOTYPE_NOISESPREAD, out fvalue)) { detailPrototype._noiseSpread = fvalue; } int iValue = 0; if (HEU_GeneralUtility.GetAttributeIntSingle(session, geoID, partID, HEU_Defines.HEIGHTFIELD_DETAIL_PROTOTYPE_RENDERMODE, out iValue)) { detailPrototype._renderMode = iValue; } }
/// <summary> /// Apply the given detail layers and properties to the given terrain. /// The detail distance and resolution will be set, along with detail prototypes, and layers. /// </summary> /// <param name="terrain">The Terrain to set the detail properies on</param> /// <param name="terrainData">The TerrainData to apply the layers to</param> /// <param name="detailProperties">Container for detail distance and resolution</param> /// <param name="heuDetailPrototypes">Data for creating DetailPrototypes</param> /// <param name="convertedDetailMaps">The detail maps to set for the detail layers</param> public static void ApplyDetailLayers(Terrain terrain, TerrainData terrainData, HEU_DetailProperties detailProperties, List<HEU_DetailPrototype> heuDetailPrototypes, List<int[,]> convertedDetailMaps) { #if UNITY_2018_3_OR_NEWER if (detailProperties != null) { if (detailProperties._detailDistance >= 0) { terrain.detailObjectDistance = detailProperties._detailDistance; } if (detailProperties._detailDensity >= 0) { terrain.detailObjectDensity = detailProperties._detailDensity; } int resPerPath = detailProperties._detailResolutionPerPatch > 0 ? detailProperties._detailResolutionPerPatch : terrainData.detailResolutionPerPatch; int detailRes = detailProperties._detailResolution > 0 ? detailProperties._detailResolution : terrainData.detailResolutionPerPatch; if (resPerPath > 0 && detailRes > 0) { // This should match with half the terrain size terrainData.SetDetailResolution(detailRes, resPerPath); } } if (heuDetailPrototypes.Count != convertedDetailMaps.Count) { Debug.LogError("Number of volume detail layers differs from converted detail maps. Unable to apply detail layers."); return; } // For now, just override existing detail prototypes and layers // If user asks for appending/overwriting them, then can use a new index attribute to map them List<DetailPrototype> detailPrototypes = new List<DetailPrototype>(); int numDetailLayers = heuDetailPrototypes.Count; for(int i = 0; i < numDetailLayers; ++i) { DetailPrototype detailPrototype = new DetailPrototype(); HEU_DetailPrototype heuDetail = heuDetailPrototypes[i]; if (!string.IsNullOrEmpty(heuDetail._prototypePrefab)) { detailPrototype.prototype = HEU_AssetDatabase.LoadAssetAtPath(heuDetail._prototypePrefab, typeof(GameObject)) as GameObject; detailPrototype.usePrototypeMesh = true; } else if (!string.IsNullOrEmpty(heuDetail._prototypeTexture)) { detailPrototype.prototypeTexture = HEU_MaterialFactory.LoadTexture(heuDetail._prototypeTexture); detailPrototype.usePrototypeMesh = false; } detailPrototype.bendFactor = heuDetail._bendFactor; detailPrototype.dryColor = heuDetail._dryColor; detailPrototype.healthyColor = heuDetail._healthyColor; detailPrototype.maxHeight = heuDetail._maxHeight; detailPrototype.maxWidth = heuDetail._maxWidth; detailPrototype.minHeight = heuDetail._minHeight; detailPrototype.minWidth = heuDetail._minWidth; detailPrototype.noiseSpread = heuDetail._noiseSpread; detailPrototype.renderMode = (DetailRenderMode)heuDetail._renderMode; detailPrototypes.Add(detailPrototype); } // Set the DetailPrototypes if (detailPrototypes.Count > 0) { terrainData.detailPrototypes = detailPrototypes.ToArray(); } // Set the DetailLayers for(int i = 0; i < numDetailLayers; ++i) { terrainData.SetDetailLayer(0, 0, i, convertedDetailMaps[i]); } #endif }
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 detailResolution = 0; 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); HFLayerType layerType = HEU_TerrainUtility.GetHeightfieldLayerType(session, nodeID, volumeParts[i].id, volumeName); //Debug.LogFormat("Index: {0}, Part id: {1}, Part Name: {2}, Volume Name: {3}", i, volumeParts[i].id, HEU_SessionManager.GetString(volumeParts[i].nameSH), volumeName); // Ignoring mask layer because it is Houdini-specific (same behaviour as regular HDA terrain generation) if (layerType == HFLayerType.MASK) { continue; } HEU_LoadBufferVolumeLayer layer = new HEU_LoadBufferVolumeLayer(); layer._layerName = volumeName; layer._partID = volumeParts[i].id; layer._heightMapWidth = volumeInfo.xLength; layer._heightMapHeight = volumeInfo.yLength; layer._layerType = layerType; 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); if (layerType != HFLayerType.DETAIL) { layer._hasLayerAttributes = HEU_TerrainUtility.VolumeLayerHasAttributes(session, nodeID, volumeParts[i].id); if (layer._hasLayerAttributes) { 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, (layerType == HFLayerType.HEIGHT)); } // 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 (layerType == HFLayerType.HEIGHT) { // Height layer always first layer volumeBuffer._splatLayers.Insert(0, layer); volumeBuffer._heightMapWidth = layer._heightMapWidth; volumeBuffer._heightMapHeight = layer._heightMapHeight; volumeBuffer._terrainSizeX = layer._terrainSizeX; volumeBuffer._terrainSizeY = layer._terrainSizeY; volumeBuffer._heightRange = layer._heightRange; // The terrain heightfield position in y requires offset of min height layer._position.y += layer._minHeight; // Use y position from attribute if user has set it float userYPos; if (HEU_GeneralUtility.GetAttributeFloatSingle(session, nodeID, volumeParts[i].id, HEU_Defines.DEFAULT_UNITY_HEIGHTFIELD_YPOS, out userYPos)) { layer._position.y = userYPos; } // 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); // Look up TerrainData export file path via attribute if user has set it volumeBuffer._terrainDataExportPath = HEU_GeneralUtility.GetAttributeStringValueSingle(session, nodeID, volumeBuffer._id, HEU_Defines.DEFAULT_UNITY_HEIGHTFIELD_TERRAINDATA_EXPORT_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; } HEU_TerrainUtility.PopulateDetailProperties(session, nodeID, volumeBuffer._id, ref volumeBuffer._detailProperties); // Get specified material if any volumeBuffer._specifiedTerrainMaterialName = HEU_GeneralUtility.GetMaterialAttributeValueFromPart(session, nodeID, volumeBuffer._id); } else if(layer._layerType == HFLayerType.DETAIL) { // Get detail prototype HEU_DetailPrototype detailPrototype = null; HEU_TerrainUtility.PopulateDetailPrototype(session, nodeID, volumeParts[i].id, ref detailPrototype); int[,] detailMap = HEU_TerrainUtility.GetDetailMapFromPart(session, nodeID, volumeParts[i].id, out detailResolution); volumeBuffer._detailPrototypes.Add(detailPrototype); volumeBuffer._detailMaps.Add(detailMap); // Set the detail resolution which is formed from the detail layer if (volumeBuffer._detailProperties == null) { volumeBuffer._detailProperties = new HEU_DetailProperties(); } volumeBuffer._detailProperties._detailResolution = detailResolution; } else { volumeBuffer._splatLayers.Add(layer); } } Sleep(); } // Each volume buffer is a self contained terrain tile foreach(HEU_LoadBufferVolume volumeBuffer in volumeBuffers) { List<HEU_LoadBufferVolumeLayer> layers = volumeBuffer._splatLayers; //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) { // Ignore Detail layers as they are handled differently if(layers[m]._layerType != HFLayerType.DETAIL) { 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; } // TODO: revisit how the position is calculated volumeBuffer._position = new Vector3( volumeBuffer._terrainSizeX + volumeBuffer._splatLayers[0]._minBounds.x, volumeBuffer._splatLayers[0]._position.y, volumeBuffer._splatLayers[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)) { // Try part 0 (the height layer) to get the tile index. // For scatter points merged with HF, in some cases the part ID doesn't have the tile attribute. HEU_GeneralUtility.GetAttribute(session, nodeID, 0, 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.PopulateScatterTrees(session, nodeID, scatterInstancerParts[i].id, scatterInstancerParts[i].pointCount, ref volumeBuffer._scatterTrees); } return true; }