예제 #1
0
		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);
		}
예제 #2
0
		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);
			*/
		}
예제 #3
0
		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;
			}
		}
예제 #4
0
		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;
		}
예제 #6
0
		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;
		}