public bool GenerateTerrainBuffers(HEU_SessionBase session, HAPI_NodeId nodeID, List<HAPI_PartInfo> volumeParts, out List<HEU_LoadBufferVolume> volumeBuffers) { volumeBuffers = null; if (volumeParts.Count == 0) { return true; } volumeBuffers = new List<HEU_LoadBufferVolume>(); int numParts = volumeParts.Count; for (int i = 0; i < numParts; ++i) { HAPI_VolumeInfo volumeInfo = new HAPI_VolumeInfo(); bool bResult = session.GetVolumeInfo(nodeID, volumeParts[i].id, ref volumeInfo); if (!bResult || volumeInfo.tupleSize != 1 || volumeInfo.zLength != 1 || volumeInfo.storage != HAPI_StorageType.HAPI_STORAGETYPE_FLOAT) { SetLog(HEU_LoadData.LoadStatus.ERROR, "This heightfield is not supported. Please check documentation."); return false; } if (volumeInfo.xLength != volumeInfo.yLength) { SetLog(HEU_LoadData.LoadStatus.ERROR, "Non-square sized terrain not supported."); return false; } string volumeName = HEU_SessionManager.GetString(volumeInfo.nameSH, session); bool bHeightPart = volumeName.Equals("height"); //Debug.LogFormat("Part name: {0}, GeoName: {1}, Volume Name: {2}, Display: {3}", part.PartName, geoNode.GeoName, volumeName, geoNode.Displayable); HEU_LoadBufferVolumeLayer layer = new HEU_LoadBufferVolumeLayer(); layer._layerName = volumeName; layer._partID = volumeParts[i].id; layer._heightMapSize = volumeInfo.xLength; Matrix4x4 volumeTransformMatrix = HEU_HAPIUtility.GetMatrixFromHAPITransform(ref volumeInfo.transform, false); layer._position = HEU_HAPIUtility.GetPosition(ref volumeTransformMatrix); Vector3 scale = HEU_HAPIUtility.GetScale(ref volumeTransformMatrix); // Calculate real terrain size in both Houdini and Unity. // The height values will be mapped over this terrain size. float gridSpacingX = scale.x * 2f; float gridSpacingY = scale.y * 2f; layer._terrainSizeX = Mathf.Round((volumeInfo.xLength - 1) * gridSpacingX); layer._terrainSizeY = Mathf.Round((volumeInfo.yLength - 1) * gridSpacingY); // Get volume bounds for calculating position offset session.GetVolumeBounds(nodeID, volumeParts[i].id, out layer._minBounds.x, out layer._minBounds.y, out layer._minBounds.z, out layer._maxBounds.x, out layer._maxBounds.y, out layer._maxBounds.z, out layer._center.x, out layer._center.y, out layer._center.z); LoadStringFromAttribute(session, nodeID, volumeParts[i].id, HEU_Defines.DEFAULT_UNITY_HEIGHTFIELD_TEXTURE_DIFFUSE_ATTR, ref layer._diffuseTexturePath); LoadStringFromAttribute(session, nodeID, volumeParts[i].id, HEU_Defines.DEFAULT_UNITY_HEIGHTFIELD_TEXTURE_MASK_ATTR, ref layer._maskTexturePath); LoadStringFromAttribute(session, nodeID, volumeParts[i].id, HEU_Defines.DEFAULT_UNITY_HEIGHTFIELD_TEXTURE_NORMAL_ATTR, ref layer._normalTexturePath); LoadFloatFromAttribute(session, nodeID, volumeParts[i].id, HEU_Defines.DEFAULT_UNITY_HEIGHTFIELD_NORMAL_SCALE_ATTR, ref layer._normalScale); LoadFloatFromAttribute(session, nodeID, volumeParts[i].id, HEU_Defines.DEFAULT_UNITY_HEIGHTFIELD_METALLIC_ATTR, ref layer._metallic); LoadFloatFromAttribute(session, nodeID, volumeParts[i].id, HEU_Defines.DEFAULT_UNITY_HEIGHTFIELD_SMOOTHNESS_ATTR, ref layer._smoothness); LoadLayerColorFromAttribute(session, nodeID, volumeParts[i].id, HEU_Defines.DEFAULT_UNITY_HEIGHTFIELD_SPECULAR_ATTR, ref layer._specularColor); LoadLayerVector2FromAttribute(session, nodeID, volumeParts[i].id, HEU_Defines.DEFAULT_UNITY_HEIGHTFIELD_TILE_OFFSET_ATTR, ref layer._tileOffset); LoadLayerVector2FromAttribute(session, nodeID, volumeParts[i].id, HEU_Defines.DEFAULT_UNITY_HEIGHTFIELD_TILE_SIZE_ATTR, ref layer._tileSize); // Get the height values from Houdini and find the min and max height range. if (!HEU_GeometryUtility.GetHeightfieldValues(session, volumeInfo.xLength, volumeInfo.yLength, nodeID, volumeParts[i].id, ref layer._rawHeights, ref layer._minHeight, ref layer._maxHeight)) { return false; } // TODO: Tried to replace above with this, but it flattens the heights //layer._rawHeights = HEU_GeometryUtility.GetHeightfieldFromPart(_session, nodeID, volumeParts[i].id, "part", volumeInfo.xLength); // Get the tile index, if it exists, for this part HAPI_AttributeInfo tileAttrInfo = new HAPI_AttributeInfo(); int[] tileAttrData = new int[0]; HEU_GeneralUtility.GetAttribute(session, nodeID, volumeParts[i].id, "tile", ref tileAttrInfo, ref tileAttrData, session.GetAttributeIntData); int tileIndex = 0; if (tileAttrInfo.exists && tileAttrData.Length == 1) { tileIndex = tileAttrData[0]; } // Add layer based on tile index if (tileIndex >= 0) { HEU_LoadBufferVolume volumeBuffer = null; for(int j = 0; j < volumeBuffers.Count; ++j) { if (volumeBuffers[j]._tileIndex == tileIndex) { volumeBuffer = volumeBuffers[j]; break; } } if (volumeBuffer == null) { volumeBuffer = new HEU_LoadBufferVolume(); volumeBuffer.InitializeBuffer(volumeParts[i].id, volumeName, false, false); volumeBuffer._tileIndex = tileIndex; volumeBuffers.Add(volumeBuffer); } if (bHeightPart) { // Height layer always first layer volumeBuffer._layers.Insert(0, layer); volumeBuffer._heightMapSize = layer._heightMapSize; volumeBuffer._terrainSizeX = layer._terrainSizeX; volumeBuffer._terrainSizeY = layer._terrainSizeY; volumeBuffer._heightRange = (layer._maxHeight - layer._minHeight); } else { volumeBuffer._layers.Add(layer); } } Sleep(); } // Each volume buffer is a self contained terrain tile foreach(HEU_LoadBufferVolume volumeBuffer in volumeBuffers) { List<HEU_LoadBufferVolumeLayer> layers = volumeBuffer._layers; //Debug.LogFormat("Heightfield: tile={0}, layers={1}", tile._tileIndex, layers.Count); int heightMapSize = volumeBuffer._heightMapSize; int numLayers = layers.Count; if (numLayers > 0) { // Convert heightmap values from Houdini to Unity volumeBuffer._heightMap = HEU_GeometryUtility.ConvertHeightMapHoudiniToUnity(heightMapSize, layers[0]._rawHeights, layers[0]._minHeight, layers[0]._maxHeight); Sleep(); // Convert splatmap values from Houdini to Unity. List<float[]> heightFields = new List<float[]>(); for(int m = 1; m < numLayers; ++m) { heightFields.Add(layers[m]._rawHeights); } volumeBuffer._splatMaps = HEU_GeometryUtility.ConvertHeightSplatMapHoudiniToUnity(heightMapSize, heightFields); volumeBuffer._position = new Vector3((volumeBuffer._terrainSizeX + volumeBuffer._layers[0]._minBounds.x), volumeBuffer._layers[0]._minHeight + volumeBuffer._layers[0]._position.y, volumeBuffer._layers[0]._minBounds.z); } } return true; }
public static void ExecuteToolOperatorMultiple(string toolName, string toolPath, GameObject[] inputObjects) { GameObject outputObjectToSelect = null; GameObject go = HEU_HAPIUtility.InstantiateHDA(toolPath, Vector3.zero, HEU_SessionManager.GetOrCreateDefaultSession(), false); if (go == null) { HEU_Logger.LogWarningFormat("Failed to instantiate tool: {0}", toolName); return; } HEU_HoudiniAssetRoot assetRoot = go.GetComponent<HEU_HoudiniAssetRoot>(); if (assetRoot != null) { HEU_HoudiniAsset asset = assetRoot._houdiniAsset; HEU_SessionBase session = asset.GetAssetSession(true); int numInputs = inputObjects.Length; List<HEU_InputNode> inputNodes = asset.GetInputNodes(); if (inputNodes == null || inputNodes.Count == 0) { HEU_Logger.LogErrorFormat("Unable to assign input geometry due to no asset inputs on selected tool."); } else { // User could have selected any number of inputs objects, and asset could have any number of inputs. // So use minimum of either to set input object into asset input. int minInputCount = Mathf.Min(inputNodes.Count, numInputs); for (int i = 0; i < minInputCount; ++i) { bool bShouldUseHDA = IsValidInputHDA(inputObjects[i]); if (!bShouldUseHDA &&!IsValidInputMesh(inputObjects[i])) { continue; } else if (bShouldUseHDA && !IsValidInputHDA(inputObjects[i])) { continue; } GameObject inputObject = inputObjects[i]; HEU_InputNode inputNode = inputNodes[i]; inputNode.ResetInputNode(session); if (!bShouldUseHDA) { inputNode.ChangeInputType(session, HEU_InputNode.InputObjectType.UNITY_MESH); HEU_InputObjectInfo inputInfo = inputNode.AddInputEntryAtEndMesh(inputObject); if (inputInfo != null) { inputInfo._useTransformOffset = false; inputNode.KeepWorldTransform = true; inputNode.PackGeometryBeforeMerging = false; inputNode.RequiresUpload = true; } else { HEU_Logger.LogErrorFormat("Invalid input format: {0}", inputObject.gameObject.name); } } else { inputNode.ChangeInputType(session, HEU_InputNode.InputObjectType.HDA); HEU_InputHDAInfo inputHDAInfo = inputNode.AddInputEntryAtEndHDA(inputObject); if (inputHDAInfo != null) { inputNode.KeepWorldTransform = true; inputNode.PackGeometryBeforeMerging = false; inputNode.RequiresUpload = true; } else { HEU_Logger.LogErrorFormat("Invalid input format: {0}", inputObject.gameObject.name); } } } asset.RequestCook(true, true, true, true); outputObjectToSelect = assetRoot.gameObject; } } if (outputObjectToSelect != null) { HEU_EditorUtility.SelectObject(outputObjectToSelect); } }
/// <summary> /// Creates terrain from given volumeInfo for the given gameObject. /// If gameObject has a valid Terrain component, then it is reused. /// Similarly, if the Terrain component has a valid TerrainData, or if the given terrainData is valid, then it is used. /// Otherwise a new TerrainData is created and set to the Terrain. /// Populates the volumePositionOffset with the heightfield offset position. /// Returns true if successfully created the terrain, otherwise false. /// </summary> /// <param name="session">Houdini Engine session to query heightfield data from</param> /// <param name="volumeInfo">Volume info pertaining to the heightfield to generate the Terrain from</param> /// <param name="geoID">The geometry ID</param> /// <param name="partID">The part ID (height layer)</param> /// <param name="gameObject">The target GameObject containing the Terrain component</param> /// <param name="terrainData">A valid TerrainData to use, or if empty, a new one is created and populated</param> /// <param name="volumePositionOffset">Heightfield offset</param> /// <returns>True if successfully popupated the terrain</returns> public static bool GenerateTerrainFromVolume(HEU_SessionBase session, ref HAPI_VolumeInfo volumeInfo, HAPI_NodeId geoID, HAPI_PartId partID, GameObject gameObject, ref TerrainData terrainData, out Vector3 volumePositionOffset, ref Terrain terrain) { volumePositionOffset = Vector3.zero; if (volumeInfo.zLength == 1 && volumeInfo.tupleSize == 1) { // Heightfields will be converted to terrain in Unity. // Unity requires terrainData.heightmapResolution to be square power of two plus 1 (eg. 513, 257, 129, 65). // Houdini gives volumeInfo.xLength and volumeInfo.yLength which are the number of height values per dimension. // Note that volumeInfo.xLength and volumeInfo.yLength is equal to Houdini heightfield size / grid spacing. // The heightfield grid spacing is given as volumeTransformMatrix.scale but divided by 2 (grid spacing / 2 = volumeTransformMatrix.scale). // It is recommended to use grid spacing of 2. // Use the volumeInfo.transform to get the actual heightfield position and size. Matrix4x4 volumeTransformMatrix = HEU_HAPIUtility.GetMatrixFromHAPITransform(ref volumeInfo.transform, false); Vector3 position = HEU_HAPIUtility.GetPosition(ref volumeTransformMatrix); Vector3 scale = HEU_HAPIUtility.GetScale(ref volumeTransformMatrix); // Calculate real terrain size in both Houdini and Unity. // The height values will be mapped over this terrain size. float gridSpacingX = scale.x * 2f; float gridSpacingY = scale.y * 2f; float terrainSizeX = Mathf.Round((volumeInfo.xLength - 1) * gridSpacingX); float terrainSizeY = Mathf.Round((volumeInfo.yLength - 1) * gridSpacingY); // Test size //float terrainSizeX = Mathf.Round((volumeInfo.xLength) * gridSpacingX); //float terrainSizeY = Mathf.Round((volumeInfo.yLength) * gridSpacingY); //Debug.LogFormat("GS = {0},{1},{2}. SX = {1}. SY = {2}", gridSpacingX, gridSpacingY, terrainSizeX, terrainSizeY); //Debug.LogFormat("HeightField Pos:{0}, Scale:{1}", position, scale.ToString("{0.00}")); //Debug.LogFormat("HeightField tileSize:{0}, xLength:{1}, yLength:{2}", volumeInfo.tileSize.ToString("{0.00}"), volumeInfo.xLength.ToString("{0.00}"), volumeInfo.yLength.ToString("{0.00}")); //Debug.LogFormat("HeightField Terrain Size x:{0}, y:{1}", terrainSizeX.ToString("{0.00}"), terrainSizeY.ToString("{0.00}")); //Debug.LogFormat("HeightField minX={0}, minY={1}, minZ={2}", volumeInfo.minX.ToString("{0.00}"), volumeInfo.minY.ToString("{0.00}"), volumeInfo.minZ.ToString("{0.00}")); const int UNITY_MINIMUM_HEIGHTMAP_RESOLUTION = 33; if (terrainSizeX < UNITY_MINIMUM_HEIGHTMAP_RESOLUTION || terrainSizeY < UNITY_MINIMUM_HEIGHTMAP_RESOLUTION) { Debug.LogWarningFormat("Unity Terrain has a minimum heightmap resolution of {0}. This HDA heightmap size is {1}x{2}." + "\nPlease resize the terrain to a value higher than this.", UNITY_MINIMUM_HEIGHTMAP_RESOLUTION, terrainSizeX, terrainSizeY); return false; } bool bNewTerrain = false; bool bNewTerrainData = false; terrain = gameObject.GetComponent<Terrain>(); if (terrain == null) { terrain = gameObject.AddComponent<Terrain>(); bNewTerrain = true; } TerrainCollider collider = HEU_GeneralUtility.GetOrCreateComponent<TerrainCollider>(gameObject); // Look up terrain material, if specified, on the height layer string specifiedTerrainMaterialName = HEU_GeneralUtility.GetMaterialAttributeValueFromPart(session, geoID, partID); // This ensures to reuse existing terraindata, and only creates new if none exist or none provided if (terrain.terrainData == null) { if (terrainData == null) { terrainData = new TerrainData(); bNewTerrainData = true; } terrain.terrainData = terrainData; SetTerrainMaterial(terrain, specifiedTerrainMaterialName); } terrainData = terrain.terrainData; collider.terrainData = terrainData; if (bNewTerrain) { #if UNITY_2018_3_OR_NEWER terrain.allowAutoConnect = true; // This has to be set after setting material terrain.drawInstanced = true; #endif } // Heightmap resolution must be square power-of-two plus 1. // Unity will automatically resize terrainData.heightmapResolution so need to handle the changed size (if Unity changed it). int heightMapResolution = volumeInfo.xLength; terrainData.heightmapResolution = heightMapResolution; int terrainResizedDelta = terrainData.heightmapResolution - heightMapResolution; if (terrainResizedDelta < 0) { Debug.LogWarningFormat("Note that Unity automatically resized terrain resolution to {0} from {1}. Use terrain size of power of two plus 1, and grid spacing of 2.", heightMapResolution, terrainData.heightmapResolution); heightMapResolution = terrainData.heightmapResolution; } else if (terrainResizedDelta > 0) { Debug.LogErrorFormat("Unsupported terrain size. Use terrain size of power of two plus 1, and grid spacing of 2. Given size is {0} but Unity resized it to {1}.", heightMapResolution, terrainData.heightmapResolution); return false; } int mapWidth = volumeInfo.xLength; int mapHeight = volumeInfo.yLength; // Get the converted height values from Houdini and find the min and max height range. float minHeight = 0; float maxHeight = 0; float heightRange = 0; bool bUseHeightRangeOverride = true; float[] normalizedHeights = GetNormalizedHeightmapFromPartWithMinMax(session, geoID, partID, volumeInfo.xLength, volumeInfo.yLength, ref minHeight, ref maxHeight, ref heightRange, bUseHeightRangeOverride); float[,] unityHeights = ConvertHeightMapHoudiniToUnity(heightMapResolution, heightMapResolution, normalizedHeights); // The terrainData.baseMapResolution is not set here, but rather left to whatever default Unity uses // The terrainData.alphamapResolution is set later when setting the alphamaps. if (bNewTerrainData) { // 32 is the default for resolutionPerPatch const int detailResolution = 1024; const int resolutionPerPatch = 32; terrainData.SetDetailResolution(detailResolution, resolutionPerPatch); } // Note SetHeights must be called before setting size in next line, as otherwise // the internal terrain size will not change after setting the size. terrainData.SetHeights(0, 0, unityHeights); // Note that Unity uses a default height range of 600 when a flat terrain is created. // Without a non-zero value for the height range, user isn't able to draw heights. // Therefore, set 600 as the value if height range is currently 0 (due to flat heightfield). if (heightRange == 0) { heightRange = terrainData.size.y > 1 ? terrainData.size.y : 600; } terrainData.size = new Vector3(terrainSizeX, heightRange, terrainSizeY); terrain.Flush(); // Unity Terrain has origin at bottom left, whereas Houdini uses centre of terrain. // Use volume bounds to set position offset when using split tiles float xmin, xmax, zmin, zmax, ymin, ymax, xcenter, ycenter, zcenter; session.GetVolumeBounds(geoID, partID, out xmin, out ymin, out zmin, out xmax, out ymax, out zmax, out xcenter, out ycenter, out zcenter); //Debug.LogFormat("xmin: {0}, xmax: {1}, ymin: {2}, ymax: {3}, zmin: {4}, zmax: {5}, xc: {6}, yc: {7}, zc: {8}", // xmin, xmax, ymin, ymax, zmin, zmax, xcenter, ycenter, zcenter); // Offset position is based on size of heightfield float offsetX = (float)heightMapResolution / (float)mapWidth; float offsetZ = (float)heightMapResolution / (float)mapHeight; //Debug.LogFormat("offsetX: {0}, offsetZ: {1}", offsetX, offsetZ); // Use y position from attribute if user has set it float ypos = position.y + minHeight; float userYPos; if (HEU_GeneralUtility.GetAttributeFloatSingle(session, geoID, partID, HEU_Defines.DEFAULT_UNITY_HEIGHTFIELD_YPOS, out userYPos)) { ypos = userYPos; } // TODO: revisit how the position is calculated volumePositionOffset = new Vector3((terrainSizeX + xmin) * offsetX, ypos, zmin * offsetZ); // Test position //volumePositionOffset = new Vector3(xcenter + mapWidth, ycenter, zcenter - mapHeight); return true; } else { Debug.LogWarning("Non-heightfield volume type not supported!"); } return false; }
public bool UploadAttributeViaMeshInput(HEU_SessionBase session, HAPI_NodeId geoID, HAPI_PartId partID) { // Need to upload same geometry as in the original Editable node, and with custom attribute values. // First get the current geometry info and data. HAPI_GeoInfo geoInfo = new HAPI_GeoInfo(); if(!session.GetGeoInfo(geoID, ref geoInfo)) { return false; } HAPI_PartInfo oldPartInfo = new HAPI_PartInfo(); if(!session.GetPartInfo(geoID, partID, ref oldPartInfo)) { return false; } int pointCount = oldPartInfo.pointCount; int vertexCount = oldPartInfo.vertexCount; int faceCount = oldPartInfo.faceCount; // Get facecounts int[] faceCountData = new int[faceCount]; if(!HEU_GeneralUtility.GetArray2Arg(geoID, partID, session.GetFaceCounts, faceCountData, 0, faceCount)) { return false; } // Get indices int[] vertexList = new int[vertexCount]; if (!HEU_GeneralUtility.GetArray2Arg(geoID, partID, session.GetVertexList, vertexList, 0, vertexCount)) { return false; } List<HEU_AttributeData> pointAttributeDatas = new List<HEU_AttributeData>(); List<HEU_AttributeData> vertexAttributeDatas = new List<HEU_AttributeData>(); List<HEU_AttributeData> primitiveAttributeDatas = new List<HEU_AttributeData>(); List<HEU_AttributeData> detailAttributeDatas = new List<HEU_AttributeData>(); // Get all attributes, including editted GetAttributesList(session, geoID, partID, pointAttributeDatas, HAPI_AttributeOwner.HAPI_ATTROWNER_POINT, oldPartInfo.pointAttributeCount); GetAttributesList(session, geoID, partID, vertexAttributeDatas, HAPI_AttributeOwner.HAPI_ATTROWNER_VERTEX, oldPartInfo.vertexAttributeCount); GetAttributesList(session, geoID, partID, primitiveAttributeDatas, HAPI_AttributeOwner.HAPI_ATTROWNER_PRIM, oldPartInfo.primitiveAttributeCount); GetAttributesList(session, geoID, partID, detailAttributeDatas, HAPI_AttributeOwner.HAPI_ATTROWNER_DETAIL, oldPartInfo.detailAttributeCount); // Now create new geometry info and populate it HAPI_PartInfo newPartInfo = new HAPI_PartInfo(); newPartInfo.faceCount = faceCount; newPartInfo.vertexCount = vertexCount; newPartInfo.pointCount = pointCount; newPartInfo.pointAttributeCount = oldPartInfo.pointAttributeCount; newPartInfo.vertexAttributeCount = oldPartInfo.vertexAttributeCount; newPartInfo.primitiveAttributeCount = oldPartInfo.primitiveAttributeCount; newPartInfo.detailAttributeCount = oldPartInfo.detailAttributeCount; int newPartID = partID; if (!session.SetPartInfo(geoID, newPartID, ref newPartInfo)) { return false; } if (!HEU_GeneralUtility.SetArray2Arg(geoID, newPartID, session.SetFaceCount, faceCountData, 0, newPartInfo.faceCount)) { return false; } if (!HEU_GeneralUtility.SetArray2Arg(geoID, newPartID, session.SetVertexList, vertexList, 0, newPartInfo.vertexCount)) { return false; } // Upload all attributes, include editted UpdateAttributeList(session, geoID, partID, pointAttributeDatas); UpdateAttributeList(session, geoID, partID, vertexAttributeDatas); UpdateAttributeList(session, geoID, partID, primitiveAttributeDatas); UpdateAttributeList(session, geoID, partID, detailAttributeDatas); return session.CommitGeo(geoID); }
public void GenerateTransform(HEU_SessionBase session, HEU_Parameters parameters) { HAPI_TransformEuler transformEuler = new HAPI_TransformEuler(true); transformEuler.rstOrder = _rstOrder; transformEuler.rotationOrder = _xyzOrder; transformEuler.position[0] = 0; transformEuler.position[1] = 0; transformEuler.position[2] = 0; transformEuler.rotationEuler[0] = 0; transformEuler.rotationEuler[1] = 0; transformEuler.rotationEuler[2] = 0; transformEuler.scale[0] = 1; transformEuler.scale[1] = 1; transformEuler.scale[2] = 1; if (IsSpecialRSTOrder(transformEuler.rstOrder) && _handleParamTranslateBinding != null) { HEU_ParameterData parmData = parameters.GetParameterWithParmID(_handleParamTranslateBinding._parmID); if (parmData != null && !parmData._parmInfo.invisible) { transformEuler.position[0] = parmData._floatValues[0]; transformEuler.position[1] = parmData._floatValues[1]; transformEuler.position[2] = parmData._floatValues[2]; } } if(_handleParamRotateBinding != null) { HEU_ParameterData parmData = parameters.GetParameterWithParmID(_handleParamRotateBinding._parmID); if (parmData != null && !parmData._parmInfo.invisible) { transformEuler.rotationEuler[0] = parmData._floatValues[0]; transformEuler.rotationEuler[1] = parmData._floatValues[1]; transformEuler.rotationEuler[2] = parmData._floatValues[2]; } } if(_handleParamScaleBinding != null) { HEU_ParameterData parmData = parameters.GetParameterWithParmID(_handleParamScaleBinding._parmID); if (parmData != null && !parmData._parmInfo.invisible) { transformEuler.scale[0] = parmData._floatValues[0]; transformEuler.scale[1] = parmData._floatValues[1]; transformEuler.scale[2] = parmData._floatValues[2]; } } if (!session.ConvertTransform(ref transformEuler, HAPI_RSTOrder.HAPI_SRT, HAPI_XYZOrder.HAPI_ZXY, out _convertedTransformEuler)) { return; } // Convert to left-handed Unity _convertedTransformEuler.position[0] = -_convertedTransformEuler.position[0]; _convertedTransformEuler.rotationEuler[1] = -_convertedTransformEuler.rotationEuler[1]; _convertedTransformEuler.rotationEuler[2] = -_convertedTransformEuler.rotationEuler[2]; if (IsSpecialRSTOrder(transformEuler.rstOrder)) { _handlePosition = new Vector3(_convertedTransformEuler.position[0], _convertedTransformEuler.position[1], _convertedTransformEuler.position[2]); } else if (_handleParamTranslateBinding != null) { _handlePosition = new Vector3(-transformEuler.position[0], transformEuler.position[1], transformEuler.position[2]); } else { _handlePosition = Vector3.zero; } _handleRotation = Quaternion.Euler(_convertedTransformEuler.rotationEuler[0], _convertedTransformEuler.rotationEuler[1], _convertedTransformEuler.rotationEuler[2]); if (_handleParamScaleBinding != null) { _handleScale = new Vector3(transformEuler.scale[0], transformEuler.scale[1], transformEuler.scale[2]); } else { _handleScale = Vector3.one; } }
private HEU_LoadBufferInstancer GeneratePointAttributeInstancerBuffer(HEU_SessionBase session, HAPI_NodeId geoID, HAPI_PartId partID, string partName, HAPI_PartInfo partInfo) { int numInstances = partInfo.pointCount; if (numInstances <= 0) { return null; } // Find type of instancer string instanceAttrName = HEU_PluginSettings.InstanceAttr; string unityInstanceAttrName = HEU_PluginSettings.UnityInstanceAttr; string instancePrefixAttrName = HEU_Defines.DEFAULT_INSTANCE_PREFIX_ATTR; HAPI_AttributeInfo instanceAttrInfo = new HAPI_AttributeInfo(); HAPI_AttributeInfo unityInstanceAttrInfo = new HAPI_AttributeInfo(); HAPI_AttributeInfo instancePrefixAttrInfo = new HAPI_AttributeInfo(); HEU_GeneralUtility.GetAttributeInfo(session, geoID, partID, instanceAttrName, ref instanceAttrInfo); HEU_GeneralUtility.GetAttributeInfo(session, geoID, partID, unityInstanceAttrName, ref unityInstanceAttrInfo); if (unityInstanceAttrInfo.exists) { // Object instancing via existing Unity object (path from point attribute) HAPI_Transform[] instanceTransforms = new HAPI_Transform[numInstances]; if (!HEU_GeneralUtility.GetArray3Arg(geoID, partID, HAPI_RSTOrder.HAPI_SRT, session.GetInstanceTransformsOnPart, instanceTransforms, 0, numInstances)) { return null; } string[] instancePrefixes = null; HEU_GeneralUtility.GetAttributeInfo(session, geoID, partID, instancePrefixAttrName, ref instancePrefixAttrInfo); if (instancePrefixAttrInfo.exists) { instancePrefixes = HEU_GeneralUtility.GetAttributeStringData(session, geoID, partID, instancePrefixAttrName, ref instancePrefixAttrInfo); } string[] assetPaths = null; // Attribute owner type determines whether to use single (detail) or multiple (point) asset(s) as source if (unityInstanceAttrInfo.owner == HAPI_AttributeOwner.HAPI_ATTROWNER_POINT || unityInstanceAttrInfo.owner == HAPI_AttributeOwner.HAPI_ATTROWNER_DETAIL) { assetPaths = HEU_GeneralUtility.GetAttributeStringData(session, geoID, partID, unityInstanceAttrName, ref unityInstanceAttrInfo); } else { // Other attribute owned types are unsupported SetLog(HEU_LoadData.LoadStatus.ERROR, string.Format("Unsupported attribute owner {0} for attribute {1}", unityInstanceAttrInfo.owner, unityInstanceAttrName)); return null; } if (assetPaths == null) { SetLog(HEU_LoadData.LoadStatus.ERROR, "Unable to get instanced asset path from attribute!"); return null; } HEU_LoadBufferInstancer instancerBuffer = new HEU_LoadBufferInstancer(); instancerBuffer.InitializeBuffer(partID, partName, partInfo.isInstanced, true); instancerBuffer._instanceTransforms = instanceTransforms; instancerBuffer._instancePrefixes = instancePrefixes; instancerBuffer._assetPaths = assetPaths; HAPI_AttributeInfo collisionGeoAttrInfo = new HAPI_AttributeInfo(); HEU_GeneralUtility.GetAttributeInfo(session, geoID, partID, HEU_Defines.DEFAULT_COLLISION_GEO, ref collisionGeoAttrInfo); if (collisionGeoAttrInfo.owner == HAPI_AttributeOwner.HAPI_ATTROWNER_POINT || collisionGeoAttrInfo.owner == HAPI_AttributeOwner.HAPI_ATTROWNER_DETAIL) { instancerBuffer._collisionAssetPaths = HEU_GeneralUtility.GetAttributeStringData(session, geoID, partID, HEU_Defines.DEFAULT_COLLISION_GEO, ref collisionGeoAttrInfo); } return instancerBuffer; } else if (instanceAttrInfo.exists) { // Object instancing via internal object path is not supported SetLog(HEU_LoadData.LoadStatus.ERROR, string.Format("Object instancing is not supported (part {0})!", partName)); } else { // Standard object instancing via single Houdini object is not supported SetLog(HEU_LoadData.LoadStatus.ERROR, string.Format("Object instancing is not supported (part {0})!", partName)); } return null; }
private void PopulateAttributeData(HEU_SessionBase session, HAPI_NodeId geoID, HAPI_PartId partID, HEU_AttributeData attributeData, ref HAPI_AttributeInfo attributeInfo) { attributeData._attributeInfo = attributeInfo; int tupleSize = attributeInfo.tupleSize; int attributeCount = attributeInfo.count; int arraySize = attributeCount * tupleSize; // First reset arrays if the type had changed since last sync if ((attributeInfo.storage == HAPI_StorageType.HAPI_STORAGETYPE_INT && attributeData._attributeType != HEU_AttributeData.AttributeType.INT) || (attributeInfo.storage == HAPI_StorageType.HAPI_STORAGETYPE_FLOAT && attributeData._attributeType != HEU_AttributeData.AttributeType.FLOAT) || (attributeInfo.storage == HAPI_StorageType.HAPI_STORAGETYPE_STRING && attributeData._attributeType != HEU_AttributeData.AttributeType.STRING)) { // Reset arrays if type is different attributeData._floatValues = null; attributeData._stringValues = null; attributeData._intValues = null; attributeData._attributeState = HEU_AttributeData.AttributeState.INVALID; if(attributeInfo.storage == HAPI_StorageType.HAPI_STORAGETYPE_INT) { attributeData._attributeType = HEU_AttributeData.AttributeType.INT; } else if (attributeInfo.storage == HAPI_StorageType.HAPI_STORAGETYPE_FLOAT) { attributeData._attributeType = HEU_AttributeData.AttributeType.FLOAT; } else if (attributeInfo.storage == HAPI_StorageType.HAPI_STORAGETYPE_STRING) { attributeData._attributeType = HEU_AttributeData.AttributeType.STRING; } } // Make sure the internal array is correctly sized for syncing. if (attributeData._attributeType == HEU_AttributeData.AttributeType.INT) { if (attributeData._intValues == null) { attributeData._intValues = new int[arraySize]; attributeData._attributeState = HEU_AttributeData.AttributeState.INVALID; } else if (attributeData._intValues.Length != arraySize) { System.Array.Resize<int>(ref attributeData._intValues, arraySize); attributeData._attributeState = HEU_AttributeData.AttributeState.INVALID; } attributeData._floatValues = null; attributeData._stringValues = null; if (attributeData._attributeState == HEU_AttributeData.AttributeState.INVALID) { int[] data = new int[0]; HEU_GeneralUtility.GetAttribute(session, geoID, partID, attributeData._name, ref attributeInfo, ref data, session.GetAttributeIntData); for (int i = 0; i < attributeCount; ++i) { for (int tuple = 0; tuple < tupleSize; ++tuple) { attributeData._intValues[i * tupleSize + tuple] = data[i * tupleSize + tuple]; } } } } else if (attributeData._attributeType == HEU_AttributeData.AttributeType.FLOAT) { if (attributeData._floatValues == null) { attributeData._floatValues = new float[arraySize]; attributeData._attributeState = HEU_AttributeData.AttributeState.INVALID; } else if (attributeData._floatValues.Length != arraySize) { System.Array.Resize<float>(ref attributeData._floatValues, arraySize); attributeData._attributeState = HEU_AttributeData.AttributeState.INVALID; } attributeData._intValues = null; attributeData._stringValues = null; if (attributeData._attributeState == HEU_AttributeData.AttributeState.INVALID) { float[] data = new float[0]; HEU_GeneralUtility.GetAttribute(session, geoID, partID, attributeData._name, ref attributeInfo, ref data, session.GetAttributeFloatData); for (int i = 0; i < attributeCount; ++i) { for (int tuple = 0; tuple < tupleSize; ++tuple) { attributeData._floatValues[i * tupleSize + tuple] = data[i * tupleSize + tuple]; } } } } else if (attributeData._attributeType == HEU_AttributeData.AttributeType.STRING) { if (attributeData._stringValues == null) { attributeData._stringValues = new string[arraySize]; attributeData._attributeState = HEU_AttributeData.AttributeState.INVALID; } else if (attributeData._stringValues.Length != arraySize) { System.Array.Resize<string>(ref attributeData._stringValues, arraySize); attributeData._attributeState = HEU_AttributeData.AttributeState.INVALID; } attributeData._intValues = null; attributeData._floatValues = null; if (attributeData._attributeState == HEU_AttributeData.AttributeState.INVALID) { HAPI_StringHandle[] data = new HAPI_StringHandle[0]; HEU_GeneralUtility.GetAttribute(session, geoID, partID, attributeData._name, ref attributeInfo, ref data, session.GetAttributeStringData); for (int i = 0; i < attributeCount; ++i) { for (int tuple = 0; tuple < tupleSize; ++tuple) { HAPI_StringHandle stringHandle = data[i * tupleSize + tuple]; attributeData._stringValues[i * tupleSize + tuple] = HEU_SessionManager.GetString(stringHandle, session); } } } } SetAttributeDataSyncd(attributeData); }
protected virtual void SetupLoadTask(HEU_SessionBase session) { }
/// <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> /// 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 }
HAPI_NodeId GetParentNodeID(HEU_SessionBase session) { HAPI_NodeInfo nodeInfo = new HAPI_NodeInfo(); return((session.GetNodeInfo(_cookNodeID, ref nodeInfo, false)) ? nodeInfo.parentId : -1); }
/// <summary> /// Creates a new input asset in scene, as well as in a Houdini session. /// </summary> /// <returns>A valid input asset gameobject or null if failed.</returns> public static GameObject CreateNewInputAsset(Transform parentTransform = null, HEU_SessionBase session = null, bool bBuildAsync = true) { return(CreateNewAsset(HEU_HoudiniAsset.HEU_AssetType.TYPE_INPUT, "HoudiniInput", parentTransform, session, bBuildAsync)); }
public static GameObject CreateNewAsset(HEU_HoudiniAsset.HEU_AssetType assetType, string rootName = "HoudiniAsset", Transform parentTransform = null, HEU_SessionBase session = null, bool bBuildAsync = true) { if (session == null) { session = HEU_SessionManager.GetOrCreateDefaultSession(); } if (!session.IsSessionValid()) { Debug.LogWarning("Invalid Houdini Engine session!"); return(null); } // This will be the root GameObject for the HDA. Adding HEU_HoudiniAssetRoot // allows to use a custom Inspector. GameObject rootGO = new GameObject(); HEU_HoudiniAssetRoot assetRoot = rootGO.AddComponent <HEU_HoudiniAssetRoot>(); // Set the game object's name to the asset's name rootGO.name = string.Format("{0}{1}", rootName, rootGO.GetInstanceID()); // Under the root, we'll add the HEU_HoudiniAsset onto another GameObject // This will be marked as EditorOnly to strip out for builds GameObject hdaGEO = new GameObject(HEU_PluginSettings.HDAData_Name); hdaGEO.transform.parent = rootGO.transform; // This holds all Houdini Engine data HEU_HoudiniAsset asset = hdaGEO.AddComponent <HEU_HoudiniAsset>(); // Marking as EditorOnly to be excluded from builds if (HEU_GeneralUtility.DoesUnityTagExist(HEU_PluginSettings.EditorOnly_Tag)) { hdaGEO.tag = HEU_PluginSettings.EditorOnly_Tag; } // Bind the root to the asset assetRoot._houdiniAsset = asset; // Populate asset with what we know asset.SetupAsset(assetType, null, rootGO, session); // Build it in Houdini Engine asset.RequestReload(bBuildAsync); if (parentTransform != null) { rootGO.transform.parent = parentTransform; rootGO.transform.localPosition = Vector3.zero; } else { rootGO.transform.position = Vector3.zero; } return(rootGO); }
/// <summary> /// Load and instantiate an HDA asset in Unity and Houdini, for the asset located at given path. /// </summary> /// <param name="filePath">Full path to the HDA in Unity project</param> /// <param name="initialPosition">Initial location to create the instance in Unity.</param> /// <returns>Returns the newly created gameobject for the asset in the scene, or null if creation failed.</returns> public static GameObject InstantiateHDA(string filePath, Vector3 initialPosition, HEU_SessionBase session, bool bBuildAsync) { if (filePath == null || !HEU_Platform.DoesFileExist(filePath)) { return(null); } // This will be the root GameObject for the HDA. Adding HEU_HoudiniAssetRoot // allows to use a custom Inspector. GameObject rootGO = new GameObject(HEU_Defines.HEU_DEFAULT_ASSET_NAME); HEU_HoudiniAssetRoot assetRoot = rootGO.AddComponent <HEU_HoudiniAssetRoot>(); // Under the root, we'll add the HEU_HoudiniAsset onto another GameObject // This will be marked as EditorOnly to strip out for builds GameObject hdaGEO = new GameObject(HEU_PluginSettings.HDAData_Name); hdaGEO.transform.parent = rootGO.transform; // This holds all Houdini Engine data HEU_HoudiniAsset asset = hdaGEO.AddComponent <HEU_HoudiniAsset>(); // Marking as EditorOnly to be excluded from builds if (HEU_GeneralUtility.DoesUnityTagExist(HEU_PluginSettings.EditorOnly_Tag)) { hdaGEO.tag = HEU_PluginSettings.EditorOnly_Tag; } // Bind the root to the asset assetRoot._houdiniAsset = asset; // Populate asset with what we know asset.SetupAsset(HEU_HoudiniAsset.HEU_AssetType.TYPE_HDA, filePath, rootGO, session); // Build it in Houdini Engine asset.RequestReload(bBuildAsync); // Apply Unity transform and possibly upload to Houdini Engine rootGO.transform.position = initialPosition; Debug.LogFormat("{0}: Created new HDA asset from {1} of type {2}.", HEU_Defines.HEU_NAME, filePath, asset.AssetType); return(rootGO); }
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("Part name: {0}, GeoName: {1}, Volume Name: {2}, Display: {3}", part.PartName, geoNode.GeoName, volumeName, geoNode.Displayable); // Ignoring mask layer because it is Houdini-specific (same behaviour as regular HDA terrain generation) if (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); // 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)) { 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; }
/// <summary> /// Find all TOP networks from linked HDA, as well as the TOP nodes within, and populate internal state. /// </summary> /// <returns>True if successfully populated data</returns> public bool PopulateTOPNetworks() { HEU_SessionBase session = GetHAPISession(); HAPI_NodeInfo assetInfo = new HAPI_NodeInfo(); if (!session.GetNodeInfo(_assetID, ref assetInfo, true)) { return false; } // Get all networks within the asset, recursively. // The reason to get all networks is because there can be TOP network SOP which isn't a TOP network type, but rather a SOP type int nodeCount = 0; if (!session.ComposeChildNodeList(_assetID, (int)(HAPI_NodeType.HAPI_NODETYPE_ANY), (int)HAPI_NodeFlags.HAPI_NODEFLAGS_NETWORK, true, ref nodeCount)) { return false; } HAPI_NodeId[] nodeIDs = new HAPI_NodeId[nodeCount]; if (!session.GetComposedChildNodeList(_assetID, nodeIDs, nodeCount)) { return false; } // Holds TOP networks in use List<HEU_TOPNetworkData> newNetworks = new List<HEU_TOPNetworkData>(); // For each network, only add those with TOP child nodes (therefore guaranteeing only TOP networks are added). for (int t = 0; t < nodeCount; ++t) { HAPI_NodeInfo topNodeInfo = new HAPI_NodeInfo(); if (!session.GetNodeInfo(nodeIDs[t], ref topNodeInfo)) { return false; } string nodeName = HEU_SessionManager.GetString(topNodeInfo.nameSH, session); //Debug.LogFormat("Top node: {0} - {1}", nodeName, topNodeInfo.type); // Skip any non TOP or SOP networks if (topNodeInfo.type != HAPI_NodeType.HAPI_NODETYPE_TOP && topNodeInfo.type != HAPI_NodeType.HAPI_NODETYPE_SOP) { continue; } // Get list of all TOP nodes within this network. HAPI_NodeId[] topNodeIDs = null; if (!HEU_SessionManager.GetComposedChildNodeList(session, nodeIDs[t], (int)(HAPI_NodeType.HAPI_NODETYPE_TOP), (int)HAPI_NodeFlags.HAPI_NODEFLAGS_TOP_NONSCHEDULER, true, out topNodeIDs)) { continue; } // Skip networks without TOP nodes if (topNodeIDs == null || topNodeIDs.Length == 0) { continue; } // Get any filter tags from spare parms on TOP nodes TOPNodeTags tags = new TOPNodeTags(); if (_useHEngineData) { ParseHEngineData(session, nodeIDs[t], ref topNodeInfo, ref tags); if (!tags._show) { continue; } } else { tags._show = true; } HEU_TOPNetworkData topNetworkData = GetTOPNetworkByName(nodeName, _topNetworks); if (topNetworkData == null) { topNetworkData = new HEU_TOPNetworkData(); } else { // Found previous TOP network, so remove it from old list. This makes // sure to not remove it when cleaning up old nodes. _topNetworks.Remove(topNetworkData); } newNetworks.Add(topNetworkData); topNetworkData._nodeID = nodeIDs[t]; topNetworkData._nodeName = nodeName; topNetworkData._parentName = _assetName; topNetworkData._tags = tags; PopulateTOPNodes(session, topNetworkData, topNodeIDs, _useHEngineData); } // Clear old TOP networks and nodes ClearAllTOPData(); _topNetworks = newNetworks; // Update latest TOP network names _topNetworkNames = new string[_topNetworks.Count]; for(int i = 0; i < _topNetworks.Count; ++i) { _topNetworkNames[i] = _topNetworks[i]._nodeName; } return true; }
public bool GenerateMeshBuffers(HEU_SessionBase session, HAPI_NodeId nodeID, List<HAPI_PartInfo> meshParts, bool bSplitPoints, bool bUseLODGroups, bool bGenerateUVs, bool bGenerateTangents, bool bGenerateNormals, out List<HEU_LoadBufferMesh> meshBuffers) { meshBuffers = null; if (meshParts.Count == 0) { return true; } bool bSuccess = true; string assetCacheFolderPath = ""; meshBuffers = new List<HEU_LoadBufferMesh>(); foreach(HAPI_PartInfo partInfo in meshParts) { HAPI_NodeId geoID = nodeID; int partID = partInfo.id; string partName = HEU_SessionManager.GetString(partInfo.nameSH, session); bool bPartInstanced = partInfo.isInstanced; if (partInfo.type == HAPI_PartType.HAPI_PARTTYPE_MESH) { List<HEU_MaterialData> materialCache = new List<HEU_MaterialData>(); HEU_GenerateGeoCache geoCache = HEU_GenerateGeoCache.GetPopulatedGeoCache(session, -1, geoID, partID, bUseLODGroups, materialCache, assetCacheFolderPath); if (geoCache == null) { // Failed to get necessary info for generating geometry. SetLog(HEU_LoadData.LoadStatus.ERROR, string.Format("Failed to generate geometry cache for part: {0}", partName)); continue; } geoCache._materialCache = materialCache; // Build the GeoGroup using points or vertices bool bResult = false; List<HEU_GeoGroup> LODGroupMeshes = null; int defaultMaterialKey = 0; if (bSplitPoints) { bResult = HEU_GenerateGeoCache.GenerateGeoGroupUsingGeoCachePoints(session, geoCache, bGenerateUVs, bGenerateTangents, bGenerateNormals, bUseLODGroups, bPartInstanced, out LODGroupMeshes, out defaultMaterialKey); } else { bResult = HEU_GenerateGeoCache.GenerateGeoGroupUsingGeoCacheVertices(session, geoCache, bGenerateUVs, bGenerateTangents, bGenerateNormals, bUseLODGroups, bPartInstanced, out LODGroupMeshes, out defaultMaterialKey); } if (bResult) { HEU_LoadBufferMesh meshBuffer = new HEU_LoadBufferMesh(); meshBuffer.InitializeBuffer(partID, partName, partInfo.isInstanced, false); meshBuffer._geoCache = geoCache; meshBuffer._LODGroupMeshes = LODGroupMeshes; meshBuffer._defaultMaterialKey = defaultMaterialKey; meshBuffer._bGenerateUVs = bGenerateUVs; meshBuffer._bGenerateTangents = bGenerateTangents; meshBuffer._bGenerateNormals = bGenerateNormals; meshBuffer._bPartInstanced = partInfo.isInstanced; meshBuffers.Add(meshBuffer); } else { SetLog(HEU_LoadData.LoadStatus.ERROR, string.Format("Failed to generated geometry for part: {0}", partName)); } } } return bSuccess; }
/// <summary> /// Given TOP nodes from a TOP network, populate internal state from each TOP node. /// </summary> /// <param name="session">Houdini Engine session</param> /// <param name="topNetwork">TOP network to query TOP nodes from</param> /// <param name="topNodeIDs">List of TOP nodes in the TOP network</param> /// <param name="useHEngineData">Whether or not to use HEngine data for filtering</param> /// <returns>True if successfully populated data</returns> public static bool PopulateTOPNodes(HEU_SessionBase session, HEU_TOPNetworkData topNetwork, HAPI_NodeId[] topNodeIDs, bool useHEngineData) { // Holds list of found TOP nodes List<HEU_TOPNodeData> newNodes = new List<HEU_TOPNodeData>(); foreach(HAPI_NodeId topNodeID in topNodeIDs) { // Not necessary. Blocks main thread. //session.CookNode(childNodeID, HEU_PluginSettings.CookTemplatedGeos); HAPI_NodeInfo childNodeInfo = new HAPI_NodeInfo(); if (!session.GetNodeInfo(topNodeID, ref childNodeInfo)) { return false; } string nodeName = HEU_SessionManager.GetString(childNodeInfo.nameSH, session); //Debug.LogFormat("TOP Node: name={0}, type={1}", nodeName, childNodeInfo.type); TOPNodeTags tags = new TOPNodeTags(); if (useHEngineData) { ParseHEngineData(session, topNodeID, ref childNodeInfo, ref tags); if (!tags._show) { continue; } } else { tags._show = true; } HEU_TOPNodeData topNodeData = GetTOPNodeByName(nodeName, topNetwork._topNodes); if (topNodeData == null) { topNodeData = new HEU_TOPNodeData(); } else { topNetwork._topNodes.Remove(topNodeData); } newNodes.Add(topNodeData); //topNodeData.Reset(); topNodeData._nodeID = topNodeID; topNodeData._nodeName = nodeName; topNodeData._parentName = topNetwork._parentName + "_" + topNetwork._nodeName; topNodeData._tags = tags; } // Clear old unused TOP nodes for (int i = 0; i < topNetwork._topNodes.Count; ++i) { ClearTOPNodeWorkItemResults(topNetwork._topNodes[i]); } topNetwork._topNodes = newNodes; // Get list of updated TOP node names topNetwork._topNodeNames = new string[topNetwork._topNodes.Count]; for (int i = 0; i < topNetwork._topNodes.Count; ++i) { topNetwork._topNodeNames[i] = topNetwork._topNodes[i]._nodeName; } return true; }
public void SyncAllAttributesFrom(HEU_SessionBase session, HEU_HoudiniAsset asset, HAPI_NodeId geoID, ref HAPI_PartInfo partInfo, GameObject outputGameObject) { _geoID = geoID; _partID = partInfo.id; HAPI_GeoInfo geoInfo = new HAPI_GeoInfo(); if(session.GetGeoInfo(_geoID, ref geoInfo)) { _geoName = HEU_SessionManager.GetString(geoInfo.nameSH, session); } _outputTransform = outputGameObject.transform; // Need the vertex list of indices to map the positions to vertex colors _vertexIndices = new int[partInfo.vertexCount]; if (!HEU_GeneralUtility.GetArray2Arg(geoID, partInfo.id, session.GetVertexList, _vertexIndices, 0, partInfo.vertexCount)) { return; } // Note that this currently only supports point attributes int attributePointCount = partInfo.attributeCounts[(int)HAPI_AttributeOwner.HAPI_ATTROWNER_POINT]; string[] pointAttributeNames = new string[attributePointCount]; if(!session.GetAttributeNames(geoID, partInfo.id, HAPI_AttributeOwner.HAPI_ATTROWNER_POINT, ref pointAttributeNames, attributePointCount)) { Debug.LogErrorFormat("Failed to sync attributes. Unable to retrieve attribute names."); return; } // Create new list of attributes. We'll move existing attributes that are still in use as we find them. List<HEU_AttributeData> newAttributeDatas = new List<HEU_AttributeData>(); foreach (string pointAttributeName in pointAttributeNames) { if(string.IsNullOrEmpty(pointAttributeName)) { continue; } // Get position attribute values separately. Used for painting and editing points in 3D scene. HAPI_AttributeInfo pointAttributeInfo = new HAPI_AttributeInfo(); if(session.GetAttributeInfo(geoID, partInfo.id, pointAttributeName, HAPI_AttributeOwner.HAPI_ATTROWNER_POINT, ref pointAttributeInfo)) { if (pointAttributeName.Equals(HEU_Defines.HAPI_ATTRIB_POSITION)) { if (pointAttributeInfo.storage != HAPI_StorageType.HAPI_STORAGETYPE_FLOAT) { Debug.LogErrorFormat("Expected float type for position attribute, but got {0}", pointAttributeInfo.storage); return; } _positionAttributeValues = new Vector3[pointAttributeInfo.count]; float[] data = new float[0]; HEU_GeneralUtility.GetAttribute(session, geoID, partInfo.id, pointAttributeName, ref pointAttributeInfo, ref data, session.GetAttributeFloatData); for (int i = 0; i < pointAttributeInfo.count; ++i) { _positionAttributeValues[i] = new Vector3(-data[i * pointAttributeInfo.tupleSize + 0], data[i * pointAttributeInfo.tupleSize + 1], data[i * pointAttributeInfo.tupleSize + 2]); } // We don't let position attributes be editted (for now anyway) continue; } HEU_AttributeData attrData = GetAttributeData(pointAttributeName); if (attrData == null) { // Attribute data not found. Create it. attrData = CreateAttribute(pointAttributeName, ref pointAttributeInfo); //Debug.LogFormat("Created attribute data: {0}", pointAttributeName); } // Add to new list. newAttributeDatas.Add(attrData); // Sync the attribute info to data. PopulateAttributeData(session, geoID, partInfo.id, attrData, ref pointAttributeInfo); if(pointAttributeName.Equals(HEU_Defines.HAPI_ATTRIB_COLOR) || pointAttributeInfo.typeInfo == HAPI_AttributeTypeInfo.HAPI_ATTRIBUTE_TYPE_COLOR) { _hasColorAttribute = true; } } else { // Failed to get point attribute info! } } // Overwriting the old list with the new should automatically remove unused attribute datas. _attributeDatas = newAttributeDatas; }
/// <summary> /// Load the geometry generated as results of the given work item, of the given TOP node. /// The load will be done asynchronously. /// Results must be tagged with 'file', and must have a file path, otherwise will not be loaded. /// </summary> /// <param name="session">Houdini Engine session that the TOP node is in</param> /// <param name="topNode">TOP node that the work item belongs to</param> /// <param name="workItemInfo">Work item whose results to load</param> /// <param name="resultInfos">Results data</param> /// <param name="workItemID">The work item's ID. Required for clearning its results.</param> public void LoadResults(HEU_SessionBase session, HEU_TOPNodeData topNode, HAPI_PDG_WorkitemInfo workItemInfo, HAPI_PDG_WorkitemResultInfo[] resultInfos, HAPI_PDG_WorkitemId workItemID) { // Create HEU_GeoSync objects, set results, and sync it string workItemName = HEU_SessionManager.GetString(workItemInfo.nameSH, session); //Debug.LogFormat("Work item: {0}:: name={1}, results={2}", workItemInfo.index, workItemName, workItemInfo.numResults); // Clear previously generated result ClearWorkItemResultByIndex(topNode, workItemInfo.index); if (resultInfos == null || resultInfos.Length == 0) { return; } HEU_TOPWorkResult result = GetWorkResultByIndex(topNode, workItemInfo.index); if (result == null) { result = new HEU_TOPWorkResult(); result._workItemIndex = workItemInfo.index; result._workItemID = workItemID; topNode._workResults.Add(result); } // Load each result geometry int numResults = resultInfos.Length; for (int i = 0; i < numResults; ++i) { if (resultInfos[i].resultTagSH <= 0 || resultInfos[i].resultSH <= 0) { continue; } string tag = HEU_SessionManager.GetString(resultInfos[i].resultTagSH, session); string path = HEU_SessionManager.GetString(resultInfos[i].resultSH, session); //Debug.LogFormat("Result for work item {0}: result={1}, tag={2}, path={3}", result._workItemIndex, i, tag, path); if (string.IsNullOrEmpty(tag) || !tag.StartsWith("file")) { continue; } string name = string.Format("{0}_{1}_{2}", topNode._parentName, workItemName, workItemInfo.index); // Get or create parent GO if (topNode._workResultParentGO == null) { topNode._workResultParentGO = new GameObject(topNode._nodeName); HEU_GeneralUtility.SetParentWithCleanTransform(GetLoadRootTransform(), topNode._workResultParentGO.transform); topNode._workResultParentGO.SetActive(topNode._showResults); } GameObject newGO = new GameObject(name); HEU_GeneralUtility.SetParentWithCleanTransform(topNode._workResultParentGO.transform, newGO.transform); result._generatedGOs.Add(newGO); // HEU_GeoSync does the loading HEU_GeoSync geoSync = newGO.AddComponent<HEU_GeoSync>(); if (geoSync != null) { geoSync._filePath = path; geoSync.StartSync(); } } }
/// <summary> /// This reverts local modifcations, refresh upstream inputs, and cooks the editable node. /// This ensures that the node will use the latest upstream input data before applying its own changes. /// </summary> /// <param name="session"></param> public void RefreshUpstreamInputs(HEU_SessionBase session) { session.RevertGeo(_geoID); HEU_HAPIUtility.CookNodeInHoudini(session, _geoID, false, ""); }
/// <summary> /// Retrieves object info from Houdini session and updates internal state. /// New geo nodes are created, unused geo nodes are destroyed. /// Geo nodes are then refreshed to be in sync with Houdini session. /// </summary> /// <returns>True if internal state has changed (including geometry).</returns> public void UpdateObject(HEU_SessionBase session, bool bForceUpdate) { // Update the geo info if (!session.GetObjectInfo(ObjectID, ref _objectInfo)) { return; } SyncWithObjectInfo(session); // Update the object transform _objectTransform = ParentAsset.GetObjectTransform(session, ObjectID); bool bApplyHAPITransform = false; // Container for existing geo nodes that are still in use List<HEU_GeoNode> geoNodesToKeep = new List<HEU_GeoNode>(); // Container for new geo infos that need to be created List<HAPI_GeoInfo> newGeoInfosToCreate = new List<HAPI_GeoInfo>(); if (_objectInfo.haveGeosChanged || bForceUpdate) { // Indicates that the geometry nodes have changed //Debug.Log("Geos have changed!"); // Form a list of geo infos that are now present after cooking List<HAPI_GeoInfo> postCookGeoInfos = new List<HAPI_GeoInfo>(); // Get the display geo info HAPI_GeoInfo displayGeoInfo = new HAPI_GeoInfo(); if (!session.GetDisplayGeoInfo(_objectInfo.nodeId, ref displayGeoInfo)) { return; } postCookGeoInfos.Add(displayGeoInfo); // Get editable nodes, cook em, then create geo nodes for them HAPI_NodeId[] editableNodes = null; HEU_SessionManager.GetComposedChildNodeList(session, _objectInfo.nodeId, (int)HAPI_NodeType.HAPI_NODETYPE_SOP, (int)HAPI_NodeFlags.HAPI_NODEFLAGS_EDITABLE, true, out editableNodes); if (editableNodes != null) { foreach (HAPI_NodeId editNodeID in editableNodes) { if (editNodeID != displayGeoInfo.nodeId) { session.CookNode(editNodeID, HEU_PluginSettings.CookTemplatedGeos); HAPI_GeoInfo editGeoInfo = new HAPI_GeoInfo(); if (session.GetGeoInfo(editNodeID, ref editGeoInfo)) { postCookGeoInfos.Add(editGeoInfo); } } } } // Now for each geo node that are present after cooking, we check if its // new or whether we already have it prior to cooking. int numPostCookGeoInfos = postCookGeoInfos.Count; for (int i = 0; i < numPostCookGeoInfos; i++) { bool bFound = false; for (int j = 0; j < _geoNodes.Count; j++) { string geoName = HEU_SessionManager.GetString(postCookGeoInfos[i].nameSH, session); if(geoName.Equals(_geoNodes[j].GeoName)) { _geoNodes[j].SetGeoInfo(postCookGeoInfos[i]); geoNodesToKeep.Add(_geoNodes[j]); _geoNodes.RemoveAt(j); bFound = true; break; } } if (!bFound) { newGeoInfosToCreate.Add(postCookGeoInfos[i]); } } // Whatever is left in _geoNodes is no longer needed so clean up int numCurrentGeos = _geoNodes.Count; for(int i = 0; i < numCurrentGeos; ++i) { _geoNodes[i].DestroyAllData(); } bApplyHAPITransform = true; } else { Debug.Assert(_objectInfo.geoCount == _geoNodes.Count, "Expected same number of geometry nodes."); } // Go through the old geo nodes that are still in use and update if necessary. foreach (HEU_GeoNode geoNode in geoNodesToKeep) { // Get geo info and check if geo changed bool bGeoChanged = bForceUpdate || geoNode.HasGeoNodeChanged(session); if(bGeoChanged) { geoNode.UpdateGeo(session); } else { if (_objectInfo.haveGeosChanged) { // Clear object instances since the object info has changed. // Without this, the object instances were never getting updated // if only the inputs changed but not outputs (of instancers). geoNode.ClearObjectInstances(); } // Visiblity might have changed, so update that geoNode.CalculateVisiblity(IsVisible()); } } // Create the new geo infos and add to our keep list foreach (HAPI_GeoInfo newGeoInfo in newGeoInfosToCreate) { geoNodesToKeep.Add(CreateGeoNode(session, newGeoInfo)); } // Overwrite the old list with new _geoNodes = geoNodesToKeep; // Update transform to all geo nodes whether they were created newly, or // this object's transform has changed if (bApplyHAPITransform || bForceUpdate || _objectInfo.hasTransformChanged) { // This has been moved to GenerateGeometry but kept here just in case. //ApplyObjectTransformToGeoNodes(); } }
// LOGIC ----------------------------------------------------------------------------------------------------- public bool SetupHandle(HEU_SessionBase session, HAPI_NodeId assetID, int handleIndex, string handleName, HEU_HandleType handleType, ref HAPI_HandleInfo handleInfo, HEU_Parameters parameters) { _handleIndex = handleIndex; _handleName = handleName; _handleType = handleType; HAPI_HandleBindingInfo[] handleBindingInfos = new HAPI_HandleBindingInfo[handleInfo.bindingsCount]; if (!session.GetHandleBindingInfo(assetID, _handleIndex, handleBindingInfos, 0, handleInfo.bindingsCount)) { return false; } HAPI_ParmId translateParmID = -1; HAPI_ParmId rotateParmID = -1; HAPI_ParmId scaleParmID = -1; HAPI_ParmId rstOrderParmID = -1; HAPI_ParmId xyzOrderParmID = -1; _rstOrder = HAPI_RSTOrder.HAPI_SRT; _xyzOrder = HAPI_XYZOrder.HAPI_XYZ; _handleParamTranslateBinding = null; _handleParamRotateBinding = null; _handleParamScaleBinding = null; for (int i = 0; i < handleBindingInfos.Length; ++i) { string parmName = HEU_SessionManager.GetString(handleBindingInfos[i].handleParmNameSH, session); //string assetParmName = HEU_SessionManager.GetString(handleBindingInfos[i].assetParmNameSH, session); //Debug.LogFormat("Handle {0} has parm {1} with asset parm {2} with asset parm id {3}", handleName, parmName, assetParmName, handleBindingInfos[i].assetParmId); if (parmName.Equals("tx") || parmName.Equals("ty") || parmName.Equals("tz")) { translateParmID = handleBindingInfos[i].assetParmId; if(_handleParamTranslateBinding == null) { HEU_ParameterData parmData = parameters.GetParameterWithParmID(translateParmID); if (parmData != null && !parmData._parmInfo.invisible) { _handleParamTranslateBinding = new HEU_HandleParamBinding(); _handleParamTranslateBinding._paramType = HEU_HandleParamBinding.HEU_HandleParamType.TRANSLATE; _handleParamTranslateBinding._parmID = parmData.ParmID; _handleParamTranslateBinding._paramName = parmData._name; _handleParamTranslateBinding._bDisabled = parmData._parmInfo.disabled; } } if(_handleParamTranslateBinding != null) { if(parmName.Equals("tx")) { _handleParamTranslateBinding._boundChannels[0] = true; } else if (parmName.Equals("ty")) { _handleParamTranslateBinding._boundChannels[1] = true; } else if (parmName.Equals("tz")) { _handleParamTranslateBinding._boundChannels[2] = true; } } } if (parmName.Equals("rx") || parmName.Equals("ry") || parmName.Equals("rz")) { rotateParmID = handleBindingInfos[i].assetParmId; if(_handleParamRotateBinding == null) { HEU_ParameterData parmData = parameters.GetParameterWithParmID(rotateParmID); if (parmData != null && !parmData._parmInfo.invisible) { _handleParamRotateBinding = new HEU_HandleParamBinding(); _handleParamRotateBinding._paramType = HEU_HandleParamBinding.HEU_HandleParamType.ROTATE; _handleParamRotateBinding._parmID = parmData.ParmID; _handleParamRotateBinding._paramName = parmData._name; _handleParamRotateBinding._bDisabled = parmData._parmInfo.disabled; } } if (_handleParamRotateBinding != null) { if (parmName.Equals("rx")) { _handleParamRotateBinding._boundChannels[0] = true; } else if (parmName.Equals("ry")) { _handleParamRotateBinding._boundChannels[1] = true; } else if (parmName.Equals("rz")) { _handleParamRotateBinding._boundChannels[2] = true; } } } if (parmName.Equals("sx") || parmName.Equals("sy") || parmName.Equals("sz")) { scaleParmID = handleBindingInfos[i].assetParmId; if (_handleParamScaleBinding == null) { HEU_ParameterData parmData = parameters.GetParameterWithParmID(scaleParmID); if (parmData != null && !parmData._parmInfo.invisible) { _handleParamScaleBinding = new HEU_HandleParamBinding(); _handleParamScaleBinding._paramType = HEU_HandleParamBinding.HEU_HandleParamType.SCALE; _handleParamScaleBinding._parmID = parmData.ParmID; _handleParamScaleBinding._paramName = parmData._name; _handleParamScaleBinding._bDisabled = parmData._parmInfo.disabled; } } if (_handleParamScaleBinding != null) { if (parmName.Equals("sx")) { _handleParamScaleBinding._boundChannels[0] = true; } else if (parmName.Equals("sy")) { _handleParamScaleBinding._boundChannels[1] = true; } else if (parmName.Equals("sz")) { _handleParamScaleBinding._boundChannels[2] = true; } } } if(parmName.Equals("trs_order")) { rstOrderParmID = handleBindingInfos[i].assetParmId; } if (parmName.Equals("xyz_order")) { xyzOrderParmID = handleBindingInfos[i].assetParmId; } } if (rstOrderParmID >= 0) { HEU_ParameterData parmData = parameters.GetParameter(rstOrderParmID); if (parmData != null) { _rstOrder = (HAPI_RSTOrder)parmData._intValues[0]; } } if (xyzOrderParmID >= 0) { HEU_ParameterData parmData = parameters.GetParameter(xyzOrderParmID); if (parmData != null) { _xyzOrder = (HAPI_XYZOrder)parmData._intValues[0]; } } GenerateTransform(session, parameters); return true; }
public void GenerateGeometry(HEU_SessionBase session) { // Volumes could come in as a geonode + part for each heightfield layer. // Otherwise the other geo types can be done individually. bool bResult = false; List<HEU_PartData> meshParts = new List<HEU_PartData>(); List<HEU_PartData> volumeParts = new List<HEU_PartData>(); List<HEU_PartData> partsToDestroy = new List<HEU_PartData>(); HEU_HoudiniAsset parentAsset = ParentAsset; foreach (HEU_GeoNode geoNode in _geoNodes) { geoNode.GetPartsByOutputType(meshParts, volumeParts); if(volumeParts.Count > 0) { // Volumes // Each layer in the volume is retrieved as a volume part, in the display geo node. // But we need to handle all layers as 1 terrain output in Unity, with 1 height layer and // other layers as alphamaps. geoNode.ProcessVolumeParts(session, volumeParts); // Clear the volume parts after processing since we are done with this set volumeParts.Clear(); } } // Meshes foreach (HEU_PartData part in meshParts) { // This returns false when there is no valid geometry or is not instancing. Should remove it as otherwise // stale data sticks around on recook bResult = part.GenerateMesh(session, parentAsset.GenerateUVs, parentAsset.GenerateTangents, parentAsset.GenerateNormals, parentAsset.UseLODGroups); if (!bResult) { partsToDestroy.Add(part); } } int numPartsToDestroy = partsToDestroy.Count; for(int i = 0; i < numPartsToDestroy; ++i) { HEU_GeoNode parentNode = partsToDestroy[i].ParentGeoNode; if (parentNode != null) { parentNode.RemoveAndDestroyPart(partsToDestroy[i]); } else { HEU_PartData.DestroyPart(partsToDestroy[i]); } } partsToDestroy.Clear(); ApplyObjectTransformToGeoNodes(); // Set visibility and attribute-based tag, layer, and scripts bool bIsVisible = IsVisible(); foreach (HEU_GeoNode geoNode in _geoNodes) { geoNode.CalculateVisiblity(bIsVisible); geoNode.SetAttributeModifiersOnPartOutputs(session); } // Create editable attributes. // This should happen after visibility has been calculated above // since we need to show/hide the intermediate geometry during painting. foreach (HEU_PartData part in meshParts) { if (part.ParentGeoNode.IsIntermediateOrEditable()) { part.SetupAttributeGeometry(session); } } }
public static void ExecuteToolOperatorSingle(string toolName, string toolPath, GameObject[] inputObjects) { // Single operator means single asset input. If multiple inputs are provided, create tool for each input. bool bShouldUseHDA = ShouldUseHDA(inputObjects); List<GameObject> outputObjectsToSelect = new List<GameObject>(); int numInputs = inputObjects.Length; for (int i = 0; i < numInputs; ++i) { if (inputObjects[i] == null) { continue; } if (!bShouldUseHDA && !IsValidInputMesh(inputObjects[i])) { HEU_Logger.LogWarningFormat("Specified object {0} does not contain a valid mesh!", inputObjects[i].name); continue; } if (bShouldUseHDA && !IsValidInputHDA(inputObjects[i])) { HEU_Logger.LogWarningFormat("Specified object {0} does not contain a valid HDA input!", inputObjects[i].name); continue; } GameObject inputObject = inputObjects[i]; GameObject go = HEU_HAPIUtility.InstantiateHDA(toolPath, Vector3.zero, HEU_SessionManager.GetOrCreateDefaultSession(), false); if (go != null) { HEU_HoudiniAssetRoot assetRoot = go.GetComponent<HEU_HoudiniAssetRoot>(); if (assetRoot != null) { HEU_HoudiniAsset asset = assetRoot._houdiniAsset; HEU_SessionBase session = asset.GetAssetSession(true); List<HEU_InputNode> inputNodes = asset.GetInputNodes(); if (inputNodes == null || inputNodes.Count == 0) { HEU_Logger.LogErrorFormat("Unable to assign input geometry due to no asset inputs on selected tool."); } else { HEU_InputNode inputNode = inputNodes[0]; inputNode.ResetInputNode(session); if (!bShouldUseHDA) { inputNode.ChangeInputType(session, HEU_InputNode.InputObjectType.UNITY_MESH); HEU_InputObjectInfo inputInfo = inputNode.AddInputEntryAtEndMesh(inputObject); if (inputInfo != null) { inputInfo._useTransformOffset = false; inputNode.KeepWorldTransform = true; inputNode.PackGeometryBeforeMerging = false; inputNode.RequiresUpload = true; asset.RequestCook(true, true, true, true); outputObjectsToSelect.Add(assetRoot.gameObject); } else { HEU_Logger.LogErrorFormat("Invalid input format: {0}", inputObject.gameObject.name); } } else { inputNode.ChangeInputType(session, HEU_InputNode.InputObjectType.HDA); HEU_InputHDAInfo inputHDAInfo = inputNode.AddInputEntryAtEndHDA(inputObject); if (inputHDAInfo != null) { inputNode.KeepWorldTransform = true; inputNode.PackGeometryBeforeMerging = false; inputNode.RequiresUpload = true; asset.RequestCook(true, true, true, true); outputObjectsToSelect.Add(assetRoot.gameObject); } else { HEU_Logger.LogErrorFormat("Invalid input format: {0}", inputObject.gameObject.name); } } } } } else { HEU_Logger.LogWarningFormat("Failed to instantiate tool: {0}", toolName); } } if (outputObjectsToSelect.Count > 0) { HEU_EditorUtility.SelectObjects(outputObjectsToSelect.ToArray()); } }
/// <summary> /// Generates object instances. /// Skips parts that already have their instances generated. /// </summary> /// <param name="session">Active session to use</param> public void GenerateObjectInstances(HEU_SessionBase session) { if (!IsInstancer()) { Debug.LogErrorFormat("Generate object instances called on a non-instancer object {0} for asset {1}!", ObjectName, ParentAsset.AssetName); return; } //Debug.LogFormat("Generate Object Instances:: id={5}, name={0}, isInstancer={1}, isInstanced={2}, instancePath={3}, instanceId={4}", HEU_SessionManager.GetString(_objectInfo.nameSH, session), // _objectInfo.isInstancer, _objectInfo.isInstanced, HEU_SessionManager.GetString(_objectInfo.objectInstancePathSH, session), _objectInfo.objectToInstanceId, _objectInfo.nodeId); // Is this a Houdini attribute instancer? string instanceAttrName = HEU_PluginSettings.InstanceAttr; string unityInstanceAttrName = HEU_PluginSettings.UnityInstanceAttr; string instancePrefixAttrName = HEU_Defines.DEFAULT_INSTANCE_PREFIX_ATTR; HAPI_AttributeInfo instanceAttrInfo = new HAPI_AttributeInfo(); HAPI_AttributeInfo unityInstanceAttrInfo = new HAPI_AttributeInfo(); HAPI_AttributeInfo instancePrefixAttrInfo = new HAPI_AttributeInfo(); int numGeos = _geoNodes.Count; for(int i = 0; i < numGeos; ++i) { if(_geoNodes[i].Displayable) { List<HEU_PartData> parts = _geoNodes[i].GetParts(); int numParts = parts.Count; for(int j = 0; j < numParts; ++j) { if(parts[j].ObjectInstancesBeenGenerated) { // This prevents instances being created unnecessarily (e.g. part hasn't changed since last cook) continue; } HEU_GeneralUtility.GetAttributeInfo(session, _geoNodes[i].GeoID, parts[j].PartID, instanceAttrName, ref instanceAttrInfo); HEU_GeneralUtility.GetAttributeInfo(session, _geoNodes[i].GeoID, parts[j].PartID, unityInstanceAttrName, ref unityInstanceAttrInfo); string[] instancePrefixes = null; HEU_GeneralUtility.GetAttributeInfo(session, _geoNodes[i].GeoID, parts[j].PartID, instancePrefixAttrName, ref instancePrefixAttrInfo); if(instancePrefixAttrInfo.exists) { instancePrefixes = HEU_GeneralUtility.GetAttributeStringData(session, _geoNodes[i].GeoID, parts[j].PartID, instancePrefixAttrName, ref instancePrefixAttrInfo); } // Must clear out instances, as otherwise we get duplicates parts[j].ClearInstances(); // Clear out invalid object instance infos that no longer have any valid parts parts[j].ClearInvalidObjectInstanceInfos(); if (instanceAttrInfo.exists) { // Object instancing via Houdini instance attribute parts[j].GenerateInstancesFromObjectIds(session, instancePrefixes); } else if (unityInstanceAttrInfo.exists) { // Object instancing via existing Unity object (path from point attribute) // Attribute owner type determines whether to use single instanced object (detail) or multiple (point) if (unityInstanceAttrInfo.owner == HAPI_AttributeOwner.HAPI_ATTROWNER_POINT) { parts[j].GenerateInstancesFromUnityAssetPathAttribute(session, unityInstanceAttrName); } else if(unityInstanceAttrInfo.owner == HAPI_AttributeOwner.HAPI_ATTROWNER_DETAIL) { bool bInstanced = false; int[] scriptAttr = new int[unityInstanceAttrInfo.count]; if(session.GetAttributeStringData(_geoNodes[i].GeoID, parts[j].PartID, unityInstanceAttrName, ref unityInstanceAttrInfo, scriptAttr, 0, unityInstanceAttrInfo.count)) { string assetPath = HEU_SessionManager.GetString(scriptAttr[0]); if (!string.IsNullOrEmpty(assetPath)) { parts[j].GenerateInstancesFromUnityAssetPath(session, assetPath, instancePrefixes); bInstanced = true; } } if (!bInstanced) { Debug.LogWarningFormat("Unable to get instanced object path from detail instance attribute!"); } } else { // Other attribute owned types are unsupported Debug.LogWarningFormat("Unsupported attribute owner {0} for attribute {1}", unityInstanceAttrInfo.owner, unityInstanceAttrName); } } else { // Standard object instancing via single Houdini object if (_objectInfo.objectToInstanceId == HEU_Defines.HEU_INVALID_NODE_ID) { Debug.LogAssertionFormat("Invalid object ID {0} used for object instancing. " + "Make sure to turn on Full point instancing and set the correct Instance Object.", _objectInfo.objectToInstanceId); continue; } parts[j].GenerateInstancesFromObjectID(session, _objectInfo.objectToInstanceId, instancePrefixes); } } } } }
/// <summary> /// Retrieves the heightmap from Houdini for the given volume part, converts to Unity coordinates, /// normalizes to 0 and 1, along with min and max height values, as well as the range. /// </summary> /// <param name="session">Current Houdini session</param> /// <param name="geoID">Geometry object ID</param> /// <param name="partID">The volume part ID</param> /// <param name="heightMapSize">Size of each dimension of the heightmap (assumes equal sides).</param> /// <param name="minHeight">Found minimum height value in the heightmap.</param> /// <param name="maxHeight">Found maximum height value in the heightmap.</param> /// <param name="heightRange">Found height range in the heightmap.</param> /// <returns>The converted heightmap from Houdini.</returns> public static float[] GetNormalizedHeightmapFromPartWithMinMax(HEU_SessionBase session, HAPI_NodeId geoID, HAPI_PartId partID, int heightMapWidth, int heightMapHeight, ref float minHeight, ref float maxHeight, ref float heightRange, bool bUseHeightRangeOverride) { minHeight = float.MaxValue; maxHeight = float.MinValue; heightRange = 1; HAPI_VolumeInfo volumeInfo = new HAPI_VolumeInfo(); bool bResult = session.GetVolumeInfo(geoID, partID, ref volumeInfo); if (!bResult) { return null; } int volumeXLength = volumeInfo.xLength; int volumeYLength = volumeInfo.yLength; // Number of heightfield values int totalHeightValues = volumeXLength * volumeYLength; float[] heightValues = new float[totalHeightValues]; if (!GetHeightmapFromPart(session, volumeXLength, volumeYLength, geoID, partID, ref heightValues, ref minHeight, ref maxHeight)) { return null; } heightRange = (maxHeight - minHeight); // Use the override height range if user has set via attribute bool bHeightRangeOverriden = false; if (bUseHeightRangeOverride) { float userHeightRange = GetHeightRangeFromHeightfield(session, geoID, partID); if (userHeightRange > 0) { heightRange = userHeightRange; bHeightRangeOverriden = true; } } if (heightRange == 0f) { // Always use a non-zero height range, otherwise user can't paint height on Terrain. heightRange = 1f; } //Debug.LogFormat("{0} : {1}", HEU_SessionManager.GetString(volumeInfo.nameSH, session), heightRange); const int UNITY_MAX_HEIGHT_RANGE = 65536; if (Mathf.RoundToInt(heightRange) > UNITY_MAX_HEIGHT_RANGE) { Debug.LogWarningFormat("Unity Terrain has maximum height range of {0}. This HDA height range is {1}, so it will be maxed out at {0}.\nPlease resize to within valid range!", UNITY_MAX_HEIGHT_RANGE, Mathf.RoundToInt(heightRange)); heightRange = UNITY_MAX_HEIGHT_RANGE; } // Remap height values to fit terrain size int paddingWidth = heightMapWidth - volumeXLength; int paddingLeft = Mathf.CeilToInt(paddingWidth * 0.5f); int paddingRight = heightMapWidth - paddingLeft; //Debug.LogFormat("Padding: Width={0}, Left={1}, Right={2}", paddingWidth, paddingLeft, paddingRight); int paddingHeight = heightMapHeight - volumeYLength; int paddingTop = Mathf.CeilToInt(paddingHeight * 0.5f); int paddingBottom = heightMapHeight - paddingTop; //Debug.LogFormat("Padding: Height={0}, Top={1}, Bottom={2}", paddingHeight, paddingTop, paddingBottom); // Normalize the height values into the range between 0 and 1, inclusive. float inverseHeightRange = 1f / heightRange; float normalizeMinHeight = minHeight; if (minHeight >= 0f && minHeight <= 1f && maxHeight >= 0f && maxHeight <= 1f) { // Its important to leave the values alone if they are already normalized. // So these values don't actually do anything in the normalization calculation below. inverseHeightRange = 1f; normalizeMinHeight = 0f; } // Set height values at centre of the terrain, with padding on the sides if we resized float[] resizedHeightValues = new float[heightMapWidth * heightMapHeight]; for (int y = 0; y < heightMapHeight; ++y) { for (int x = 0; x < heightMapWidth; ++x) { if (y >= paddingTop && y < (paddingBottom) && x >= paddingLeft && x < (paddingRight)) { int ay = x - paddingLeft; int ax = y - paddingTop; float f = heightValues[ay + ax * volumeXLength]; if (!bHeightRangeOverriden) { f -= normalizeMinHeight; } f *= inverseHeightRange; // Flip for right-hand to left-handed coordinate system int ix = x; int iy = heightMapHeight - (y + 1); // Unity expects height array indexing to be [y, x]. resizedHeightValues[iy + ix * heightMapWidth] = f; } } } return resizedHeightValues; }
private void SyncWithObjectInfo(HEU_SessionBase session) { _objName = HEU_SessionManager.GetString(_objectInfo.nameSH, session); }
/// <summary> /// Grab the scatter data for the given part. /// This finds the properties of TreeInstances via attributes. /// </summary> /// <param name="session">Houdini session</param> /// <param name="geoID">Geometry ID</param> /// <param name="partID">Part (volume layer) ID</param> /// <param name="pointCount">Number of expected scatter points</param> public static void PopulateScatterTrees(HEU_SessionBase session, HAPI_NodeId geoID, HAPI_PartId partID, int pointCount, ref HEU_VolumeScatterTrees scatterTrees) { // The HEU_VolumeScatterTrees might already have been created when the volumecache was queried. // The "height" layer might have had prototype data which is set in _scatterTrees. if (scatterTrees == null) { scatterTrees = new HEU_VolumeScatterTrees(); } // Get prototype indices. These indices refer to _scatterTrees._treePrototypes. HAPI_AttributeInfo indicesAttrInfo = new HAPI_AttributeInfo(); int[] indices = new int[0]; if (HEU_GeneralUtility.GetAttribute(session, geoID, partID, HEU_Defines.HEIGHTFIELD_TREEINSTANCE_PROTOTYPEINDEX, ref indicesAttrInfo, ref indices, session.GetAttributeIntData)) { if (indices != null && indices.Length == pointCount) { scatterTrees._prototypeIndices = indices; } else { Debug.LogWarningFormat("Scatter instance index count for attribute {0} is not valid. Expected {1} but got {2}", HEU_Defines.HEIGHTFIELD_TREEINSTANCE_PROTOTYPEINDEX, pointCount, (indices != null ? indices.Length : 0)); } } // Using the UVs as position of the instances, since they are properly mapped to the terrain tile. // Also getting other attributes for the TreeInstances, if they are set. HAPI_AttributeInfo uvAttrInfo = new HAPI_AttributeInfo(); float[] uvs = new float[0]; if (!HEU_GeneralUtility.GetAttribute(session, geoID, partID, HEU_Defines.HAPI_ATTRIB_UV, ref uvAttrInfo, ref uvs, session.GetAttributeFloatData)) { Debug.LogWarning("UVs for scatter instances not found or valid."); } if (uvs != null && uvs.Length == (pointCount * uvAttrInfo.tupleSize)) { // Get height scales HAPI_AttributeInfo heightAttrInfo = new HAPI_AttributeInfo(); float[] heightscales = new float[0]; HEU_GeneralUtility.GetAttribute(session, geoID, partID, HEU_Defines.HEIGHTFIELD_TREEINSTANCE_HEIGHTSCALE, ref heightAttrInfo, ref heightscales, session.GetAttributeFloatData); // Get width scales HAPI_AttributeInfo widthAttrInfo = new HAPI_AttributeInfo(); float[] widthscales = new float[0]; HEU_GeneralUtility.GetAttribute(session, geoID, partID, HEU_Defines.HEIGHTFIELD_TREEINSTANCE_WIDTHSCALE, ref widthAttrInfo, ref widthscales, session.GetAttributeFloatData); // Get orientation HAPI_AttributeInfo orientAttrInfo = new HAPI_AttributeInfo(); float[] orients = new float[0]; HEU_GeneralUtility.GetAttribute(session, geoID, partID, HEU_Defines.HAPI_ATTRIB_ORIENT, ref orientAttrInfo, ref orients, session.GetAttributeFloatData); // Get color HAPI_AttributeInfo colorAttrInfo = new HAPI_AttributeInfo(); float[] colors = new float[0]; HEU_GeneralUtility.GetAttribute(session, geoID, partID, HEU_Defines.HAPI_ATTRIB_COLOR, ref colorAttrInfo, ref colors, session.GetAttributeFloatData); // Get lightmap color HAPI_AttributeInfo lightmapColorAttrInfo = new HAPI_AttributeInfo(); float[] lightmapColors = new float[0]; HEU_GeneralUtility.GetAttribute(session, geoID, partID, HEU_Defines.HEIGHTFIELD_TREEINSTANCE_LIGHTMAPCOLOR, ref lightmapColorAttrInfo, ref lightmapColors, session.GetAttributeFloatData); scatterTrees._positions = new Vector3[pointCount]; if (heightAttrInfo.exists && (heightscales.Length == pointCount)) { scatterTrees._heightScales = heightscales; } if (widthAttrInfo.exists && (widthscales.Length == pointCount)) { scatterTrees._widthScales = widthscales; } if (orientAttrInfo.exists && (orients.Length == orientAttrInfo.tupleSize * pointCount)) { scatterTrees._rotations = new float[pointCount]; } if (colorAttrInfo.exists && (colors.Length == colorAttrInfo.tupleSize * pointCount)) { scatterTrees._colors = new Color32[pointCount]; } if (lightmapColorAttrInfo.exists && (lightmapColors.Length == lightmapColorAttrInfo.tupleSize * pointCount)) { scatterTrees._lightmapColors = new Color32[pointCount]; } for (int i = 0; i < pointCount; ++i) { scatterTrees._positions[i] = new Vector3(1.0f - uvs[i * uvAttrInfo.tupleSize + 1], 0, uvs[i * uvAttrInfo.tupleSize + 0]); if (scatterTrees._colors != null) { scatterTrees._colors[i] = new Color32((byte)(colors[i * colorAttrInfo.tupleSize + 0] * 255), (byte)(colors[i * colorAttrInfo.tupleSize + 1] * 255), (byte)(colors[i * colorAttrInfo.tupleSize + 2] * 255), (byte)(colors[i * colorAttrInfo.tupleSize + 3] * 255)); } if (scatterTrees._lightmapColors != null) { scatterTrees._lightmapColors[i] = new Color32((byte)(lightmapColors[i * lightmapColorAttrInfo.tupleSize + 0] * 255), (byte)(lightmapColors[i * lightmapColorAttrInfo.tupleSize + 1] * 255), (byte)(lightmapColors[i * lightmapColorAttrInfo.tupleSize + 2] * 255), (byte)(lightmapColors[i * lightmapColorAttrInfo.tupleSize + 3] * 255)); } if (scatterTrees._rotations != null) { Quaternion quaternion = new Quaternion( orients[i * orientAttrInfo.tupleSize + 0], orients[i * orientAttrInfo.tupleSize + 1], orients[i * orientAttrInfo.tupleSize + 2], orients[i * orientAttrInfo.tupleSize + 3]); Vector3 euler = quaternion.eulerAngles; euler.y = -euler.y; euler.z = -euler.z; scatterTrees._rotations[i] = euler.y * Mathf.Deg2Rad; } } } }
/// <summary> /// Upload the inputData (mesh geometry) into the input node with inputNodeID. /// </summary> /// <param name="session">Session that the input node exists in</param> /// <param name="inputNodeID">ID of the input node</param> /// <param name="inputData">Container of the mesh geometry</param> /// <returns>True if successfully uploaded data</returns> public bool UploadData(HEU_SessionBase session, HAPI_NodeId inputNodeID, HEU_InputData inputData) { HEU_InputDataMeshes inputDataMeshes = inputData as HEU_InputDataMeshes; if (inputDataMeshes == null) { Debug.LogError("Expected HEU_InputDataMeshes type for inputData, but received unsupported type."); return false; } List<Vector3> vertices = new List<Vector3>(); List<Vector3> normals = new List<Vector3>(); List<Color> colors = new List<Color>(); #if UNITY_2018_2_OR_NEWER const int NumUVSets = 8; #else const int NumUVSets = 4; #endif List<Vector3>[] uvs = new List<Vector3>[NumUVSets]; for(int u = 0; u < NumUVSets; ++u) { uvs[u] = new List<Vector3>(); } // Use tempUVs to help with reindexing List<Vector3>[] tempUVs = new List<Vector3>[NumUVSets]; for (int u = 0; u < NumUVSets; ++u) { tempUVs[u] = new List<Vector3>(); } List<int> pointIndexList = new List<int>(); List<int> vertIndexList = new List<int>(); int numMaterials = 0; int numMeshes = inputDataMeshes._inputMeshes.Count; // Get the parent's world transform, so when there are multiple child meshes, // can merge and apply their local transform after subtracting their parent's world transform Matrix4x4 rootInvertTransformMatrix = Matrix4x4.identity; if (numMeshes > 1) { rootInvertTransformMatrix = inputDataMeshes._inputObject.transform.worldToLocalMatrix; } // Always using the first submesh topology. This doesn't support mixed topology (triangles and quads). MeshTopology meshTopology = inputDataMeshes._inputMeshes[0]._mesh.GetTopology(0); int numVertsPerFace = 3; if (meshTopology == MeshTopology.Quads) { numVertsPerFace = 4; } // For all meshes: // Accumulate vertices, normals, uvs, colors, and indices. // Keep track of indices start and count for each mesh for later when uploading material assignments and groups. // Find shared vertices, and use unique set of vertices to use as point positions. // Need to reindex indices for both unique vertices, as well as vertex attributes. for (int i = 0; i < numMeshes; ++i) { Vector3[] meshVertices = inputDataMeshes._inputMeshes[i]._mesh.vertices; Matrix4x4 localToWorld = rootInvertTransformMatrix * inputDataMeshes._inputMeshes[i]._transform.localToWorldMatrix; List<Vector3> uniqueVertices = new List<Vector3>(); // Keep track of old vertex positions (old vertex slot points to new unique vertex slot) int[] reindexVertices = new int[meshVertices.Length]; Dictionary<Vector3, int> reindexMap = new Dictionary<Vector3, int>(); // For each vertex, check against subsequent vertices for shared positions. for (int a = 0; a < meshVertices.Length; ++a) { Vector3 va = meshVertices[a]; if (!reindexMap.ContainsKey(va)) { if (numMeshes > 1 && !inputDataMeshes._hasLOD) { // For multiple meshes that are not LODs, apply local transform on vertices to get the merged mesh. uniqueVertices.Add(localToWorld.MultiplyPoint(va)); } else { uniqueVertices.Add(va); } // Reindex to point to unique vertex slot reindexVertices[a] = uniqueVertices.Count - 1; reindexMap[va] = uniqueVertices.Count - 1; } else { reindexVertices[a] = reindexMap[va]; } } int vertexOffset = vertices.Count; vertices.AddRange(uniqueVertices); Vector3[] meshNormals = inputDataMeshes._inputMeshes[i]._mesh.normals; Color[] meshColors = inputDataMeshes._inputMeshes[i]._mesh.colors; // This is really silly. mesh.GetUVs gives uvs regardless if they exist or not (makes duplicates of // first uv if they don't exist), but mesh.uv* gives correct UVs, but in Vector2 format. // Since we need to convert to Vector3 later, this checks mesh.uv*, then uses mesh.GetUVs to get in Vector3. // Note skipping uv1 as its internally used (i.e. the 2nd uv set is uv2) int uindex = 0; GetUVsFromMesh(inputDataMeshes._inputMeshes[i]._mesh, inputDataMeshes._inputMeshes[i]._mesh.uv, tempUVs[0], uindex++); GetUVsFromMesh(inputDataMeshes._inputMeshes[i]._mesh, inputDataMeshes._inputMeshes[i]._mesh.uv2, tempUVs[1], uindex++); GetUVsFromMesh(inputDataMeshes._inputMeshes[i]._mesh, inputDataMeshes._inputMeshes[i]._mesh.uv3, tempUVs[2], uindex++); GetUVsFromMesh(inputDataMeshes._inputMeshes[i]._mesh, inputDataMeshes._inputMeshes[i]._mesh.uv4, tempUVs[3], uindex++); #if UNITY_2018_2_OR_NEWER GetUVsFromMesh(inputDataMeshes._inputMeshes[i]._mesh, inputDataMeshes._inputMeshes[i]._mesh.uv5, tempUVs[4], uindex++); GetUVsFromMesh(inputDataMeshes._inputMeshes[i]._mesh, inputDataMeshes._inputMeshes[i]._mesh.uv6, tempUVs[5], uindex++); GetUVsFromMesh(inputDataMeshes._inputMeshes[i]._mesh, inputDataMeshes._inputMeshes[i]._mesh.uv7, tempUVs[6], uindex++); GetUVsFromMesh(inputDataMeshes._inputMeshes[i]._mesh, inputDataMeshes._inputMeshes[i]._mesh.uv8, tempUVs[7], uindex++); #endif inputDataMeshes._inputMeshes[i]._indexStart = new uint[inputDataMeshes._inputMeshes[i]._numSubMeshes]; inputDataMeshes._inputMeshes[i]._indexCount = new uint[inputDataMeshes._inputMeshes[i]._numSubMeshes]; // For each submesh: // Generate face to point index -> pointIndexList // Generate face to vertex attribute index -> vertIndexList for (int j = 0; j < inputDataMeshes._inputMeshes[i]._numSubMeshes; ++j) { int indexStart = pointIndexList.Count; int vertIndexStart = vertIndexList.Count; // Indices have to be re-indexed with our own offset // (using GetIndices to generalize triangles and quad indices) int[] meshIndices = inputDataMeshes._inputMeshes[i]._mesh.GetIndices(j); int numIndices = meshIndices.Length; for (int k = 0; k < numIndices; ++k) { int originalIndex = meshIndices[k]; meshIndices[k] = reindexVertices[originalIndex]; pointIndexList.Add(vertexOffset + meshIndices[k]); vertIndexList.Add(vertIndexStart + k); if (meshNormals != null && (originalIndex < meshNormals.Length)) { normals.Add(meshNormals[originalIndex]); } for (int u = 0; u < NumUVSets; ++u) { if (tempUVs[u].Count > 0) { uvs[u].Add(tempUVs[u][originalIndex]); } } if (meshColors != null && (originalIndex < meshColors.Length)) { colors.Add(meshColors[originalIndex]); } } inputDataMeshes._inputMeshes[i]._indexStart[j] = (uint)indexStart; inputDataMeshes._inputMeshes[i]._indexCount[j] = (uint)(pointIndexList.Count) - inputDataMeshes._inputMeshes[i]._indexStart[j]; } numMaterials += inputDataMeshes._inputMeshes[i]._materials != null ? inputDataMeshes._inputMeshes[i]._materials.Length : 0; } // It is possible for some meshes to not have normals/uvs/colors while others do. // In the case where an attribute is missing on some meshes, we clear out those attributes so we don't upload // partial attribute data. int totalAllVertexCount = vertIndexList.Count; if (normals.Count != totalAllVertexCount) { normals = null; } if (colors.Count != totalAllVertexCount) { colors = null; } HAPI_PartInfo partInfo = new HAPI_PartInfo(); partInfo.faceCount = vertIndexList.Count / numVertsPerFace; partInfo.vertexCount = vertIndexList.Count; partInfo.pointCount = vertices.Count; partInfo.pointAttributeCount = 1; partInfo.vertexAttributeCount = 0; partInfo.primitiveAttributeCount = 0; partInfo.detailAttributeCount = 0; //Debug.LogFormat("Faces: {0}; Vertices: {1}; Verts/Face: {2}", partInfo.faceCount, partInfo.vertexCount, numVertsPerFace); if (normals != null && normals.Count > 0) { partInfo.vertexAttributeCount++; } for (int u = 0; u < NumUVSets; ++u) { if (uvs[u].Count > 0 && uvs[u].Count == totalAllVertexCount) { partInfo.vertexAttributeCount++; } else { uvs[u].Clear(); } } if (colors != null && colors.Count > 0) { partInfo.vertexAttributeCount++; } if (numMaterials > 0) { partInfo.primitiveAttributeCount++; } if (numMeshes > 0) { partInfo.primitiveAttributeCount++; } if (inputDataMeshes._hasLOD) { partInfo.primitiveAttributeCount++; partInfo.detailAttributeCount++; } HAPI_GeoInfo displayGeoInfo = new HAPI_GeoInfo(); if (!session.GetDisplayGeoInfo(inputNodeID, ref displayGeoInfo)) { return false; } HAPI_NodeId displayNodeID = displayGeoInfo.nodeId; if (!session.SetPartInfo(displayNodeID, 0, ref partInfo)) { Debug.LogError("Failed to set input part info. "); return false; } int[] faceCounts = new int[partInfo.faceCount]; for (int i = 0; i < partInfo.faceCount; ++i) { faceCounts[i] = numVertsPerFace; } int[] faceIndices = pointIndexList.ToArray(); if (!HEU_GeneralUtility.SetArray2Arg(displayNodeID, 0, session.SetFaceCount, faceCounts, 0, partInfo.faceCount)) { Debug.LogError("Failed to set input geometry face counts."); return false; } if (!HEU_GeneralUtility.SetArray2Arg(displayNodeID, 0, session.SetVertexList, faceIndices, 0, partInfo.vertexCount)) { Debug.LogError("Failed to set input geometry indices."); return false; } if (!HEU_InputMeshUtility.SetMeshPointAttribute(session, displayNodeID, 0, HEU_Defines.HAPI_ATTRIB_POSITION, 3, vertices.ToArray(), ref partInfo, true)) { Debug.LogError("Failed to set input geometry position."); return false; } int[] vertIndices = vertIndexList.ToArray(); //if(normals != null && !SetMeshPointAttribute(session, displayNodeID, 0, HEU_Defines.HAPI_ATTRIB_NORMAL, 3, normals.ToArray(), ref partInfo, true)) if (normals != null && !HEU_InputMeshUtility.SetMeshVertexAttribute(session, displayNodeID, 0, HEU_Defines.HAPI_ATTRIB_NORMAL, 3, normals.ToArray(), vertIndices, ref partInfo, true)) { Debug.LogError("Failed to set input geometry normals."); return false; } for (int u = 0; u < NumUVSets; ++u) { if (uvs[u].Count > 0) { // Skip uv1 as its used internally. So it goes: uv, uv2, ..., uv8 string uvName = u == 0 ? HEU_Defines.HAPI_ATTRIB_UV : string.Format("{0}{1}", HEU_Defines.HAPI_ATTRIB_UV, u+1); if (!HEU_InputMeshUtility.SetMeshVertexAttribute(session, displayNodeID, 0, uvName, 3, uvs[u].ToArray(), vertIndices, ref partInfo, false)) { Debug.LogError("Failed to set input geometry UV" + u); return false; } } } if (colors != null && colors.Count > 0) { Vector3[] rgb = new Vector3[colors.Count]; float[] alpha = new float[colors.Count]; for (int i = 0; i < colors.Count; ++i) { rgb[i][0] = colors[i].r; rgb[i][1] = colors[i].g; rgb[i][2] = colors[i].b; alpha[i] = colors[i].a; } //if(!SetMeshPointAttribute(session, displayNodeID, 0, HEU_Defines.HAPI_ATTRIB_COLOR, 3, rgb, ref partInfo, false)) if (!HEU_InputMeshUtility.SetMeshVertexAttribute(session, displayNodeID, 0, HEU_Defines.HAPI_ATTRIB_COLOR, 3, rgb, vertIndices, ref partInfo, false)) { Debug.LogError("Failed to set input geometry colors."); return false; } //if(!SetMeshPointAttribute(session, displayNodeID, 0, HEU_Defines.HAPI_ATTRIB_ALPHA, 1, alpha, ref partInfo, false)) if (!HEU_InputMeshUtility.SetMeshVertexFloatAttribute(session, displayNodeID, 0, HEU_Defines.HAPI_ATTRIB_ALPHA, 1, alpha, vertIndices, ref partInfo)) { Debug.LogError("Failed to set input geometry color alpha."); return false; } } // Set material names for round-trip perservation of material assignment // Each HEU_UploadMeshData might have a list of submeshes and materials // These are all combined into a single mesh, with group names if (numMaterials > 0) { bool bFoundAtleastOneValidMaterial = false; string[] materialIDs = new string[partInfo.faceCount]; for (int g = 0; g < inputDataMeshes._inputMeshes.Count; ++g) { if (inputDataMeshes._inputMeshes[g]._numSubMeshes != inputDataMeshes._inputMeshes[g]._materials.Length) { // Number of submeshes should equal number of materials since materials determine submeshes continue; } for (int i = 0; i < inputDataMeshes._inputMeshes[g]._materials.Length; ++i) { string materialName = HEU_AssetDatabase.GetAssetPathWithSubAssetSupport(inputDataMeshes._inputMeshes[g]._materials[i]); if (materialName == null) { materialName = ""; } else if (materialName.StartsWith(HEU_Defines.DEFAULT_UNITY_BUILTIN_RESOURCES)) { materialName = HEU_AssetDatabase.GetUniqueAssetPathForUnityAsset(inputDataMeshes._inputMeshes[g]._materials[i]); } bFoundAtleastOneValidMaterial |= !string.IsNullOrEmpty(materialName); int faceStart = (int)inputDataMeshes._inputMeshes[g]._indexStart[i] / numVertsPerFace; int faceEnd = faceStart + ((int)inputDataMeshes._inputMeshes[g]._indexCount[i] / numVertsPerFace); for (int m = faceStart; m < faceEnd; ++m) { materialIDs[m] = materialName; } } } if (bFoundAtleastOneValidMaterial) { HAPI_AttributeInfo materialIDAttrInfo = new HAPI_AttributeInfo(); materialIDAttrInfo.exists = true; materialIDAttrInfo.owner = HAPI_AttributeOwner.HAPI_ATTROWNER_PRIM; materialIDAttrInfo.storage = HAPI_StorageType.HAPI_STORAGETYPE_STRING; materialIDAttrInfo.count = partInfo.faceCount; materialIDAttrInfo.tupleSize = 1; materialIDAttrInfo.originalOwner = HAPI_AttributeOwner.HAPI_ATTROWNER_INVALID; if (!session.AddAttribute(displayNodeID, 0, HEU_PluginSettings.UnityMaterialAttribName, ref materialIDAttrInfo)) { Debug.LogError("Failed to add input geometry unity material name attribute."); return false; } if (!HEU_GeneralUtility.SetAttributeArray(displayNodeID, 0, HEU_PluginSettings.UnityMaterialAttribName, ref materialIDAttrInfo, materialIDs, session.SetAttributeStringData, partInfo.faceCount)) { Debug.LogError("Failed to set input geometry unity material name."); return false; } } } // Set mesh name attribute HAPI_AttributeInfo attrInfo = new HAPI_AttributeInfo(); attrInfo.exists = true; attrInfo.owner = HAPI_AttributeOwner.HAPI_ATTROWNER_PRIM; attrInfo.storage = HAPI_StorageType.HAPI_STORAGETYPE_STRING; attrInfo.count = partInfo.faceCount; attrInfo.tupleSize = 1; attrInfo.originalOwner = HAPI_AttributeOwner.HAPI_ATTROWNER_INVALID; if (session.AddAttribute(displayNodeID, 0, HEU_PluginSettings.UnityInputMeshAttr, ref attrInfo)) { string[] primitiveNameAttr = new string[partInfo.faceCount]; for (int g = 0; g < inputDataMeshes._inputMeshes.Count; ++g) { for (int i = 0; i < inputDataMeshes._inputMeshes[g]._numSubMeshes; ++i) { int faceStart = (int)inputDataMeshes._inputMeshes[g]._indexStart[i] / numVertsPerFace; int faceEnd = faceStart + ((int)inputDataMeshes._inputMeshes[g]._indexCount[i] / numVertsPerFace); for (int m = faceStart; m < faceEnd; ++m) { primitiveNameAttr[m] = inputDataMeshes._inputMeshes[g]._meshPath; } } } if (!HEU_GeneralUtility.SetAttributeArray(displayNodeID, 0, HEU_PluginSettings.UnityInputMeshAttr, ref attrInfo, primitiveNameAttr, session.SetAttributeStringData, partInfo.faceCount)) { Debug.LogError("Failed to set input geometry unity mesh name."); return false; } } else { return false; } // Set LOD group membership if (inputDataMeshes._hasLOD) { int[] membership = new int[partInfo.faceCount]; for (int g = 0; g < inputDataMeshes._inputMeshes.Count; ++g) { if (g > 0) { // Clear array for (int m = 0; m < partInfo.faceCount; ++m) { membership[m] = 0; } } // Set 1 for faces belonging to this group for (int s = 0; s < inputDataMeshes._inputMeshes[g]._numSubMeshes; ++s) { int faceStart = (int)inputDataMeshes._inputMeshes[g]._indexStart[s] / numVertsPerFace; int faceEnd = faceStart + ((int)inputDataMeshes._inputMeshes[g]._indexCount[s] / numVertsPerFace); for (int m = faceStart; m < faceEnd; ++m) { membership[m] = 1; } } if (!session.AddGroup(displayNodeID, 0, HAPI_GroupType.HAPI_GROUPTYPE_PRIM, inputDataMeshes._inputMeshes[g]._meshName)) { Debug.LogError("Failed to add input geometry LOD group name."); return false; } if (!session.SetGroupMembership(displayNodeID, 0, HAPI_GroupType.HAPI_GROUPTYPE_PRIM, inputDataMeshes._inputMeshes[g]._meshName, membership, 0, partInfo.faceCount)) { Debug.LogError("Failed to set input geometry LOD group name."); return false; } } } return session.CommitGeo(displayNodeID); }