/// <summary> /// Returns the various geometry types (parts) from the given node. /// Only part instancers and point instancers (via attributes) are returned. /// </summary> private bool QueryParts(HAPI_NodeId nodeID, ref List<HAPI_PartInfo> meshParts, ref List<HAPI_PartInfo> volumeParts, ref List<HAPI_PartInfo> instancerParts, ref List<HAPI_PartInfo> curveParts) { // Get display geo info HAPI_GeoInfo geoInfo = new HAPI_GeoInfo(); if (!_session.GetGeoInfo(nodeID, ref geoInfo)) { return false; } //Debug.LogFormat("GeoNode name:{0}, type: {1}, isTemplated: {2}, isDisplayGeo: {3}, isEditable: {4}, parts: {5}", // HEU_SessionManager.GetString(geoInfo.nameSH, _session), // geoInfo.type, geoInfo.isTemplated, // geoInfo.isDisplayGeo, geoInfo.isEditable, geoInfo.partCount); if (geoInfo.type == HAPI_GeoType.HAPI_GEOTYPE_DEFAULT) { int numParts = geoInfo.partCount; for(int i = 0; i < numParts; ++i) { HAPI_PartInfo partInfo = new HAPI_PartInfo(); if (!_session.GetPartInfo(geoInfo.nodeId, i, ref partInfo)) { return false; } bool isAttribInstancer = false; // Preliminary check for attribute instancing (mesh type with no verts but has points with instances) if (HEU_HAPIUtility.IsSupportedPolygonType(partInfo.type) && partInfo.vertexCount == 0 && partInfo.pointCount > 0) { HAPI_AttributeInfo instanceAttrInfo = new HAPI_AttributeInfo(); HEU_GeneralUtility.GetAttributeInfo(_session, nodeID, partInfo.id, HEU_PluginSettings.UnityInstanceAttr, ref instanceAttrInfo); if (instanceAttrInfo.exists && instanceAttrInfo.count > 0) { isAttribInstancer = true; } } if (partInfo.type == HAPI_PartType.HAPI_PARTTYPE_VOLUME) { volumeParts.Add(partInfo); } else if (partInfo.type == HAPI_PartType.HAPI_PARTTYPE_INSTANCER || isAttribInstancer) { instancerParts.Add(partInfo); } else if (partInfo.type == HAPI_PartType.HAPI_PARTTYPE_CURVE) { curveParts.Add(partInfo); } else if(HEU_HAPIUtility.IsSupportedPolygonType(partInfo.type)) { meshParts.Add(partInfo); } else { string partName = HEU_SessionManager.GetString(partInfo.nameSH, _session); SetLog(HEU_LoadData.LoadStatus.ERROR, string.Format("Part {0} with type {1} is not supported for GeoSync.", partName, partInfo.type)); } } } else if(geoInfo.type == HAPI_GeoType.HAPI_GEOTYPE_CURVE) { SetLog(HEU_LoadData.LoadStatus.ERROR, string.Format("Currently {0} geo type is not implemented for threaded geo loading!", geoInfo.type)); } return true; }
public static bool GetAttributeInfo(HEU_SessionBase session, HAPI_NodeId geoID, HAPI_PartId partID, string attribName, ref HAPI_AttributeInfo attribInfo) { bool bResult = false; for (HAPI_AttributeOwner type = 0; type < HAPI_AttributeOwner.HAPI_ATTROWNER_MAX; ++type) { bResult = session.GetAttributeInfo(geoID, partID, attribName, type, ref attribInfo); if (!bResult) { attribInfo.exists = false; return false; } else if(attribInfo.exists) { break; } } return true; }
public static bool GetAttribute<T>(HEU_SessionBase session, HAPI_NodeId geoID, HAPI_PartId partID, string name, ref HAPI_AttributeInfo info, ref T[] data, GetAttributeArrayInputFunc<T> getFunc) { int originalTupleSize = info.tupleSize; bool bResult = false; for (HAPI_AttributeOwner type = 0; type < HAPI_AttributeOwner.HAPI_ATTROWNER_MAX; ++type) { bResult = session.GetAttributeInfo(geoID, partID, name, type, ref info); if (bResult && info.exists) { break; } } if (!bResult || !info.exists) { return false; } if (originalTupleSize > 0) { info.tupleSize = originalTupleSize; } data = new T[info.count * info.tupleSize]; return GetAttributeArray(geoID, partID, name, ref info, data, getFunc, info.count); }
public static bool DoesGeoPartHaveAttribute(HEU_SessionBase session, HAPI_NodeId geoID, HAPI_PartId partID, string attrName, HAPI_AttributeOwner owner, ref HAPI_AttributeInfo attributeInfo) { if (session.GetAttributeInfo(geoID, partID, attrName, owner, ref attributeInfo)) { return(attributeInfo.exists); //Debug.LogFormat("Attr {0} exists={1}, with count={2}, type={3}, storage={4}, tuple={5}", "Cd", colorAttrInfo.exists, colorAttrInfo.count, colorAttrInfo.typeInfo, colorAttrInfo.storage, colorAttrInfo.tupleSize); } return(false); }
public static bool SetAttributeArray<T>(HAPI_NodeId geoID, HAPI_PartId partID, string attrName, ref HAPI_AttributeInfo attrInfo, T[] items, SetAttributeArrayFunc<T> setFunc, int count) { bool bResult = false; int maxArraySize = 0; if(typeof(T) == typeof(string)) { int maxStringLength = 1; foreach(T s in items) { string str = (string)(object)s; if(str.Length > maxStringLength) { maxStringLength = str.Length; } } maxArraySize = HEU_Defines.HAPI_MAX_PAGE_SIZE / (maxStringLength * Marshal.SizeOf(typeof(char)) * attrInfo.tupleSize); } else { maxArraySize = HEU_Defines.HAPI_MAX_PAGE_SIZE / (Marshal.SizeOf(typeof(T)) * attrInfo.tupleSize); } int localCount = count; int currentIndex = 0; while(localCount > 0) { int length = 0; if(localCount > maxArraySize) { length = maxArraySize; localCount -= maxArraySize; } else { length = localCount; localCount = 0; } T[] localArray = new T[length * attrInfo.tupleSize]; // Copy subset to temp array for(int i = currentIndex; i < currentIndex + length; ++i) { for(int j = 0; j < attrInfo.tupleSize; ++j) { localArray[(i - currentIndex) * attrInfo.tupleSize + j] = items[i * attrInfo.tupleSize + j]; } } bResult = setFunc(geoID, partID, attrName, ref attrInfo, localArray, currentIndex, length); if (!bResult) { break; } currentIndex += length; } return bResult; }
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); } if (outputGameObject != null) { _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; }
public bool GenerateTerrainBuffers(HEU_SessionBase session, HAPI_NodeId nodeID, List<HAPI_PartInfo> volumeParts, List<HAPI_PartInfo> scatterInstancerParts, out List<HEU_LoadBufferVolume> volumeBuffers) { volumeBuffers = null; if (volumeParts.Count == 0) { return true; } volumeBuffers = new List<HEU_LoadBufferVolume>(); int detailResolution = 0; int numParts = volumeParts.Count; for (int i = 0; i < numParts; ++i) { HAPI_VolumeInfo volumeInfo = new HAPI_VolumeInfo(); bool bResult = session.GetVolumeInfo(nodeID, volumeParts[i].id, ref volumeInfo); if (!bResult || volumeInfo.tupleSize != 1 || volumeInfo.zLength != 1 || volumeInfo.storage != HAPI_StorageType.HAPI_STORAGETYPE_FLOAT) { SetLog(HEU_LoadData.LoadStatus.ERROR, "This heightfield is not supported. Please check documentation."); return false; } if (volumeInfo.xLength != volumeInfo.yLength) { SetLog(HEU_LoadData.LoadStatus.ERROR, "Non-square sized terrain not supported."); return false; } string volumeName = HEU_SessionManager.GetString(volumeInfo.nameSH, session); HFLayerType layerType = HEU_TerrainUtility.GetHeightfieldLayerType(session, nodeID, volumeParts[i].id, volumeName); //Debug.LogFormat("Index: {0}, Part id: {1}, Part Name: {2}, Volume Name: {3}", i, volumeParts[i].id, HEU_SessionManager.GetString(volumeParts[i].nameSH), volumeName); // Ignoring mask layer because it is Houdini-specific (same behaviour as regular HDA terrain generation) if (layerType == HFLayerType.MASK) { continue; } HEU_LoadBufferVolumeLayer layer = new HEU_LoadBufferVolumeLayer(); layer._layerName = volumeName; layer._partID = volumeParts[i].id; layer._heightMapWidth = volumeInfo.xLength; layer._heightMapHeight = volumeInfo.yLength; layer._layerType = layerType; Matrix4x4 volumeTransformMatrix = HEU_HAPIUtility.GetMatrixFromHAPITransform(ref volumeInfo.transform, false); layer._position = HEU_HAPIUtility.GetPosition(ref volumeTransformMatrix); Vector3 scale = HEU_HAPIUtility.GetScale(ref volumeTransformMatrix); // Calculate real terrain size in both Houdini and Unity. // The height values will be mapped over this terrain size. float gridSpacingX = scale.x * 2f; float gridSpacingY = scale.y * 2f; layer._terrainSizeX = Mathf.Round((volumeInfo.xLength - 1) * gridSpacingX); layer._terrainSizeY = Mathf.Round((volumeInfo.yLength - 1) * gridSpacingY); // Get volume bounds for calculating position offset session.GetVolumeBounds(nodeID, volumeParts[i].id, out layer._minBounds.x, out layer._minBounds.y, out layer._minBounds.z, out layer._maxBounds.x, out layer._maxBounds.y, out layer._maxBounds.z, out layer._center.x, out layer._center.y, out layer._center.z); // Look up TerrainLayer file via attribute if user has set it layer._layerPath = HEU_GeneralUtility.GetAttributeStringValueSingle(session, nodeID, volumeParts[i].id, HEU_Defines.DEFAULT_UNITY_HEIGHTFIELD_TERRAINLAYER_FILE_ATTR, HAPI_AttributeOwner.HAPI_ATTROWNER_PRIM); if (layerType != HFLayerType.DETAIL) { layer._hasLayerAttributes = HEU_TerrainUtility.VolumeLayerHasAttributes(session, nodeID, volumeParts[i].id); if (layer._hasLayerAttributes) { LoadStringFromAttribute(session, nodeID, volumeParts[i].id, HEU_Defines.DEFAULT_UNITY_HEIGHTFIELD_TEXTURE_DIFFUSE_ATTR, ref layer._diffuseTexturePath); LoadStringFromAttribute(session, nodeID, volumeParts[i].id, HEU_Defines.DEFAULT_UNITY_HEIGHTFIELD_TEXTURE_MASK_ATTR, ref layer._maskTexturePath); LoadStringFromAttribute(session, nodeID, volumeParts[i].id, HEU_Defines.DEFAULT_UNITY_HEIGHTFIELD_TEXTURE_NORMAL_ATTR, ref layer._normalTexturePath); LoadFloatFromAttribute(session, nodeID, volumeParts[i].id, HEU_Defines.DEFAULT_UNITY_HEIGHTFIELD_NORMAL_SCALE_ATTR, ref layer._normalScale); LoadFloatFromAttribute(session, nodeID, volumeParts[i].id, HEU_Defines.DEFAULT_UNITY_HEIGHTFIELD_METALLIC_ATTR, ref layer._metallic); LoadFloatFromAttribute(session, nodeID, volumeParts[i].id, HEU_Defines.DEFAULT_UNITY_HEIGHTFIELD_SMOOTHNESS_ATTR, ref layer._smoothness); LoadLayerColorFromAttribute(session, nodeID, volumeParts[i].id, HEU_Defines.DEFAULT_UNITY_HEIGHTFIELD_SPECULAR_ATTR, ref layer._specularColor); LoadLayerVector2FromAttribute(session, nodeID, volumeParts[i].id, HEU_Defines.DEFAULT_UNITY_HEIGHTFIELD_TILE_OFFSET_ATTR, ref layer._tileOffset); LoadLayerVector2FromAttribute(session, nodeID, volumeParts[i].id, HEU_Defines.DEFAULT_UNITY_HEIGHTFIELD_TILE_SIZE_ATTR, ref layer._tileSize); } // Get the height values from Houdini along with the min and max height range. layer._normalizedHeights = HEU_TerrainUtility.GetNormalizedHeightmapFromPartWithMinMax( _session, nodeID, volumeParts[i].id, volumeInfo.xLength, volumeInfo.yLength, ref layer._minHeight, ref layer._maxHeight, ref layer._heightRange, (layerType == HFLayerType.HEIGHT)); } // Get the tile index, if it exists, for this part HAPI_AttributeInfo tileAttrInfo = new HAPI_AttributeInfo(); int[] tileAttrData = new int[0]; HEU_GeneralUtility.GetAttribute(session, nodeID, volumeParts[i].id, HEU_Defines.HAPI_HEIGHTFIELD_TILE_ATTR, ref tileAttrInfo, ref tileAttrData, session.GetAttributeIntData); int tileIndex = 0; if (tileAttrInfo.exists && tileAttrData.Length == 1) { tileIndex = tileAttrData[0]; } // Add layer based on tile index if (tileIndex >= 0) { HEU_LoadBufferVolume volumeBuffer = null; for(int j = 0; j < volumeBuffers.Count; ++j) { if (volumeBuffers[j]._tileIndex == tileIndex) { volumeBuffer = volumeBuffers[j]; break; } } if (volumeBuffer == null) { volumeBuffer = new HEU_LoadBufferVolume(); volumeBuffer.InitializeBuffer(volumeParts[i].id, volumeName, false, false); volumeBuffer._tileIndex = tileIndex; volumeBuffers.Add(volumeBuffer); } if (layerType == HFLayerType.HEIGHT) { // Height layer always first layer volumeBuffer._splatLayers.Insert(0, layer); volumeBuffer._heightMapWidth = layer._heightMapWidth; volumeBuffer._heightMapHeight = layer._heightMapHeight; volumeBuffer._terrainSizeX = layer._terrainSizeX; volumeBuffer._terrainSizeY = layer._terrainSizeY; volumeBuffer._heightRange = layer._heightRange; // The terrain heightfield position in y requires offset of min height layer._position.y += layer._minHeight; // Use y position from attribute if user has set it float userYPos; if (HEU_GeneralUtility.GetAttributeFloatSingle(session, nodeID, volumeParts[i].id, HEU_Defines.DEFAULT_UNITY_HEIGHTFIELD_YPOS, out userYPos)) { layer._position.y = userYPos; } // Look up TerrainData file path via attribute if user has set it volumeBuffer._terrainDataPath = HEU_GeneralUtility.GetAttributeStringValueSingle(session, nodeID, volumeBuffer._id, HEU_Defines.DEFAULT_UNITY_HEIGHTFIELD_TERRAINDATA_FILE_ATTR, HAPI_AttributeOwner.HAPI_ATTROWNER_PRIM); // Look up TerrainData export file path via attribute if user has set it volumeBuffer._terrainDataExportPath = HEU_GeneralUtility.GetAttributeStringValueSingle(session, nodeID, volumeBuffer._id, HEU_Defines.DEFAULT_UNITY_HEIGHTFIELD_TERRAINDATA_EXPORT_FILE_ATTR, HAPI_AttributeOwner.HAPI_ATTROWNER_PRIM); // Load the TreePrototype buffers List<HEU_TreePrototypeInfo> treePrototypeInfos = HEU_TerrainUtility.GetTreePrototypeInfosFromPart(session, nodeID, volumeBuffer._id); if (treePrototypeInfos != null) { if (volumeBuffer._scatterTrees == null) { volumeBuffer._scatterTrees = new HEU_VolumeScatterTrees(); } volumeBuffer._scatterTrees._treePrototypInfos = treePrototypeInfos; } HEU_TerrainUtility.PopulateDetailProperties(session, nodeID, volumeBuffer._id, ref volumeBuffer._detailProperties); // Get specified material if any volumeBuffer._specifiedTerrainMaterialName = HEU_GeneralUtility.GetMaterialAttributeValueFromPart(session, nodeID, volumeBuffer._id); } else if(layer._layerType == HFLayerType.DETAIL) { // Get detail prototype HEU_DetailPrototype detailPrototype = null; HEU_TerrainUtility.PopulateDetailPrototype(session, nodeID, volumeParts[i].id, ref detailPrototype); int[,] detailMap = HEU_TerrainUtility.GetDetailMapFromPart(session, nodeID, volumeParts[i].id, out detailResolution); volumeBuffer._detailPrototypes.Add(detailPrototype); volumeBuffer._detailMaps.Add(detailMap); // Set the detail resolution which is formed from the detail layer if (volumeBuffer._detailProperties == null) { volumeBuffer._detailProperties = new HEU_DetailProperties(); } volumeBuffer._detailProperties._detailResolution = detailResolution; } else { volumeBuffer._splatLayers.Add(layer); } } Sleep(); } // Each volume buffer is a self contained terrain tile foreach(HEU_LoadBufferVolume volumeBuffer in volumeBuffers) { List<HEU_LoadBufferVolumeLayer> layers = volumeBuffer._splatLayers; //Debug.LogFormat("Heightfield: tile={0}, layers={1}", tile._tileIndex, layers.Count); int heightMapWidth = volumeBuffer._heightMapWidth; int heightMapHeight = volumeBuffer._heightMapHeight; int numLayers = layers.Count; if (numLayers > 0) { // Convert heightmap values from Houdini to Unity volumeBuffer._heightMap = HEU_TerrainUtility.ConvertHeightMapHoudiniToUnity(heightMapWidth, heightMapHeight, layers[0]._normalizedHeights); Sleep(); // Convert splatmap values from Houdini to Unity. // Start at 2nd index since height is strictly for height values (not splatmap). List<float[]> heightFields = new List<float[]>(); for(int m = 1; m < numLayers; ++m) { // Ignore Detail layers as they are handled differently if(layers[m]._layerType != HFLayerType.DETAIL) { heightFields.Add(layers[m]._normalizedHeights); } } // The number of maps are the number of splatmaps (ie. non height/mask layers) int numMaps = heightFields.Count; if (numMaps > 0) { // Using the first splatmap size for all splatmaps volumeBuffer._splatMaps = HEU_TerrainUtility.ConvertHeightFieldToAlphaMap(layers[1]._heightMapWidth, layers[1]._heightMapHeight, heightFields); } else { volumeBuffer._splatMaps = null; } // TODO: revisit how the position is calculated volumeBuffer._position = new Vector3( volumeBuffer._terrainSizeX + volumeBuffer._splatLayers[0]._minBounds.x, volumeBuffer._splatLayers[0]._position.y, volumeBuffer._splatLayers[0]._minBounds.z); } } // Process the scatter instancer parts to get the scatter data for (int i = 0; i < scatterInstancerParts.Count; ++i) { // Find the terrain tile (use primitive attr). Assume 0 tile if not set (i.e. not split into tiles) int terrainTile = 0; HAPI_AttributeInfo tileAttrInfo = new HAPI_AttributeInfo(); int[] tileAttrData = new int[0]; if (!HEU_GeneralUtility.GetAttribute(session, nodeID, scatterInstancerParts[i].id, HEU_Defines.HAPI_HEIGHTFIELD_TILE_ATTR, ref tileAttrInfo, ref tileAttrData, session.GetAttributeIntData)) { // Try part 0 (the height layer) to get the tile index. // For scatter points merged with HF, in some cases the part ID doesn't have the tile attribute. HEU_GeneralUtility.GetAttribute(session, nodeID, 0, HEU_Defines.HAPI_HEIGHTFIELD_TILE_ATTR, ref tileAttrInfo, ref tileAttrData, session.GetAttributeIntData); } if (tileAttrData != null && tileAttrData.Length > 0) { terrainTile = tileAttrData[0]; } // Find the volume layer associated with this part using the terrain tile index HEU_LoadBufferVolume volumeBuffer = GetLoadBufferVolumeFromTileIndex(terrainTile, volumeBuffers); if (volumeBuffer == null) { continue; } HEU_TerrainUtility.PopulateScatterTrees(session, nodeID, scatterInstancerParts[i].id, scatterInstancerParts[i].pointCount, ref volumeBuffer._scatterTrees); } return true; }
/// <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); }
/// <summary> /// Get the main geometry info struct. /// </summary> /// <param name="nodeID">The SOP node ID</param> /// <param name="partID">The part ID</param> /// <param name="name">Attribute name</param> /// <param name="owner">Attribute owner</param> /// <param name="attributeInfo">Info to populate</param> /// <returns>True if successfully queried the attribute info</returns> public virtual bool GetAttributeInfo(HAPI_NodeId nodeID, HAPI_PartId partID, string name, HAPI_AttributeOwner owner, ref HAPI_AttributeInfo attributeInfo) { return false; }
/// <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> /// Process the part at the given index, creating its data (geometry), /// and adding it to the list of parts. /// </summary> /// <param name="session"></param> /// <param name="partID"></param> /// <returns>A valid HEU_PartData if it has been successfully processed.</returns> private void ProcessPart(HEU_SessionBase session, int partID, ref HAPI_PartInfo partInfo, ref HEU_PartData partData) { HEU_HoudiniAsset parentAsset = ParentAsset; bool bResult = true; //Debug.LogFormat("Part: name={0}, id={1}, type={2}, instanced={3}, instance count={4}, instance part count={5}", HEU_SessionManager.GetString(partInfo.nameSH, session), partID, partInfo.type, partInfo.isInstanced, partInfo.instanceCount, partInfo.instancedPartCount); #if HEU_PROFILER_ON float processPartStartTime = Time.realtimeSinceStartup; #endif bool isPartEditable = IsIntermediateOrEditable(); bool isAttribInstancer = false; if (IsGeoInputType()) { // Setup for input node to accept inputs if (_inputNode == null) { string partName = HEU_SessionManager.GetString(partInfo.nameSH, session); _inputNode = HEU_InputNode.CreateSetupInput(GeoID, 0, partName, partName, HEU_InputNode.InputNodeType.NODE, ParentAsset); if (_inputNode != null) { ParentAsset.AddInputNode(_inputNode); } } if (HEU_HAPIUtility.IsSupportedPolygonType(partInfo.type) && partInfo.vertexCount == 0) { // No geometry for input asset if (partData != null) { // Clean up existing part HEU_PartData.DestroyPart(partData); partData = null; } // No need to process further since we don't have geometry return; } } else { // Preliminary check for attribute instancing (mesh type with no verts but has points with instances) if (HEU_HAPIUtility.IsSupportedPolygonType(partInfo.type) && partInfo.vertexCount == 0 && partInfo.pointCount > 0) { HAPI_AttributeInfo instanceAttrInfo = new HAPI_AttributeInfo(); HEU_GeneralUtility.GetAttributeInfo(session, GeoID, partID, HEU_PluginSettings.UnityInstanceAttr, ref instanceAttrInfo); if (instanceAttrInfo.exists && instanceAttrInfo.count > 0) { isAttribInstancer = true; } } } if (partInfo.type == HAPI_PartType.HAPI_PARTTYPE_INVALID) { // Clean up invalid parts if (partData != null) { HEU_PartData.DestroyPart(partData); partData = null; } } else if (partInfo.type < HAPI_PartType.HAPI_PARTTYPE_MAX) { // Process the part based on type. Keep or ignore. // We treat parts of type curve as curves, along with geo nodes that are editable and type curves if (partInfo.type == HAPI_PartType.HAPI_PARTTYPE_CURVE) { if (partData == null) { partData = ScriptableObject.CreateInstance <HEU_PartData>(); } partData.Initialize(session, partID, GeoID, _containerObjectNode.ObjectID, this, ref partInfo, HEU_PartData.PartOutputType.CURVE, isPartEditable, _containerObjectNode.IsInstancer(), false); SetupGameObjectAndTransform(partData, parentAsset); partData.ProcessCurvePart(session); } else if (partInfo.type == HAPI_PartType.HAPI_PARTTYPE_VOLUME) { // We only process "height" volume parts. Other volume parts are ignored for now. #if TERRAIN_SUPPORTED HAPI_VolumeInfo volumeInfo = new HAPI_VolumeInfo(); bResult = session.GetVolumeInfo(GeoID, partID, ref volumeInfo); if (!bResult) { Debug.LogErrorFormat("Unable to get volume info for geo node {0} and part {1} ", GeoID, partID); } else { if (Displayable && !IsIntermediateOrEditable()) { if (partData == null) { partData = ScriptableObject.CreateInstance <HEU_PartData>(); } else { // Clear volume data (case where switching from polygonal mesh to volume output) partData.ClearGeneratedMeshOutput(); } partData.Initialize(session, partID, GeoID, _containerObjectNode.ObjectID, this, ref partInfo, HEU_PartData.PartOutputType.VOLUME, isPartEditable, _containerObjectNode.IsInstancer(), false); SetupGameObjectAndTransform(partData, ParentAsset); } } #else Debug.LogWarningFormat("Terrain (heightfield volume) is not yet supported."); #endif } else if (partInfo.type == HAPI_PartType.HAPI_PARTTYPE_INSTANCER || isAttribInstancer) { if (partData == null) { partData = ScriptableObject.CreateInstance <HEU_PartData>(); } else { partData.ClearGeneratedMeshOutput(); partData.ClearGeneratedVolumeOutput(); } partData.Initialize(session, partID, GeoID, _containerObjectNode.ObjectID, this, ref partInfo, HEU_PartData.PartOutputType.INSTANCER, isPartEditable, _containerObjectNode.IsInstancer(), isAttribInstancer); SetupGameObjectAndTransform(partData, parentAsset); } else if (HEU_HAPIUtility.IsSupportedPolygonType(partInfo.type)) { if (partData == null) { partData = ScriptableObject.CreateInstance <HEU_PartData>(); } else { // Clear volume data (case where switching from something other output to mesh) partData.ClearGeneratedVolumeOutput(); } partData.Initialize(session, partID, GeoID, _containerObjectNode.ObjectID, this, ref partInfo, HEU_PartData.PartOutputType.MESH, isPartEditable, _containerObjectNode.IsInstancer(), false); // This check allows to ignore editable non-display nodes by default, but commented out to allow // them for now. Users can also ignore them by turning on IgnoreNonDisplayNodes //if (Displayable || (Editable && ParentAsset.EditableNodesToolsEnabled)) { SetupGameObjectAndTransform(partData, parentAsset); } } else { Debug.LogWarningFormat("Unsupported part type {0}", partInfo.type); } if (partData != null) { // Success! _parts.Add(partData); // Set unique name for the part string partName = HEU_PluginSettings.UseFullPathNamesForOutput ? GeneratePartFullName(partData.PartName) : partData.PartName; partData.SetGameObjectName(partName); // For intermediate or default-type editable nodes, setup the HEU_AttributeStore if (isPartEditable) { partData.SyncAttributesStore(session, _geoInfo.nodeId, ref partInfo); } else { // Remove attributes store if it has it partData.DestroyAttributesStore(); } } } #if HEU_PROFILER_ON Debug.LogFormat("PART PROCESS TIME:: NAME={0}, TIME={1}", HEU_SessionManager.GetString(partInfo.nameSH, session), (Time.realtimeSinceStartup - processPartStartTime)); #endif }
public void ProcessVolumeParts(HEU_SessionBase session, List <HEU_PartData> volumeParts, bool bRebuild) { int numVolumeParts = volumeParts.Count; if (numVolumeParts == 0) { DestroyVolumeCache(); } else if (_volumeCaches == null) { _volumeCaches = new List <HEU_VolumeCache>(); } // First update volume caches. Each volume cache represents a set of terrain layers grouped by tile index. // Therefore each volume cache represents a potential Unity Terrain (containing layers) _volumeCaches = HEU_VolumeCache.UpdateVolumeCachesFromParts(session, this, volumeParts, _volumeCaches); // Heightfield scatter nodes come in as mesh-type parts with attribute instancing. // So process them here to get all the tree/detail instance scatter information. int numParts = _parts.Count; for (int i = 0; i < numParts; ++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, GeoID, _parts[i].PartID, HEU_Defines.HAPI_HEIGHTFIELD_TILE_ATTR, ref tileAttrInfo, ref tileAttrData, session.GetAttributeIntData)) { if (tileAttrData != null && tileAttrData.Length > 0) { terrainTile = tileAttrData[0]; } } // Find the volumecache associated with this part using the terrain tile index HEU_VolumeCache volumeCache = GetVolumeCacheByTileIndex(terrainTile); if (volumeCache == null) { continue; } HEU_VolumeLayer volumeLayer = volumeCache.GetLayer(_parts[i].GetVolumeLayerName()); if (volumeLayer != null && volumeLayer._layerType == HFLayerType.DETAIL) { // Clear out outputs since it might have been created when the part was created. _parts[i].DestroyAllData(); volumeCache.PopulateDetailPrototype(session, GeoID, _parts[i].PartID, volumeLayer); } else if (_parts[i].IsAttribInstancer()) { HAPI_AttributeInfo treeInstAttrInfo = new HAPI_AttributeInfo(); if (HEU_GeneralUtility.GetAttributeInfo(session, GeoID, _parts[i].PartID, HEU_Defines.HEIGHTFIELD_TREEINSTANCE_PROTOTYPEINDEX, ref treeInstAttrInfo)) { if (treeInstAttrInfo.exists && treeInstAttrInfo.count > 0) { // Clear out outputs since it might have been created when the part was created. _parts[i].DestroyAllData(); // Mark the instancers as having been created so that the object instancer step skips this. _parts[i].ObjectInstancesBeenGenerated = true; // Now populate scatter trees based on attributes on this part volumeCache.PopulateScatterTrees(session, GeoID, _parts[i].PartID, treeInstAttrInfo.count); } } } } // Now generate the terrain for each volume cache foreach (HEU_VolumeCache cache in _volumeCaches) { cache.GenerateTerrainWithAlphamaps(session, ParentAsset, bRebuild); cache.IsDirty = false; } }
/// <summary> /// Generates object instances. /// Skips parts that already have their instances generated. /// </summary> /// <param name="session">Active session to use</param> internal void GenerateObjectInstances(HEU_SessionBase session) { if (ParentAsset == null) { return; } if (!IsInstancer()) { HEU_Logger.LogErrorFormat("Generate object instances called on a non-instancer object {0} for asset {1}!", ObjectName, ParentAsset.AssetName); return; } //HEU_Logger.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(); HAPI_AttributeInfo materialAttrInfo = 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]._objectInstancesGenerated || parts[j].IsPartVolume()) { // This prevents instances being created unnecessarily (e.g. part hasn't changed since last cook). // Or for volumes that might have instance attributes. 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); } string[] instanceMaterialPaths = null; HEU_GeneralUtility.GetAttributeInfo(session, _geoNodes[i].GeoID, parts[j].PartID, HEU_PluginSettings.UnityMaterialAttribName, ref materialAttrInfo); if (materialAttrInfo.exists) { instanceMaterialPaths = HEU_GeneralUtility.GetAttributeStringData(session, _geoNodes[i].GeoID, parts[j].PartID, HEU_PluginSettings.UnityMaterialAttribName, ref materialAttrInfo); } if (instanceAttrInfo.exists) { // Object instancing via Houdini instance attribute parts[j].GenerateInstancesFromObjectIds(session, instancePrefixes, instanceMaterialPaths); } 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 || unityInstanceAttrInfo.owner == HAPI_AttributeOwner.HAPI_ATTROWNER_DETAIL) { parts[j].GenerateInstancesFromUnityAssetPathAttribute(session, unityInstanceAttrName); } else { // Other attribute owned types are unsupported. // Originally had a warning here, but unnecessary as in some cases (e.g. heightfield attrbiutes) the // attribute owner could be changed in HAPI. } } else { // Standard object instancing via single Houdini object if (_objectInfo.objectToInstanceId == HEU_Defines.HEU_INVALID_NODE_ID) { // HEU_Logger.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); // Could be a part instancer continue; } parts[j].GenerateInstancesFromObjectID(session, _objectInfo.objectToInstanceId, instancePrefixes, instanceMaterialPaths); } } } } }
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; }
/// <summary> /// Returns true if given part in geometry has the given attribute with name. /// </summary> /// <param name="session">Houdini Engine session</param> /// <param name="geoID">Geometry object ID</param> /// <param name="partID">Part ID</param> /// <param name="attribName">Name of the attribute</param> /// <returns>True if attribute exists</returns> public static bool HasValidInstanceAttribute(HEU_SessionBase session, HAPI_NodeId geoID, HAPI_PartId partID, string attribName) { HAPI_AttributeInfo instanceAttrInfo = new HAPI_AttributeInfo(); GetAttributeInfo(session, geoID, partID, attribName, ref instanceAttrInfo); return (instanceAttrInfo.exists && instanceAttrInfo.count > 0); }
/// <summary> /// Get the attribute string data. /// </summary> /// <param name="nodeID">The node ID</param> /// <param name="partID">The part ID</param> /// <param name="name">Attribute name</param> /// <param name="attributeInfo">Atttribute info</param> /// <param name="dataArray">Array to populate. Must be at least the size of length * HAPI_AttributeInfo::tupleSize</param> /// <param name="start">First index of range. Must be at least 0 and at most HAPI_AttributeInfo::count - 1</param> /// <param name="length">Must be at least 0 and at most HAPI_AttributeInfo::count - start</param> /// <returns>True if successfully queried the atttribute string data</returns> public virtual bool GetAttributeStringData(HAPI_NodeId nodeID, HAPI_PartId partID, string name, ref HAPI_AttributeInfo attributeInfo, [Out] HAPI_StringHandle[] dataArray, int start, int length) { return false; }
/// <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 || parts[j].IsPartVolume()) { // This prevents instances being created unnecessarily (e.g. part hasn't changed since last cook). // Or for volumes that might have instance attributes. 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]; HEU_GeneralUtility.GetAttribute(session, _geoNodes[i].GeoID, parts[j].PartID, unityInstanceAttrName, ref unityInstanceAttrInfo, ref scriptAttr, session.GetAttributeStringData); if (unityInstanceAttrInfo.exists) { 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. // Originally had a warning here, but unnecessary as in some cases (e.g. heightfield attrbiutes) the // attribute owner could be changed in HAPI. } } 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> /// Get the attribute float data. /// </summary> /// <param name="nodeID">The node ID</param> /// <param name="partID">The part ID</param> /// <param name="name">Attribut ename</param> /// <param name="attributeInfo">Should be same struct returned by HAPI_GetAttributeInfo</param> /// <param name="data">A float array at least the size of length * HAPI_AttributeInfo::tupleSize</param> /// <param name="start">First index of range. Must be at least 0 and at most HAPI_AttributeInfo::count - 1</param> /// <param name="length">Must be at least 0 and at most HAPI_AttributeInfo::count - start.</param> /// <returns>True if successfully queried the atttribute float data</returns> public virtual bool GetAttributeFloatData(HAPI_NodeId nodeID, HAPI_PartId partID, string name, ref HAPI_AttributeInfo attributeInfo, [Out] float[] data, int start, int length) { return false; }
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) { // Temp storage to use for reassigning old values to new array int[] oldValues = null; if (attributeData._intValues == null) { attributeData._intValues = new int[arraySize]; attributeData._attributeState = HEU_AttributeData.AttributeState.INVALID; } else if (attributeData._intValues.Length != arraySize) { ArrayExtensions.CopyToWithResize(attributeData._intValues, ref oldValues); 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 index = 0; 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) { index = i * tupleSize + tuple; if (oldValues != null && index < oldValues.Length) { attributeData._intValues[index] = oldValues[index]; } else { attributeData._intValues[index] = data[index]; } } } } } else if (attributeData._attributeType == HEU_AttributeData.AttributeType.FLOAT) { // Temp storage to use for reassigning old values to new array float[] oldValues = null; if (attributeData._floatValues == null) { attributeData._floatValues = new float[arraySize]; attributeData._attributeState = HEU_AttributeData.AttributeState.INVALID; } else if (attributeData._floatValues.Length != arraySize) { ArrayExtensions.CopyToWithResize(attributeData._floatValues, ref oldValues); 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) { int index = 0; 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) { index = i * tupleSize + tuple; if (oldValues != null && index < oldValues.Length) { attributeData._floatValues[index] = oldValues[index]; } else { attributeData._floatValues[index] = data[index]; } } } } } else if (attributeData._attributeType == HEU_AttributeData.AttributeType.STRING) { // Temp storage to use for reassigning old values to new array string[] oldValues = null; if (attributeData._stringValues == null) { attributeData._stringValues = new string[arraySize]; attributeData._attributeState = HEU_AttributeData.AttributeState.INVALID; } else if (attributeData._stringValues.Length != arraySize) { ArrayExtensions.CopyToWithResize(attributeData._stringValues, ref oldValues); 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) { int index = 0; 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) { index = i * tupleSize + tuple; if (oldValues != null && index < oldValues.Length) { attributeData._stringValues[index] = oldValues[index]; } else { attributeData._stringValues[index] = HEU_SessionManager.GetString(data[index], session); } } } } } SetAttributeDataSyncd(attributeData); }
public virtual bool SetAttributeStringData(HAPI_NodeId nodeID, HAPI_PartId partID, string name, ref HAPI_AttributeInfo attrInfo, string[] data, int start, int length) { return false; }
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_PluginSettings.CollisionGroupName, 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_PluginSettings.CollisionGroupName, 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; }
public virtual bool AddAttribute(HAPI_NodeId nodeID, HAPI_PartId partID, string name, ref HAPI_AttributeInfo attrInfo) { return false; }
public static string[] GetAttributeStringData(HEU_SessionBase session, HAPI_NodeId geoID, HAPI_PartId partID, string name, ref HAPI_AttributeInfo attrInfo) { int[] stringHandles = new int[0]; if (GetAttribute(session, geoID, partID, name, ref attrInfo, ref stringHandles, session.GetAttributeStringData)) { return HEU_SessionManager.GetStringValuesFromStringIndices(stringHandles); } return null; }
// LOGIC ----------------------------------------------------------------------------------------------------- public static List<HEU_VolumeCache> UpdateVolumeCachesFromParts(HEU_SessionBase session, HEU_GeoNode ownerNode, List<HEU_PartData> volumeParts, List<HEU_VolumeCache> volumeCaches) { HEU_HoudiniAsset parentAsset = ownerNode.ParentAsset; foreach (HEU_VolumeCache cache in volumeCaches) { // Remove current volume caches from parent asset. // These get added back in below. parentAsset.RemoveVolumeCache(cache); // Mark the cache for updating cache.StartUpdateLayers(); } // This will keep track of volume caches still in use List<HEU_VolumeCache> updatedCaches = new List<HEU_VolumeCache>(); int numParts = volumeParts.Count; for (int i = 0; i < numParts; ++i) { // 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, ownerNode.GeoID, volumeParts[i].PartID, "tile", ref tileAttrInfo, ref tileAttrData, session.GetAttributeIntData); if (tileAttrData != null && tileAttrData.Length > 0) { //Debug.LogFormat("Tile: {0}", tileAttrData[0]); int tile = tileAttrData[0]; HEU_VolumeCache volumeCache = null; // Find cache in updated list for (int j = 0; j < updatedCaches.Count; ++j) { if (updatedCaches[j] != null && updatedCaches[j].TileIndex == tile) { volumeCache = updatedCaches[j]; break; } } if (volumeCache != null) { volumeCache.UpdateLayerFromPart(session, volumeParts[i]); // Skip adding new cache since already found in updated list continue; } // Find existing cache in old list if (volumeCaches != null && volumeCaches.Count > 0) { for(int j = 0; j < volumeCaches.Count; ++j) { if (volumeCaches[j] != null && volumeCaches[j].TileIndex == tile) { volumeCache = volumeCaches[j]; break; } } } // Create new cache for this tile if not found if (volumeCache == null) { volumeCache = ScriptableObject.CreateInstance<HEU_VolumeCache>(); volumeCache.Initialize(ownerNode, tile); volumeCache.StartUpdateLayers(); } volumeCache.UpdateLayerFromPart(session, volumeParts[i]); if (!updatedCaches.Contains(volumeCache)) { updatedCaches.Add(volumeCache); } } else { // No tile index. Most likely a single terrain tile. HEU_VolumeCache volumeCache = null; if (updatedCaches.Count == 0) { // Create a single volume cache, or use existing if it was just 1. // If more than 1 volume cache exists, this will recreate a single one if (volumeCaches == null || volumeCaches.Count != 1) { volumeCache = ScriptableObject.CreateInstance<HEU_VolumeCache>(); volumeCache.Initialize(ownerNode, 0); volumeCache.StartUpdateLayers(); } else if (volumeCaches.Count == 1) { // Keep the single volumecache volumeCache = volumeCaches[0]; } if (!updatedCaches.Contains(volumeCache)) { updatedCaches.Add(volumeCache); } } else { // Reuse the updated cache volumeCache = updatedCaches[0]; } volumeCache.UpdateLayerFromPart(session, volumeParts[i]); } } foreach (HEU_VolumeCache cache in updatedCaches) { // Add to parent for UI and preset parentAsset.AddVolumeCache(cache); // Finish update by keeping just the layers in use for each volume cache. cache.FinishUpdateLayers(); } return updatedCaches; }
public static bool SetAttribute<T>(HAPI_NodeId geoID, HAPI_PartId partID, string attrName, ref HAPI_AttributeInfo attrInfo, T[] items, SetAttributeArrayFunc<T> setFunc) { return SetAttributeArray(geoID, partID, attrName, ref attrInfo, items, setFunc, attrInfo.count); }
private void GetPartLayerAttributes(HEU_SessionBase session, HAPI_NodeId geoID, HAPI_NodeId partID, HEU_VolumeLayer layer) { // 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, geoID, partID, "tile", ref tileAttrInfo, ref tileAttrData, session.GetAttributeIntData); if (tileAttrData != null && tileAttrData.Length > 0) { layer._tile = tileAttrData[0]; //Debug.LogFormat("Tile: {0}", tileAttrData[0]); } else { layer._tile = 0; } // Get the layer textures, and other layer values from attributes Texture2D defaultTexture = LoadDefaultSplatTexture(); if (!IsLayerFieldOverriden(layer, HEU_VolumeLayer.Overrides.Diffuse) && (layer._diffuseTexture == null || layer._diffuseTexture == defaultTexture)) { layer._diffuseTexture = LoadLayerTextureFromAttribute(session, geoID, partID, HEU_Defines.DEFAULT_UNITY_HEIGHTFIELD_TEXTURE_DIFFUSE_ATTR); if (layer._diffuseTexture == null) { layer._diffuseTexture = defaultTexture; } } if (!IsLayerFieldOverriden(layer, HEU_VolumeLayer.Overrides.Mask) && layer._maskTexture == null) { layer._maskTexture = LoadLayerTextureFromAttribute(session, geoID, partID, HEU_Defines.DEFAULT_UNITY_HEIGHTFIELD_TEXTURE_MASK_ATTR); } if (!IsLayerFieldOverriden(layer, HEU_VolumeLayer.Overrides.Normal) && layer._normalTexture == null) { layer._normalTexture = LoadLayerTextureFromAttribute(session, geoID, partID, HEU_Defines.DEFAULT_UNITY_HEIGHTFIELD_TEXTURE_NORMAL_ATTR); } if (!IsLayerFieldOverriden(layer, HEU_VolumeLayer.Overrides.NormalScale)) { LoadLayerFloatFromAttribute(session, geoID, partID, HEU_Defines.DEFAULT_UNITY_HEIGHTFIELD_NORMAL_SCALE_ATTR, ref layer._normalScale); } if (!IsLayerFieldOverriden(layer, HEU_VolumeLayer.Overrides.Metallic)) { LoadLayerFloatFromAttribute(session, geoID, partID, HEU_Defines.DEFAULT_UNITY_HEIGHTFIELD_METALLIC_ATTR, ref layer._metallic); } if (!IsLayerFieldOverriden(layer, HEU_VolumeLayer.Overrides.Smoothness)) { LoadLayerFloatFromAttribute(session, geoID, partID, HEU_Defines.DEFAULT_UNITY_HEIGHTFIELD_SMOOTHNESS_ATTR, ref layer._smoothness); } if (!IsLayerFieldOverriden(layer, HEU_VolumeLayer.Overrides.Specular)) { LoadLayerColorFromAttribute(session, geoID, partID, HEU_Defines.DEFAULT_UNITY_HEIGHTFIELD_SPECULAR_ATTR, ref layer._specularColor); } if (!IsLayerFieldOverriden(layer, HEU_VolumeLayer.Overrides.TileOffset)) { LoadLayerVector2FromAttribute(session, geoID, partID, HEU_Defines.DEFAULT_UNITY_HEIGHTFIELD_TILE_OFFSET_ATTR, ref layer._tileOffset); } if (!IsLayerFieldOverriden(layer, HEU_VolumeLayer.Overrides.TileSize)) { LoadLayerVector2FromAttribute(session, geoID, partID, HEU_Defines.DEFAULT_UNITY_HEIGHTFIELD_TILE_SIZE_ATTR, ref layer._tileSize); } }
public static bool GetAttributeArray<T>(HAPI_NodeId geoID, HAPI_PartId partID, string name, ref HAPI_AttributeInfo info, T[] items, GetAttributeArrayInputFunc<T> getFunc, int count) { int maxArraySize = HEU_Defines.HAPI_MAX_PAGE_SIZE / (Marshal.SizeOf(typeof(T)) * info.tupleSize); int localCount = count; int currentIndex = 0; bool bResult = false; while (localCount > 0) { int length = 0; if (localCount > maxArraySize) { length = maxArraySize; localCount -= maxArraySize; } else { length = localCount; localCount = 0; } T[] localArray = new T[length * info.tupleSize]; bResult = getFunc(geoID, partID, name, ref info, localArray, currentIndex, length); if (!bResult) { break; } // Copy data from temporary array for (int i = currentIndex; i < currentIndex + length; ++i) { for (int j = 0; j < info.tupleSize; ++j) { items[i * info.tupleSize + j] = localArray[(i - currentIndex) * info.tupleSize + j]; } } currentIndex += length; } return bResult; }
/// <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 PopulateScatterInfo(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(); } Debug.Log("Point count is : " + pointCount); // 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; } } } }
public static void GetAttributeStringDataHelper(HEU_SessionBase session, HAPI_NodeId geoID, HAPI_PartId partID, string name, ref HAPI_AttributeInfo info, ref HAPI_StringHandle[] data) { int originalTupleSize = info.tupleSize; bool bResult = false; for (HAPI_AttributeOwner type = 0; type < HAPI_AttributeOwner.HAPI_ATTROWNER_MAX; ++type) { bResult = session.GetAttributeInfo(geoID, partID, name, type, ref info); if (bResult && info.exists) { break; } } if (!bResult || !info.exists) { return; } if (originalTupleSize > 0) { info.tupleSize = originalTupleSize; } data = new HAPI_StringHandle[info.count * info.tupleSize]; bResult = session.GetAttributeStringData(geoID, partID, name, ref info, data, 0, info.count); if(!bResult) { Debug.LogErrorFormat("Failed to get string IDs for attribute {0}", name); } }
/// <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<Vector2> uvs = new List<Vector2>(); List<Color> colors = new List<Color>(); 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; } // 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 = inputDataMeshes._inputMeshes[i]._transform.localToWorldMatrix * rootInvertTransformMatrix; 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]; for (int j = 0; j < meshVertices.Length; ++j) { reindexVertices[j] = -1; } // For each vertex, check against subsequent vertices for shared positions. for (int a = 0; a < meshVertices.Length; ++a) { Vector3 va = meshVertices[a]; if (reindexVertices[a] == -1) { 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; } for (int b = a + 1; b < meshVertices.Length; ++b) { if (va == meshVertices[b]) { // Shared vertex -> reindex to point to unique vertex slot reindexVertices[b] = reindexVertices[a]; } } } int vertexOffset = vertices.Count; vertices.AddRange(uniqueVertices); Vector3[] meshNormals = inputDataMeshes._inputMeshes[i]._mesh.normals; Vector2[] meshUVs = inputDataMeshes._inputMeshes[i]._mesh.uv; Color[] meshColors = inputDataMeshes._inputMeshes[i]._mesh.colors; 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 int[] meshIndices = inputDataMeshes._inputMeshes[i]._mesh.GetTriangles(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]); } if (meshUVs != null && (originalIndex < meshUVs.Length)) { uvs.Add(meshUVs[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 (uvs.Count != totalAllVertexCount) { uvs = null; } if (colors.Count != totalAllVertexCount) { colors = null; } HAPI_PartInfo partInfo = new HAPI_PartInfo(); partInfo.faceCount = vertIndexList.Count / 3; partInfo.vertexCount = vertIndexList.Count; partInfo.pointCount = vertices.Count; partInfo.pointAttributeCount = 1; partInfo.vertexAttributeCount = 0; partInfo.primitiveAttributeCount = 0; partInfo.detailAttributeCount = 0; if (normals != null && normals.Count > 0) { partInfo.vertexAttributeCount++; } if (uvs != null && uvs.Count > 0) { partInfo.vertexAttributeCount++; } 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] = 3; } int[] triIndices = 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, triIndices, 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; } if (uvs != null && uvs.Count > 0) { Vector3[] uvs3 = new Vector3[uvs.Count]; for (int i = 0; i < uvs.Count; ++i) { uvs3[i][0] = uvs[i][0]; uvs3[i][1] = uvs[i][1]; uvs3[i][2] = 0; } //if(!SetMeshPointAttribute(session, displayNodeID, 0, HEU_Defines.HAPI_ATTRIB_UV, 3, uvs3, ref partInfo, false)) if (!HEU_InputMeshUtility.SetMeshVertexAttribute(session, displayNodeID, 0, HEU_Defines.HAPI_ATTRIB_UV, 3, uvs3, vertIndices, ref partInfo, false)) { Debug.LogError("Failed to set input geometry UVs."); 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] / 3; int faceEnd = faceStart + ((int)inputDataMeshes._inputMeshes[g]._indexCount[i] / 3); 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] / 3; int faceEnd = faceStart + ((int)inputDataMeshes._inputMeshes[g]._indexCount[i] / 3); 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] / 3; int faceEnd = faceStart + ((int)inputDataMeshes._inputMeshes[g]._indexCount[s] / 3); 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); }