private void CreateNewInstanceFromObject(GameObject sourceObject, int instanceIndex, Transform parentTransform, ref HAPI_Transform hapiTransform, string[] instancePrefixes, string instanceName) { GameObject newInstanceGO = null; if (HEU_EditorUtility.IsPrefabAsset(sourceObject)) { newInstanceGO = HEU_EditorUtility.InstantiatePrefab(sourceObject) as GameObject; newInstanceGO.transform.parent = parentTransform; } else { newInstanceGO = HEU_EditorUtility.InstantiateGameObject(sourceObject, parentTransform, false, false); } // To get the instance output name, we pass in the instance index. The actual name will be +1 from this. newInstanceGO.name = HEU_GeometryUtility.GetInstanceOutputName(instanceName, instancePrefixes, instanceIndex); newInstanceGO.isStatic = sourceObject.isStatic; Transform instanceTransform = newInstanceGO.transform; HEU_HAPIUtility.ApplyLocalTransfromFromHoudiniToUnityForInstance(ref hapiTransform, instanceTransform); // When cloning, the instanced part might have been made invisible, so re-enable renderer to have the cloned instance display it. HEU_GeneralUtility.SetGameObjectRenderVisiblity(newInstanceGO, true); HEU_GeneralUtility.SetGameObjectChildrenRenderVisibility(newInstanceGO, true); HEU_GeneralUtility.SetGameObjectColliderState(newInstanceGO, true); HEU_GeneralUtility.SetGameObjectChildrenColliderState(newInstanceGO, true); }
private void Generate(HEU_SessionBase session, HEU_HoudiniAsset houdiniAsset, out TerrainData terrainData, out Vector3 terrainOffsetPosition) { terrainData = null; terrainOffsetPosition = Vector3.zero; if (_heightMapVolumeData == null) { Debug.LogError("Unable to generate terrain due to not finding heightfield with display flag!"); return; } // Generate the terrain and terrain data from the heightmap bool bResult = HEU_GeometryUtility.GenerateTerrainFromVolume(session, ref _heightMapVolumeData._volumeInfo, _heightMapVolumeData._partData.ParentGeoNode.GeoID, _heightMapVolumeData._partData.PartID, _heightMapVolumeData._partData.OutputGameObject, out terrainData, out terrainOffsetPosition); if(!bResult) { return; } int terrainSize = terrainData.heightmapResolution; /* // Now set the alphamaps (textures) for the other layers // First, preprocess all volumes to get heightfield arrays, converted to proper size // Then, merge into a float[x,y,map] List<float[]> heightFields = new List<float[]>(); foreach(HEU_VolumeData volumeData in _textureVolumeDatas) { float[] hf = GetHeightfield(session, volumeData, terrainSize); if(hf != null && hf.Length > 0) { heightFields.Add(hf); } } // Assign floats to map float[,,] alphamap = new float[terrainSize, terrainSize, heightFields.Count]; for (int y = 0; y < terrainSize; ++y) { for (int x = 0; x < terrainSize; ++x) { for(int m = 0; m < terrainSize; ++m) { alphamap[x, y, m] = heightFields[m][y * terrainSize + x]; } } } terrainData.SetAlphamaps(0, 0, alphamap); */ }
private void GenerateEditPointBoxNewMesh() { if (_selectedAttributesStore == null) { return; } Vector3[] positionArray = new Vector3[0]; _selectedAttributesStore.GetPositionAttributeValues(out positionArray); int numPoints = positionArray.Length; if (numPoints != _previousEditMeshPointCount) { _editPointsSelectedIndices.Clear(); _previousEditMeshPointCount = numPoints; } if (numPoints > 0) { float boxSize = HEU_EditorUtility.GetSerializedProperty(_toolsInfoSerializedObject, "_editPointBoxSize").floatValue; Color unselectedColor = HEU_EditorUtility.GetSerializedProperty(_toolsInfoSerializedObject, "_editPointBoxUnselectedColor").colorValue; Color selectedColor = HEU_EditorUtility.GetSerializedProperty(_toolsInfoSerializedObject, "_editPointBoxSelectedColor").colorValue; if (_editPointBoxMaterial == null) { _editPointBoxMaterial = HEU_MaterialFactory.CreateNewHoudiniStandardMaterial("", "EditPointMaterial", false); } Color[] pointColors = new Color[numPoints]; for (int i = 0; i < numPoints; ++i) { pointColors[i] = unselectedColor; } int numSelected = _editPointsSelectedIndices.Count; for (int i = 0; i < numSelected; ++i) { pointColors[_editPointsSelectedIndices[i]] = selectedColor; } _editPointBoxMesh = HEU_GeometryUtility.GenerateCubeMeshFromPoints(positionArray, pointColors, boxSize); _GUIChanged = true; } }
public void GenerateTerrainWithAlphamaps(HEU_SessionBase session, HEU_HoudiniAsset houdiniAsset) { if(_layers == null || _layers.Count == 0) { Debug.LogError("Unable to generate terrain due to lack of heightfield layers!"); return; } HEU_VolumeLayer baseLayer = _layers[0]; HAPI_VolumeInfo baseVolumeInfo = new HAPI_VolumeInfo(); bool bResult = session.GetVolumeInfo(_ownerNode.GeoID, baseLayer._part.PartID, ref baseVolumeInfo); if (!bResult) { Debug.LogErrorFormat("Unable to get volume info for layer {0}!", baseLayer._layerName); return; } TerrainData terrainData = null; Vector3 terrainOffsetPosition = Vector3.zero; // Generate the terrain and terrain data from the heightmap's height layer bResult = HEU_GeometryUtility.GenerateTerrainFromVolume(session, ref baseVolumeInfo, baseLayer._part.ParentGeoNode.GeoID, baseLayer._part.PartID, baseLayer._part.OutputGameObject, out terrainData, out terrainOffsetPosition); if (!bResult) { return; } baseLayer._part.SetTerrainData(terrainData); baseLayer._part.SetTerrainOffsetPosition(terrainOffsetPosition); int terrainSize = terrainData.heightmapResolution; // Now set the alphamaps (textures with masks) for the other layers // First, preprocess all layers to get heightfield arrays, converted to proper size // Then, merge into a float[x,y,map] List<float[]> heightFields = new List<float[]>(); List<HEU_VolumeLayer> validLayers = new List<HEU_VolumeLayer>(); int numLayers = _layers.Count; for(int i = 1; i < numLayers; ++i) { float[] hf = HEU_GeometryUtility.GetHeightfieldFromPart(session, _ownerNode.GeoID, _layers[i]._part.PartID, _layers[i]._part.PartName, terrainSize); if (hf != null && hf.Length > 0) { heightFields.Add(hf); validLayers.Add(_layers[i]); } } // Total maps is masks plus base height layer int numMaps = heightFields.Count + 1; // Assign floats to alpha map float[,,] alphamap = new float[terrainSize, terrainSize, numMaps]; for (int y = 0; y < terrainSize; ++y) { for (int x = 0; x < terrainSize; ++x) { float f = 0f; for (int m = numMaps - 1; m > 0; --m) { float a = heightFields[m - 1][y + terrainSize * x]; a = Mathf.Clamp01(a - f) * validLayers[m - 1]._strength; alphamap[x, y, m] = a; f += a; } // Base layer gets leftover value alphamap[x, y, 0] = Mathf.Clamp01(1.0f - f) * baseLayer._strength; } } #if UNITY_2018_3_OR_NEWER // Create TerrainLayer for each heightfield layer // Note that at time of this implementation the new Unity terrain // is still in beta. Therefore, the following layer creation is subject // to change. TerrainLayer[] terrainLayers = new TerrainLayer[numMaps]; for (int m = 0; m < numMaps; ++m) { terrainLayers[m] = new TerrainLayer(); HEU_VolumeLayer layer = (m == 0) ? baseLayer : validLayers[m - 1]; terrainLayers[m].diffuseTexture = layer._diffuseTexture; terrainLayers[m].diffuseRemapMin = Vector4.zero; terrainLayers[m].diffuseRemapMax = Vector4.one; terrainLayers[m].maskMapTexture = layer._maskTexture; terrainLayers[m].maskMapRemapMin = Vector4.zero; terrainLayers[m].maskMapRemapMax = Vector4.one; terrainLayers[m].metallic = layer._metallic; terrainLayers[m].normalMapTexture = layer._normalTexture; terrainLayers[m].normalScale = layer._normalScale; terrainLayers[m].smoothness = layer._smoothness; terrainLayers[m].specular = layer._specularColor; terrainLayers[m].tileOffset = layer._tileOffset; if (layer._tileSize.magnitude == 0f) { // Use texture size if tile size is 0 layer._tileSize = new Vector2(layer._diffuseTexture.width, layer._diffuseTexture.height); } terrainLayers[m].tileSize = layer._tileSize; } terrainData.terrainLayers = terrainLayers; #else // Need to create SplatPrototype for each layer in heightfield, representing the textures. SplatPrototype[] splatPrototypes = new SplatPrototype[numMaps]; for (int m = 0; m < numMaps; ++m) { splatPrototypes[m] = new SplatPrototype(); HEU_VolumeLayer layer = (m == 0) ? baseLayer : validLayers[m - 1]; splatPrototypes[m].texture = layer._diffuseTexture; splatPrototypes[m].tileOffset = layer._tileOffset; if(layer._tileSize.magnitude == 0f) { // Use texture size if tile size is 0 layer._tileSize = new Vector2(layer._diffuseTexture.width, layer._diffuseTexture.height); } splatPrototypes[m].tileSize = layer._tileSize; splatPrototypes[m].metallic = layer._metallic; splatPrototypes[m].smoothness = layer._smoothness; splatPrototypes[m].normalMap = layer._normalTexture; } terrainData.splatPrototypes = splatPrototypes; #endif terrainData.SetAlphamaps(0, 0, alphamap); }
public bool GenerateTerrainBuffers(HEU_SessionBase session, HAPI_NodeId nodeID, List<HAPI_PartInfo> volumeParts, out List<HEU_LoadBufferVolume> volumeBuffers) { volumeBuffers = null; if (volumeParts.Count == 0) { return true; } volumeBuffers = new List<HEU_LoadBufferVolume>(); int numParts = volumeParts.Count; for (int i = 0; i < numParts; ++i) { HAPI_VolumeInfo volumeInfo = new HAPI_VolumeInfo(); bool bResult = session.GetVolumeInfo(nodeID, volumeParts[i].id, ref volumeInfo); if (!bResult || volumeInfo.tupleSize != 1 || volumeInfo.zLength != 1 || volumeInfo.storage != HAPI_StorageType.HAPI_STORAGETYPE_FLOAT) { SetLog(HEU_LoadData.LoadStatus.ERROR, "This heightfield is not supported. Please check documentation."); return false; } if (volumeInfo.xLength != volumeInfo.yLength) { SetLog(HEU_LoadData.LoadStatus.ERROR, "Non-square sized terrain not supported."); return false; } string volumeName = HEU_SessionManager.GetString(volumeInfo.nameSH, session); bool bHeightPart = volumeName.Equals("height"); //Debug.LogFormat("Part name: {0}, GeoName: {1}, Volume Name: {2}, Display: {3}", part.PartName, geoNode.GeoName, volumeName, geoNode.Displayable); HEU_LoadBufferVolumeLayer layer = new HEU_LoadBufferVolumeLayer(); layer._layerName = volumeName; layer._partID = volumeParts[i].id; layer._heightMapSize = volumeInfo.xLength; Matrix4x4 volumeTransformMatrix = HEU_HAPIUtility.GetMatrixFromHAPITransform(ref volumeInfo.transform, false); layer._position = HEU_HAPIUtility.GetPosition(ref volumeTransformMatrix); Vector3 scale = HEU_HAPIUtility.GetScale(ref volumeTransformMatrix); // Calculate real terrain size in both Houdini and Unity. // The height values will be mapped over this terrain size. float gridSpacingX = scale.x * 2f; float gridSpacingY = scale.y * 2f; layer._terrainSizeX = Mathf.Round((volumeInfo.xLength - 1) * gridSpacingX); layer._terrainSizeY = Mathf.Round((volumeInfo.yLength - 1) * gridSpacingY); // Get volume bounds for calculating position offset session.GetVolumeBounds(nodeID, volumeParts[i].id, out layer._minBounds.x, out layer._minBounds.y, out layer._minBounds.z, out layer._maxBounds.x, out layer._maxBounds.y, out layer._maxBounds.z, out layer._center.x, out layer._center.y, out layer._center.z); LoadStringFromAttribute(session, nodeID, volumeParts[i].id, HEU_Defines.DEFAULT_UNITY_HEIGHTFIELD_TEXTURE_DIFFUSE_ATTR, ref layer._diffuseTexturePath); LoadStringFromAttribute(session, nodeID, volumeParts[i].id, HEU_Defines.DEFAULT_UNITY_HEIGHTFIELD_TEXTURE_MASK_ATTR, ref layer._maskTexturePath); LoadStringFromAttribute(session, nodeID, volumeParts[i].id, HEU_Defines.DEFAULT_UNITY_HEIGHTFIELD_TEXTURE_NORMAL_ATTR, ref layer._normalTexturePath); LoadFloatFromAttribute(session, nodeID, volumeParts[i].id, HEU_Defines.DEFAULT_UNITY_HEIGHTFIELD_NORMAL_SCALE_ATTR, ref layer._normalScale); LoadFloatFromAttribute(session, nodeID, volumeParts[i].id, HEU_Defines.DEFAULT_UNITY_HEIGHTFIELD_METALLIC_ATTR, ref layer._metallic); LoadFloatFromAttribute(session, nodeID, volumeParts[i].id, HEU_Defines.DEFAULT_UNITY_HEIGHTFIELD_SMOOTHNESS_ATTR, ref layer._smoothness); LoadLayerColorFromAttribute(session, nodeID, volumeParts[i].id, HEU_Defines.DEFAULT_UNITY_HEIGHTFIELD_SPECULAR_ATTR, ref layer._specularColor); LoadLayerVector2FromAttribute(session, nodeID, volumeParts[i].id, HEU_Defines.DEFAULT_UNITY_HEIGHTFIELD_TILE_OFFSET_ATTR, ref layer._tileOffset); LoadLayerVector2FromAttribute(session, nodeID, volumeParts[i].id, HEU_Defines.DEFAULT_UNITY_HEIGHTFIELD_TILE_SIZE_ATTR, ref layer._tileSize); // Get the height values from Houdini and find the min and max height range. if (!HEU_GeometryUtility.GetHeightfieldValues(session, volumeInfo.xLength, volumeInfo.yLength, nodeID, volumeParts[i].id, ref layer._rawHeights, ref layer._minHeight, ref layer._maxHeight)) { return false; } // TODO: Tried to replace above with this, but it flattens the heights //layer._rawHeights = HEU_GeometryUtility.GetHeightfieldFromPart(_session, nodeID, volumeParts[i].id, "part", volumeInfo.xLength); // Get the tile index, if it exists, for this part HAPI_AttributeInfo tileAttrInfo = new HAPI_AttributeInfo(); int[] tileAttrData = new int[0]; HEU_GeneralUtility.GetAttribute(session, nodeID, volumeParts[i].id, "tile", ref tileAttrInfo, ref tileAttrData, session.GetAttributeIntData); int tileIndex = 0; if (tileAttrInfo.exists && tileAttrData.Length == 1) { tileIndex = tileAttrData[0]; } // Add layer based on tile index if (tileIndex >= 0) { HEU_LoadBufferVolume volumeBuffer = null; for(int j = 0; j < volumeBuffers.Count; ++j) { if (volumeBuffers[j]._tileIndex == tileIndex) { volumeBuffer = volumeBuffers[j]; break; } } if (volumeBuffer == null) { volumeBuffer = new HEU_LoadBufferVolume(); volumeBuffer.InitializeBuffer(volumeParts[i].id, volumeName, false, false); volumeBuffer._tileIndex = tileIndex; volumeBuffers.Add(volumeBuffer); } if (bHeightPart) { // Height layer always first layer volumeBuffer._layers.Insert(0, layer); volumeBuffer._heightMapSize = layer._heightMapSize; volumeBuffer._terrainSizeX = layer._terrainSizeX; volumeBuffer._terrainSizeY = layer._terrainSizeY; volumeBuffer._heightRange = (layer._maxHeight - layer._minHeight); } else { volumeBuffer._layers.Add(layer); } } Sleep(); } // Each volume buffer is a self contained terrain tile foreach(HEU_LoadBufferVolume volumeBuffer in volumeBuffers) { List<HEU_LoadBufferVolumeLayer> layers = volumeBuffer._layers; //Debug.LogFormat("Heightfield: tile={0}, layers={1}", tile._tileIndex, layers.Count); int heightMapSize = volumeBuffer._heightMapSize; int numLayers = layers.Count; if (numLayers > 0) { // Convert heightmap values from Houdini to Unity volumeBuffer._heightMap = HEU_GeometryUtility.ConvertHeightMapHoudiniToUnity(heightMapSize, layers[0]._rawHeights, layers[0]._minHeight, layers[0]._maxHeight); Sleep(); // Convert splatmap values from Houdini to Unity. List<float[]> heightFields = new List<float[]>(); for(int m = 1; m < numLayers; ++m) { heightFields.Add(layers[m]._rawHeights); } volumeBuffer._splatMaps = HEU_GeometryUtility.ConvertHeightSplatMapHoudiniToUnity(heightMapSize, heightFields); volumeBuffer._position = new Vector3((volumeBuffer._terrainSizeX + volumeBuffer._layers[0]._minBounds.x), volumeBuffer._layers[0]._minHeight + volumeBuffer._layers[0]._position.y, volumeBuffer._layers[0]._minBounds.z); } } return true; }
public static 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; }