public static bool GenerateMeshUsingGeoCache(HEU_SessionBase session, HEU_HoudiniAsset asset, GameObject gameObject, HEU_GenerateGeoCache geoCache, bool bGenerateUVs, bool bGenerateTangents, bool bPartInstanced) { #if HEU_PROFILER_ON float generateMeshTime = Time.realtimeSinceStartup; #endif string collisionGroupName = HEU_PluginSettings.CollisionGroupName; string renderCollisionGroupName = HEU_PluginSettings.RenderedCollisionGroupName; // Stores submesh data based on material key (ie. a submesh for each unique material) // Unity requires that if using multiple materials in the same GameObject, then we // need to create corresponding number of submeshes as materials. // So we'll create a submesh for each material in use. // Each submesh will have a list of vertices and their attributes which // we'll collect in a helper class (HEU_MeshData). // Once we collected all the submesh data, we create a CombineInstance for each // submesh, then combine it while perserving the submeshes. Dictionary<int, HEU_MeshData> subMeshesMap = new Dictionary<int, HEU_MeshData>(); string defaultMaterialName = HEU_HoudiniAsset.GenerateDefaultMaterialName(geoCache.GeoID, geoCache.PartID); int defaultMaterialKey = HEU_MaterialFactory.MaterialNameToKey(defaultMaterialName); int singleFaceUnityMaterialKey = HEU_Defines.HEU_INVALID_MATERIAL; int singleFaceHoudiniMaterialKey = HEU_Defines.HEU_INVALID_MATERIAL; // Now go through each group data and acquire the vertex data. // We'll create the collider mesh rightaway and assign to the gameobject. int numCollisionMeshes = 0; foreach (KeyValuePair<string, int[]> groupSplitFacesPair in geoCache._groupSplitVertexIndices) { string groupName = groupSplitFacesPair.Key; int[] groupVertexList = groupSplitFacesPair.Value; bool bIsCollidable = groupName.Contains(collisionGroupName); bool bIsRenderCollidable = groupName.Contains(renderCollisionGroupName); if (bIsCollidable || bIsRenderCollidable) { if (numCollisionMeshes > 0) { Debug.LogWarningFormat("More than 1 collision mesh detected for part {0}.\nOnly a single collision mesh is supported per part.", geoCache._partName); } if (geoCache._partInfo.type == HAPI_PartType.HAPI_PARTTYPE_BOX) { // Box collider HAPI_BoxInfo boxInfo = new HAPI_BoxInfo(); if (session.GetBoxInfo(geoCache.GeoID, geoCache.PartID, ref boxInfo)) { BoxCollider boxCollider = HEU_GeneralUtility.GetOrCreateComponent<BoxCollider>(gameObject); boxCollider.center = new Vector3(-boxInfo.center[0], boxInfo.center[1], boxInfo.center[2]); boxCollider.size = new Vector3(boxInfo.size[0] * 2f, boxInfo.size[1] * 2f, boxInfo.size[2] * 2f); // TODO: Should we apply the box info rotation here to the box collider? // If so, it should be in its own gameobject? } } else if (geoCache._partInfo.type == HAPI_PartType.HAPI_PARTTYPE_SPHERE) { // Sphere collider HAPI_SphereInfo sphereInfo = new HAPI_SphereInfo(); if (session.GetSphereInfo(geoCache.GeoID, geoCache.PartID, ref sphereInfo)) { SphereCollider sphereCollider = HEU_GeneralUtility.GetOrCreateComponent<SphereCollider>(gameObject); sphereCollider.center = new Vector3(-sphereInfo.center[0], sphereInfo.center[1], sphereInfo.center[2]); sphereCollider.radius = sphereInfo.radius; } } else { // Mesh collider List<Vector3> collisionVertices = new List<Vector3>(); for (int v = 0; v < groupVertexList.Length; ++v) { int index = groupVertexList[v]; if (index >= 0 && index < geoCache._posAttr.Length) { collisionVertices.Add(new Vector3(-geoCache._posAttr[index * 3], geoCache._posAttr[index * 3 + 1], geoCache._posAttr[index * 3 + 2])); } } int[] collisionIndices = new int[collisionVertices.Count]; for (int i = 0; i < collisionIndices.Length; ++i) { collisionIndices[i] = i; } Mesh collisionMesh = new Mesh(); #if UNITY_2017_3_OR_NEWER collisionMesh.indexFormat = UnityEngine.Rendering.IndexFormat.UInt32; #endif collisionMesh.name = groupName; collisionMesh.vertices = collisionVertices.ToArray(); collisionMesh.triangles = collisionIndices; collisionMesh.RecalculateBounds(); MeshCollider meshCollider = HEU_GeneralUtility.GetOrCreateComponent<MeshCollider>(gameObject); meshCollider.sharedMesh = collisionMesh; } numCollisionMeshes++; } if (bIsCollidable && !bIsRenderCollidable) { continue; } // After this point, we'll be only processing renderable geometry // Transfer indices for each attribute from the single large list into group lists float[] groupColorAttr = new float[0]; HEU_GenerateGeoCache.TransferRegularPointAttributesToVertices(groupVertexList, ref geoCache._colorAttrInfo, geoCache._colorAttr, ref groupColorAttr); float[] groupAlphaAttr = new float[0]; HEU_GenerateGeoCache.TransferRegularPointAttributesToVertices(groupVertexList, ref geoCache._alphaAttrInfo, geoCache._alphaAttr, ref groupAlphaAttr); float[] groupNormalAttr = new float[0]; HEU_GenerateGeoCache.TransferRegularPointAttributesToVertices(groupVertexList, ref geoCache._normalAttrInfo, geoCache._normalAttr, ref groupNormalAttr); float[] groupTangentsAttr = new float[0]; HEU_GenerateGeoCache.TransferRegularPointAttributesToVertices(groupVertexList, ref geoCache._tangentAttrInfo, geoCache._tangentAttr, ref groupTangentsAttr); float[] groupUVAttr = new float[0]; HEU_GenerateGeoCache.TransferRegularPointAttributesToVertices(groupVertexList, ref geoCache._uvAttrInfo, geoCache._uvAttr, ref groupUVAttr); float[] groupUV2Attr = new float[0]; HEU_GenerateGeoCache.TransferRegularPointAttributesToVertices(groupVertexList, ref geoCache._uv2AttrInfo, geoCache._uv2Attr, ref groupUV2Attr); float[] groupUV3Attr = new float[0]; HEU_GenerateGeoCache.TransferRegularPointAttributesToVertices(groupVertexList, ref geoCache._uv3AttrInfo, geoCache._uv3Attr, ref groupUV3Attr); // Unity mesh creation requires # of vertices must equal # of attributes (color, normal, uvs). // HAPI gives us point indices. Since our attributes are via vertex, we need to therefore // create new indices of vertices that correspond to our attributes. // To reindex, we go through each index, add each attribute corresponding to that index to respective lists. // Then we set the index of where we added those attributes as the new index. int numIndices = groupVertexList.Length; for (int vertexIndex = 0; vertexIndex < numIndices; vertexIndex += 3) { // groupVertexList contains -1 for unused indices, and > 0 for used if (groupVertexList[vertexIndex] == -1) { continue; } int faceIndex = vertexIndex / 3; int faceMaterialID = geoCache._houdiniMaterialIDs[faceIndex]; // Get the submesh ID for this face. Depends on whether it is a Houdini or Unity material. // Using default material as failsafe int submeshID = HEU_Defines.HEU_INVALID_MATERIAL; if (geoCache._unityMaterialAttrInfo.exists) { // This face might have a Unity or Substance material attribute. // Formulate the submesh ID by combining the material attributes. if (geoCache._singleFaceUnityMaterial) { if (singleFaceUnityMaterialKey == HEU_Defines.HEU_INVALID_MATERIAL && geoCache._unityMaterialInfos.Count > 0) { // Use first material var unityMaterialMapEnumerator = geoCache._unityMaterialInfos.GetEnumerator(); if (unityMaterialMapEnumerator.MoveNext()) { singleFaceUnityMaterialKey = unityMaterialMapEnumerator.Current.Key; } } submeshID = singleFaceUnityMaterialKey; } else { int attrIndex = faceIndex; if (geoCache._unityMaterialAttrInfo.owner == HAPI_AttributeOwner.HAPI_ATTROWNER_PRIM || geoCache._unityMaterialAttrInfo.owner == HAPI_AttributeOwner.HAPI_ATTROWNER_POINT) { if (geoCache._unityMaterialAttrInfo.owner == HAPI_AttributeOwner.HAPI_ATTROWNER_POINT) { attrIndex = groupVertexList[vertexIndex]; } string unityMaterialName = ""; string substanceName = ""; int substanceIndex = -1; submeshID = HEU_GenerateGeoCache.GetMaterialKeyFromAttributeIndex(geoCache, attrIndex, out unityMaterialName, out substanceName, out substanceIndex); } else { // (geoCache._unityMaterialAttrInfo.owner == HAPI_AttributeOwner.HAPI_ATTROWNER_DETAIL) should have been handled as geoCache._singleFaceMaterial above Debug.LogErrorFormat("Unity material attribute not supported for attribute type {0}!", geoCache._unityMaterialAttrInfo.owner); } } } if (submeshID == HEU_Defines.HEU_INVALID_MATERIAL) { // Check if has Houdini material assignment if (geoCache._houdiniMaterialIDs.Length > 0) { if (geoCache._singleFaceHoudiniMaterial) { if (singleFaceHoudiniMaterialKey == HEU_Defines.HEU_INVALID_MATERIAL) { singleFaceHoudiniMaterialKey = geoCache._houdiniMaterialIDs[0]; } submeshID = singleFaceHoudiniMaterialKey; } else if (faceMaterialID > 0) { submeshID = faceMaterialID; } } if (submeshID == HEU_Defines.HEU_INVALID_MATERIAL) { // Use default material submeshID = defaultMaterialKey; } } if (!subMeshesMap.ContainsKey(submeshID)) { // New submesh subMeshesMap.Add(submeshID, new HEU_MeshData()); } HEU_MeshData subMeshData = subMeshesMap[submeshID]; for (int triIndex = 0; triIndex < 3; ++triIndex) { int vertexTriIndex = vertexIndex + triIndex; int positionIndex = groupVertexList[vertexTriIndex]; // Position Vector3 position = new Vector3(-geoCache._posAttr[positionIndex * 3 + 0], geoCache._posAttr[positionIndex * 3 + 1], geoCache._posAttr[positionIndex * 3 + 2]); subMeshData._vertices.Add(position); // Color if (geoCache._colorAttrInfo.exists) { Color tempColor = new Color(); tempColor.r = Mathf.Clamp01(groupColorAttr[vertexTriIndex * geoCache._colorAttrInfo.tupleSize + 0]); tempColor.g = Mathf.Clamp01(groupColorAttr[vertexTriIndex * geoCache._colorAttrInfo.tupleSize + 1]); tempColor.b = Mathf.Clamp01(groupColorAttr[vertexTriIndex * geoCache._colorAttrInfo.tupleSize + 2]); if (geoCache._alphaAttrInfo.exists) { tempColor.a = Mathf.Clamp01(groupAlphaAttr[vertexTriIndex]); } else if (geoCache._colorAttrInfo.tupleSize == 4) { tempColor.a = Mathf.Clamp01(groupColorAttr[vertexTriIndex * geoCache._colorAttrInfo.tupleSize + 3]); } else { tempColor.a = 1f; } subMeshData._colors.Add(tempColor); } else { subMeshData._colors.Add(Color.white); } // Normal if (vertexTriIndex < groupNormalAttr.Length) { // Flip the x Vector3 normal = new Vector3(-groupNormalAttr[vertexTriIndex * 3 + 0], groupNormalAttr[vertexTriIndex * 3 + 1], groupNormalAttr[vertexTriIndex * 3 + 2]); subMeshData._normals.Add(normal); } else { // We'll be calculating normals later subMeshData._normals.Add(Vector3.zero); } // UV1 if (vertexTriIndex < groupUVAttr.Length) { Vector2 uv = new Vector2(groupUVAttr[vertexTriIndex * 2 + 0], groupUVAttr[vertexTriIndex * 2 + 1]); subMeshData._UVs.Add(uv); } // UV2 if (vertexTriIndex < groupUV2Attr.Length) { Vector2 uv = new Vector2(groupUV2Attr[vertexTriIndex * 2 + 0], groupUV2Attr[vertexTriIndex * 2 + 1]); subMeshData._UV2s.Add(uv); } // UV3 if (vertexTriIndex < groupUV3Attr.Length) { Vector2 uv = new Vector2(groupUV3Attr[vertexTriIndex * 2 + 0], groupUV3Attr[vertexTriIndex * 2 + 1]); subMeshData._UV3s.Add(uv); } // Tangents if (bGenerateTangents && vertexTriIndex < groupTangentsAttr.Length) { Vector4 tangent = Vector4.zero; if (geoCache._tangentAttrInfo.tupleSize == 4) { tangent = new Vector4(-groupTangentsAttr[vertexTriIndex * 4 + 0], groupTangentsAttr[vertexTriIndex * 4 + 1], groupTangentsAttr[vertexTriIndex * 4 + 2], groupTangentsAttr[vertexTriIndex * 4 + 3]); } else if (geoCache._tangentAttrInfo.tupleSize == 3) { tangent = new Vector4(-groupTangentsAttr[vertexTriIndex * 3 + 0], groupTangentsAttr[vertexTriIndex * 3 + 1], groupTangentsAttr[vertexTriIndex * 3 + 2], 1); } subMeshData._tangents.Add(tangent); } subMeshData._indices.Add(subMeshData._vertices.Count - 1); //Debug.LogFormat("Submesh index mat {0} count {1}", faceMaterialID, subMeshData._indices.Count); } if (!geoCache._normalAttrInfo.exists) { // To generate normals after all the submeshes have been defined, we // calculate and store each triangle normal, along with the list // of connected vertices for each vertex int triIndex = subMeshData._indices.Count - 3; int i1 = subMeshData._indices[triIndex + 0]; int i2 = subMeshData._indices[triIndex + 1]; int i3 = subMeshData._indices[triIndex + 2]; // Triangle normal Vector3 p1 = subMeshData._vertices[i2] - subMeshData._vertices[i1]; Vector3 p2 = subMeshData._vertices[i3] - subMeshData._vertices[i1]; Vector3 normal = Vector3.Cross(p1, p2).normalized; subMeshData._triangleNormals.Add(normal); int normalIndex = subMeshData._triangleNormals.Count - 1; // Connected vertices geoCache._sharedNormalIndices[groupVertexList[vertexIndex + 0]].Add(new VertexEntry(submeshID, i1, normalIndex)); geoCache._sharedNormalIndices[groupVertexList[vertexIndex + 1]].Add(new VertexEntry(submeshID, i2, normalIndex)); geoCache._sharedNormalIndices[groupVertexList[vertexIndex + 2]].Add(new VertexEntry(submeshID, i3, normalIndex)); } } } int numSubmeshes = subMeshesMap.Keys.Count; bool bGenerated = false; if (numSubmeshes > 0) { if (!geoCache._normalAttrInfo.exists) { // Normal calculation // Go throuch each vertex for the entire geometry and calculate the normal vector based on connected // vertices. This includes vertex connections between submeshes so we should get smooth transitions across submeshes. int numSharedNormals = geoCache._sharedNormalIndices.Length; for (int a = 0; a < numSharedNormals; ++a) { for (int b = 0; b < geoCache._sharedNormalIndices[a].Count; ++b) { Vector3 sumNormal = new Vector3(); VertexEntry leftEntry = geoCache._sharedNormalIndices[a][b]; HEU_MeshData leftSubMesh = subMeshesMap[leftEntry._meshKey]; List<VertexEntry> rightList = geoCache._sharedNormalIndices[a]; for (int c = 0; c < rightList.Count; ++c) { VertexEntry rightEntry = rightList[c]; HEU_MeshData rightSubMesh = subMeshesMap[rightEntry._meshKey]; if (leftEntry._vertexIndex == rightEntry._vertexIndex) { sumNormal += rightSubMesh._triangleNormals[rightEntry._normalIndex]; } else { float dot = Vector3.Dot(leftSubMesh._triangleNormals[leftEntry._normalIndex], rightSubMesh._triangleNormals[rightEntry._normalIndex]); if (dot >= geoCache._cosineThreshold) { sumNormal += rightSubMesh._triangleNormals[rightEntry._normalIndex]; } } } leftSubMesh._normals[leftEntry._vertexIndex] = sumNormal.normalized; } } } // Go through each valid submesh data and upload into a CombineInstance for combining. // Each CombineInstance represents a submesh in the final mesh. // And each submesh in that final mesh corresponds to a material. // Filter out only the submeshes with valid geometry List<Material> validMaterials = new List<Material>(); List<int> validSubmeshes = new List<int>(); Dictionary<int, HEU_MaterialData> assetMaterialMap = asset.GetMaterialDataMap(); foreach (KeyValuePair<int, HEU_MeshData> meshPair in subMeshesMap) { HEU_MeshData meshData = meshPair.Value; if (meshData._indices.Count > 0) { int materialKey = meshPair.Key; // Find the material or create it HEU_MaterialData materialData = null; HEU_UnityMaterialInfo unityMaterialInfo = null; if (geoCache._unityMaterialInfos.TryGetValue(materialKey, out unityMaterialInfo)) { if (!assetMaterialMap.TryGetValue(materialKey, out materialData)) { // Create the material materialData = asset.CreateUnitySubstanceMaterialData(materialKey, unityMaterialInfo._unityMaterialPath, unityMaterialInfo._substancePath, unityMaterialInfo._substanceIndex); assetMaterialMap.Add(materialData._materialKey, materialData); } } else if (!assetMaterialMap.TryGetValue(materialKey, out materialData)) { if (materialKey == defaultMaterialKey) { materialData = asset.GetOrCreateDefaultMaterialInCache(session, geoCache.GeoID, geoCache.PartID, false); } else { materialData = asset.CreateHoudiniMaterialData(session, materialKey, geoCache.GeoID, geoCache.PartID); } } if (materialData != null) { validSubmeshes.Add(meshPair.Key); validMaterials.Add(materialData._material); if (materialData != null && bPartInstanced) { // Handle GPU instancing on material for instanced meshes if (materialData._materialSource != HEU_MaterialData.Source.UNITY && materialData._materialSource != HEU_MaterialData.Source.SUBSTANCE) { // Always enable GPU instancing for material generated from Houdini HEU_MaterialFactory.EnableGPUInstancing(materialData._material); } } } } } int validNumSubmeshes = validSubmeshes.Count; CombineInstance[] meshCombiner = new CombineInstance[validNumSubmeshes]; for (int submeshIndex = 0; submeshIndex < validNumSubmeshes; ++submeshIndex) { HEU_MeshData submesh = subMeshesMap[validSubmeshes[submeshIndex]]; CombineInstance combine = new CombineInstance(); combine.mesh = new Mesh(); #if UNITY_2017_3_OR_NEWER combine.mesh.indexFormat = UnityEngine.Rendering.IndexFormat.UInt32; #endif combine.mesh.SetVertices(submesh._vertices); combine.mesh.SetIndices(submesh._indices.ToArray(), MeshTopology.Triangles, 0); if (submesh._colors.Count > 0) { combine.mesh.SetColors(submesh._colors); } if (submesh._normals.Count > 0) { combine.mesh.SetNormals(submesh._normals); } if (submesh._tangents.Count > 0) { combine.mesh.SetTangents(submesh._tangents); } if (bGenerateUVs) { // TODO: revisit to test this out Vector2[] generatedUVs = HEU_GeometryUtility.GeneratePerTriangle(combine.mesh); if (generatedUVs != null) { combine.mesh.uv = generatedUVs; } } else if (submesh._UVs.Count > 0) { combine.mesh.SetUVs(0, submesh._UVs); } if (submesh._UV2s.Count > 0) { combine.mesh.SetUVs(1, submesh._UV2s); } if (submesh._UV3s.Count > 0) { combine.mesh.SetUVs(2, submesh._UV3s); } combine.transform = Matrix4x4.identity; combine.mesh.RecalculateBounds(); //Debug.LogFormat("Number of submeshes {0}", combine.mesh.subMeshCount); meshCombiner[submeshIndex] = combine; } // Geometry data MeshFilter meshFilter = HEU_GeneralUtility.GetOrCreateComponent<MeshFilter>(gameObject); meshFilter.sharedMesh = new Mesh(); #if UNITY_2017_3_OR_NEWER meshFilter.sharedMesh.indexFormat = UnityEngine.Rendering.IndexFormat.UInt32; #endif meshFilter.sharedMesh.name = geoCache._partName + "_mesh"; meshFilter.sharedMesh.CombineMeshes(meshCombiner, false, false); meshFilter.sharedMesh.RecalculateBounds(); if (!geoCache._tangentAttrInfo.exists && bGenerateTangents) { HEU_GeometryUtility.CalculateMeshTangents(meshFilter.sharedMesh); } meshFilter.sharedMesh.UploadMeshData(true); // Render data MeshRenderer meshRenderer = HEU_GeneralUtility.GetOrCreateComponent<MeshRenderer>(gameObject); meshRenderer.sharedMaterials = validMaterials.ToArray(); bGenerated = true; } #if HEU_PROFILER_ON Debug.LogFormat("GENERATE MESH TIME:: {0}", (Time.realtimeSinceStartup - generateMeshTime)); #endif return bGenerated; }