// TRANSFORMS ------------------------------------------------------------------------------------------------- /// <summary> /// Apply Houdini Engine world transform to Unity's transform object. /// This assumes given HAPI transform is in world space. /// </summary> /// <param name="hapiTransform">Houdini Engine transform to get data from</param> /// <param name="unityTransform">The Unity transform to apply data to</param> public static void ApplyWorldTransfromFromHoudiniToUnity(HAPI_Transform hapiTransform, Transform unityTransform) { // Houdini uses right-handed coordinate system, while Unity uses left-handed. // Note: we always use global transform space when communicating with Houdini // Invert the X for position unityTransform.position = new Vector3(-hapiTransform.position[0], hapiTransform.position[1], hapiTransform.position[2]); // Invert Y and Z for rotation Quaternion quaternion = new Quaternion(hapiTransform.rotationQuaternion[0], hapiTransform.rotationQuaternion[1], hapiTransform.rotationQuaternion[2], hapiTransform.rotationQuaternion[3]); Vector3 euler = quaternion.eulerAngles; euler.y = -euler.y; euler.z = -euler.z; unityTransform.rotation = Quaternion.Euler(euler); // No inversion required for scale // We can't directly set global scale in Unity, but the proper workaround is to unparent, set scale, then reparent Vector3 scale = new Vector3(hapiTransform.scale[0], hapiTransform.scale[1], hapiTransform.scale[2]); if (unityTransform.parent != null) { Transform parent = unityTransform.parent; unityTransform.parent = null; unityTransform.localScale = scale; unityTransform.parent = parent; } else { unityTransform.localScale = scale; } }
/// <summary> /// Apply the given HAPI transform to all parts of this node. /// </summary> /// <param name="hapiTransform">HAPI transform to apply</param> public void ApplyHAPITransform(ref HAPI_Transform hapiTransform) { foreach (HEU_PartData part in _parts) { part.ApplyHAPITransform(ref hapiTransform); } }
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); }
/// <summary> /// Returns Unity 4x4 matrix corresponding to the given HAPI_Transform. /// Converts from Houdini to Unity coordinate system. /// </summary> /// <param name="hapiTransform">HAPI transform to get values from</param> /// <returns>Matrix4x4 in Unity coordinate system</returns> public static Matrix4x4 GetMatrixFromHAPITransform(ref HAPI_Transform hapiTransform, bool bConvertToUnity = true) { float invert = bConvertToUnity ? -1f : 1f; // TODO: Refactor this so as to use a common function to get these values // Invert the X for position Vector3 position = new Vector3(invert * hapiTransform.position[0], hapiTransform.position[1], hapiTransform.position[2]); // Invert Y and Z for rotation Quaternion quaternion = new Quaternion(hapiTransform.rotationQuaternion[0], hapiTransform.rotationQuaternion[1], hapiTransform.rotationQuaternion[2], hapiTransform.rotationQuaternion[3]); Vector3 euler = quaternion.eulerAngles; euler.y = invert * euler.y; euler.z = invert * euler.z; Quaternion rotation = Quaternion.Euler(euler); // No inversion required for scale // We can't directly set global scale in Unity, but the proper workaround is to unparent, set scale, then reparent Vector3 scale = new Vector3(hapiTransform.scale[0], hapiTransform.scale[1], hapiTransform.scale[2]); Matrix4x4 matrix = new Matrix4x4(); matrix.SetTRS(position, rotation, scale); return(matrix); }
public void Initialize(HEU_SessionBase session, HAPI_ObjectInfo objectInfo, HAPI_Transform objectTranform, HEU_HoudiniAsset parentAsset) { _objectInfo = objectInfo; _objectTransform = objectTranform; _parentAsset = parentAsset; SyncWithObjectInfo(session); // Translate transform to Unity (TODO) List<HAPI_GeoInfo> geoInfos = new List<HAPI_GeoInfo>(); // Get display geo info HAPI_GeoInfo displayGeoInfo = new HAPI_GeoInfo(); if(!session.GetDisplayGeoInfo(_objectInfo.nodeId, ref displayGeoInfo)) { return; } //Debug.LogFormat("Found geoinfo with name {0} and id {1}", HEU_SessionManager.GetString(displayGeoInfo.nameSH, session), displayGeoInfo.nodeId); geoInfos.Add(displayGeoInfo); // TODO: The following editable node query also retrieves geo nodes for terrain with visualization nodes. Need to review to check if we're using the // correct query flags, and handling returned nodes correctly. // Get editable nodes, cook em, then create geo nodes for them HAPI_NodeId[] editableNodes = null; HEU_SessionManager.GetComposedChildNodeList(session, _objectInfo.nodeId, (int)HAPI_NodeType.HAPI_NODETYPE_SOP, (int)HAPI_NodeFlags.HAPI_NODEFLAGS_EDITABLE, true, out editableNodes); if(editableNodes != null) { foreach(HAPI_NodeId editNodeID in editableNodes) { if (editNodeID != displayGeoInfo.nodeId) { session.CookNode(editNodeID, HEU_PluginSettings.CookTemplatedGeos); HAPI_GeoInfo editGeoInfo = new HAPI_GeoInfo(); if (session.GetGeoInfo(editNodeID, ref editGeoInfo)) { geoInfos.Add(editGeoInfo); } } } } //Debug.LogFormat("Object 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); // Go through geo infos to create geometry int numGeoInfos = geoInfos.Count; for(int i = 0; i < numGeoInfos; ++i) { // Create GeoNode for each _geoNodes.Add(CreateGeoNode(session, geoInfos[i])); } // This has been moved to GenerateGeometry but kept here just in case. //ApplyObjectTransformToGeoNodes(); }
public void Reset() { _objName = ""; _parentAsset = null; _objectInfo = new HAPI_ObjectInfo(); _geoNodes = new List<HEU_GeoNode>(); _objectTransform = new HAPI_Transform(true); }
/// <summary> /// Copy src HAPI_Transfrom to dest HAPI_Transform. /// </summary> public static void CopyHAPITransform(ref HAPI_Transform src, ref HAPI_Transform dest) { src.position.CopyToWithResize<float>(ref dest.position); src.rotationQuaternion.CopyToWithResize<float>(ref dest.rotationQuaternion); src.scale.CopyToWithResize<float>(ref dest.scale); src.shear.CopyToWithResize<float>(ref dest.shear); dest.rstOrder = src.rstOrder; }
internal void Initialize(HEU_SessionBase session, HAPI_ObjectInfo objectInfo, HAPI_Transform objectTranform, HEU_HoudiniAsset parentAsset, bool bUseOutputNodes) { _objectInfo = objectInfo; _objectTransform = objectTranform; _parentAsset = parentAsset; SyncWithObjectInfo(session); // Translate transform to Unity (TODO) List<HAPI_GeoInfo> geoInfos = new List<HAPI_GeoInfo>(); HEU_HAPIUtility.GatherAllAssetGeoInfos(session, parentAsset.AssetInfo, objectInfo, bUseOutputNodes, ref geoInfos); int numGeoInfos = geoInfos.Count; for (int i = 0; i < numGeoInfos; ++i) { // Create GeoNode for each _geoNodes.Add(CreateGeoNode(session, geoInfos[i])); } }
private HEU_LoadBufferInstancer GeneratePartsInstancerBuffer(HEU_SessionBase session, HAPI_NodeId geoID, HAPI_PartId partID, string partName, HAPI_PartInfo partInfo) { // Get the instance node IDs to get the geometry to be instanced. // Get the instanced count to all the instances. These will end up being mesh references to the mesh from instance node IDs. // Get each instance's transform HAPI_Transform[] instanceTransforms = new HAPI_Transform[partInfo.instanceCount]; if (!HEU_GeneralUtility.GetArray3Arg(geoID, partID, HAPI_RSTOrder.HAPI_SRT, session.GetInstancerPartTransforms, instanceTransforms, 0, partInfo.instanceCount)) { SetLog(HEU_LoadData.LoadStatus.ERROR, string.Format("Unable to get instance transforms for part {0}", partName)); return null; } // Get part IDs for the parts being instanced HAPI_NodeId[] instanceNodeIDs = new HAPI_NodeId[partInfo.instancedPartCount]; if (!HEU_GeneralUtility.GetArray2Arg(geoID, partID, session.GetInstancedPartIds, instanceNodeIDs, 0, partInfo.instancedPartCount)) { SetLog(HEU_LoadData.LoadStatus.ERROR, string.Format("Unable to get instance node IDs for part {0}", partName)); return null; } // Get instance names if set string[] instancePrefixes = null; HAPI_AttributeInfo instancePrefixAttrInfo = new HAPI_AttributeInfo(); HEU_GeneralUtility.GetAttributeInfo(session, geoID, partID, HEU_Defines.DEFAULT_INSTANCE_PREFIX_ATTR, ref instancePrefixAttrInfo); if (instancePrefixAttrInfo.exists) { instancePrefixes = HEU_GeneralUtility.GetAttributeStringData(session, geoID, partID, HEU_Defines.DEFAULT_INSTANCE_PREFIX_ATTR, ref instancePrefixAttrInfo); } HEU_LoadBufferInstancer instancerBuffer = new HEU_LoadBufferInstancer(); instancerBuffer.InitializeBuffer(partID, partName, partInfo.isInstanced, true); instancerBuffer._instanceTransforms = instanceTransforms; instancerBuffer._instanceNodeIDs = instanceNodeIDs; instancerBuffer._instancePrefixes = instancePrefixes; return instancerBuffer; }
/// <summary> /// Gets the object infos and transforms for given asset. /// </summary> /// <param name="assetID">ID of the asset</param> /// <param name="nodeInfo">HAPI_NodeInfo of the asset</param> /// <param name="objectInfos">Array of retrieved object infos</param> /// <param name="objectTransforms">Array of retrieved object transforms</param> /// <returns>True if succesfully retrieved object infos and transforms</returns> public static bool GetObjectInfos(HEU_SessionBase session, HAPI_NodeId assetID, ref HAPI_NodeInfo nodeInfo, out HAPI_ObjectInfo[] objectInfos, out HAPI_Transform[] objectTransforms) { objectInfos = new HAPI_ObjectInfo[0]; objectTransforms = new HAPI_Transform[0]; if (nodeInfo.type == HAPI_NodeType.HAPI_NODETYPE_SOP) { // For SOP assets, we use the parent IDs to get the object info and geo info objectInfos = new HAPI_ObjectInfo[1]; if (!session.GetObjectInfo(nodeInfo.parentId, ref objectInfos[0])) { return(false); } // Identity transform will be used for SOP assets, so not querying transform objectTransforms = new HAPI_Transform[1]; objectTransforms[0] = new HAPI_Transform(true); } else if (nodeInfo.type == HAPI_NodeType.HAPI_NODETYPE_OBJ) { int objectCount = 0; if (!session.ComposeObjectList(assetID, out objectCount)) { return(false); } if (objectCount <= 0) { // Since this asset is an object type and has 0 object as children, we use the object itself objectInfos = new HAPI_ObjectInfo[1]; if (!session.GetObjectInfo(nodeInfo.id, ref objectInfos[0])) { return(false); } // Identity transform will be used for single object assets, so not querying transform objectTransforms = new HAPI_Transform[1]; objectTransforms[0] = new HAPI_Transform(true); } else { // This object has children, so use GetComposedObjectList to get list of HAPI_ObjectInfos objectInfos = new HAPI_ObjectInfo[objectCount]; if (!session.GetComposedObjectList(nodeInfo.parentId, objectInfos, 0, objectCount)) { return(false); } // Now get the object transforms objectTransforms = new HAPI_Transform[objectCount]; if (!HEU_SessionManager.GetComposedObjectTransformsMemorySafe(session, nodeInfo.parentId, HAPI_RSTOrder.HAPI_SRT, objectTransforms, 0, objectCount)) { return(false); } } } else { Debug.LogWarningFormat(HEU_Defines.HEU_NAME + ": Unsupported node type {0}", nodeInfo.type); return(false); } return(true); }
private void GenerateTerrain(List<HEU_LoadBufferVolume> terrainBuffers) { Transform parent = this.gameObject.transform; // Directory to store generated terrain files. string outputTerrainpath = GetOutputCacheDirectory(); outputTerrainpath = HEU_Platform.BuildPath(outputTerrainpath, "Terrain"); int numVolumes = terrainBuffers.Count; for(int t = 0; t < numVolumes; ++t) { if (terrainBuffers[t]._heightMap != null) { GameObject newGameObject = new GameObject("heightfield_" + terrainBuffers[t]._tileIndex); Transform newTransform = newGameObject.transform; newTransform.parent = parent; HEU_GeneratedOutput generatedOutput = new HEU_GeneratedOutput(); generatedOutput._outputData._gameObject = newGameObject; Terrain terrain = HEU_GeneralUtility.GetOrCreateComponent<Terrain>(newGameObject); TerrainCollider collider = HEU_GeneralUtility.GetOrCreateComponent<TerrainCollider>(newGameObject); if (!string.IsNullOrEmpty(terrainBuffers[t]._terrainDataPath)) { // Load the source TerrainData, then make a unique copy of it in the cache folder TerrainData sourceTerrainData = HEU_AssetDatabase.LoadAssetAtPath(terrainBuffers[t]._terrainDataPath, typeof(TerrainData)) as TerrainData; if (sourceTerrainData == null) { Debug.LogWarningFormat("TerrainData, set via attribute, not found at: {0}", terrainBuffers[t]._terrainDataPath); } terrain.terrainData = HEU_AssetDatabase.CopyUniqueAndLoadAssetAtAnyPath(sourceTerrainData, outputTerrainpath, typeof(TerrainData)) as TerrainData; if (terrain.terrainData != null) { // Store path so that it can be deleted on clean up AddGeneratedOutputFilePath(HEU_AssetDatabase.GetAssetPath(terrain.terrainData)); } } if (terrain.terrainData == null) { terrain.terrainData = new TerrainData(); } TerrainData terrainData = terrain.terrainData; collider.terrainData = terrainData; HEU_TerrainUtility.SetTerrainMaterial(terrain, terrainBuffers[t]._specifiedTerrainMaterialName); #if UNITY_2018_3_OR_NEWER terrain.allowAutoConnect = true; // This has to be set after setting material terrain.drawInstanced = true; #endif int heightMapSize = terrainBuffers[t]._heightMapWidth; terrainData.heightmapResolution = heightMapSize; if (terrainData.heightmapResolution != heightMapSize) { Debug.LogErrorFormat("Unsupported terrain size: {0}", heightMapSize); continue; } // The terrainData.baseMapResolution is not set here, but rather left to whatever default Unity uses // The terrainData.alphamapResolution is set later when setting the alphamaps. // 32 is the default for resolutionPerPatch const int detailResolution = 1024; const int resolutionPerPatch = 32; terrainData.SetDetailResolution(detailResolution, resolutionPerPatch); terrainData.SetHeights(0, 0, terrainBuffers[t]._heightMap); // Note that Unity uses a default height range of 600 when a flat terrain is created. // Without a non-zero value for the height range, user isn't able to draw heights. // Therefore, set 600 as the value if height range is currently 0 (due to flat heightfield). float heightRange = terrainBuffers[t]._heightRange; if (heightRange == 0) { heightRange = 600; } terrainData.size = new Vector3(terrainBuffers[t]._terrainSizeX, heightRange, terrainBuffers[t]._terrainSizeY); terrain.Flush(); // Set position HAPI_Transform hapiTransformVolume = new HAPI_Transform(true); hapiTransformVolume.position[0] += terrainBuffers[t]._position[0]; hapiTransformVolume.position[1] += terrainBuffers[t]._position[1]; hapiTransformVolume.position[2] += terrainBuffers[t]._position[2]; HEU_HAPIUtility.ApplyLocalTransfromFromHoudiniToUnity(ref hapiTransformVolume, newTransform); // Set layers Texture2D defaultTexture = HEU_VolumeCache.LoadDefaultSplatTexture(); int numLayers = terrainBuffers[t]._splatLayers.Count; #if UNITY_2018_3_OR_NEWER // Create TerrainLayer for each heightfield layer. // Note that height and mask layers are ignored (i.e. not created as TerrainLayers). // Since height layer is first, only process layers from 2nd index onwards. if (numLayers > 1) { // Keep existing TerrainLayers, and either update or append to them TerrainLayer[] existingTerrainLayers = terrainData.terrainLayers; // Total layers are existing layers + new alpha maps List<TerrainLayer> finalTerrainLayers = new List<TerrainLayer>(existingTerrainLayers); for (int m = 1; m < numLayers; ++m) { TerrainLayer terrainlayer = null; int terrainLayerIndex = -1; bool bSetTerrainLayerProperties = true; HEU_LoadBufferVolumeLayer layer = terrainBuffers[t]._splatLayers[m]; // Look up TerrainLayer file via attribute if user has set it if (!string.IsNullOrEmpty(layer._layerPath)) { terrainlayer = HEU_AssetDatabase.LoadAssetAtPath(layer._layerPath, typeof(TerrainLayer)) as TerrainLayer; if (terrainlayer == null) { Debug.LogWarningFormat("TerrainLayer, set via attribute, not found at: {0}", layer._layerPath); continue; } else { // Always check if its part of existing list so as not to add it again terrainLayerIndex = HEU_TerrainUtility.GetTerrainLayerIndex(terrainlayer, existingTerrainLayers); } } if (terrainlayer == null) { terrainlayer = new TerrainLayer(); terrainLayerIndex = finalTerrainLayers.Count; finalTerrainLayers.Add(terrainlayer); } else { // For existing TerrainLayer, make a copy of it if it has custom layer attributes // because we don't want to change the original TerrainLayer. if (layer._hasLayerAttributes && terrainLayerIndex >= 0) { // Copy the TerrainLayer file TerrainLayer prevTerrainLayer = terrainlayer; terrainlayer = HEU_AssetDatabase.CopyAndLoadAssetAtAnyPath(terrainlayer, outputTerrainpath, typeof(TerrainLayer), true) as TerrainLayer; if (terrainlayer != null) { // Update the TerrainLayer reference in the list with this copy finalTerrainLayers[terrainLayerIndex] = terrainlayer; // Store path for clean up later AddGeneratedOutputFilePath(HEU_AssetDatabase.GetAssetPath(terrainlayer)); } else { Debug.LogErrorFormat("Unable to copy TerrainLayer '{0}' for generating Terrain. " + "Using original TerrainLayer. Will not be able to set any TerrainLayer properties.", layer._layerName); terrainlayer = prevTerrainLayer; bSetTerrainLayerProperties = false; // Again, continuing on to keep proper indexing. } } } if (bSetTerrainLayerProperties) { if (!string.IsNullOrEmpty(layer._diffuseTexturePath)) { terrainlayer.diffuseTexture = HEU_MaterialFactory.LoadTexture(layer._diffuseTexturePath); } if (terrainlayer.diffuseTexture == null) { terrainlayer.diffuseTexture = defaultTexture; } terrainlayer.diffuseRemapMin = Vector4.zero; terrainlayer.diffuseRemapMax = Vector4.one; if (!string.IsNullOrEmpty(layer._maskTexturePath)) { terrainlayer.maskMapTexture = HEU_MaterialFactory.LoadTexture(layer._maskTexturePath); } terrainlayer.maskMapRemapMin = Vector4.zero; terrainlayer.maskMapRemapMax = Vector4.one; terrainlayer.metallic = layer._metallic; if (!string.IsNullOrEmpty(layer._normalTexturePath)) { terrainlayer.normalMapTexture = HEU_MaterialFactory.LoadTexture(layer._normalTexturePath); } terrainlayer.normalScale = layer._normalScale; terrainlayer.smoothness = layer._smoothness; terrainlayer.specular = layer._specularColor; terrainlayer.tileOffset = layer._tileOffset; if (layer._tileSize.magnitude == 0f && terrainlayer.diffuseTexture != null) { // Use texture size if tile size is 0 layer._tileSize = new Vector2(terrainlayer.diffuseTexture.width, terrainlayer.diffuseTexture.height); } terrainlayer.tileSize = layer._tileSize; } } terrainData.terrainLayers = finalTerrainLayers.ToArray(); } #else // Need to create SplatPrototype for each layer in heightfield, representing the textures. SplatPrototype[] splatPrototypes = new SplatPrototype[numLayers]; for (int m = 0; m < numLayers; ++m) { splatPrototypes[m] = new SplatPrototype(); HEU_LoadBufferVolumeLayer layer = terrainBuffers[t]._splatLayers[m]; Texture2D diffuseTexture = null; if (!string.IsNullOrEmpty(layer._diffuseTexturePath)) { diffuseTexture = HEU_MaterialFactory.LoadTexture(layer._diffuseTexturePath); } if (diffuseTexture == null) { diffuseTexture = defaultTexture; } splatPrototypes[m].texture = diffuseTexture; splatPrototypes[m].tileOffset = layer._tileOffset; if (layer._tileSize.magnitude == 0f && diffuseTexture != null) { // Use texture size if tile size is 0 layer._tileSize = new Vector2(diffuseTexture.width, diffuseTexture.height); } splatPrototypes[m].tileSize = layer._tileSize; splatPrototypes[m].metallic = layer._metallic; splatPrototypes[m].smoothness = layer._smoothness; if (!string.IsNullOrEmpty(layer._normalTexturePath)) { splatPrototypes[m].normalMap = HEU_MaterialFactory.LoadTexture(layer._normalTexturePath); } } terrainData.splatPrototypes = splatPrototypes; #endif // Set the splatmaps if (terrainBuffers[t]._splatMaps != null) { // Set the alphamap size before setting the alphamaps to get correct scaling // The alphamap size comes from the first alphamap layer int alphamapResolution = terrainBuffers[t]._heightMapWidth; if (numLayers > 1) { alphamapResolution = terrainBuffers[t]._splatLayers[1]._heightMapWidth; } terrainData.alphamapResolution = alphamapResolution; terrainData.SetAlphamaps(0, 0, terrainBuffers[t]._splatMaps); } // Set the tree scattering if (terrainBuffers[t]._scatterTrees != null) { HEU_TerrainUtility.ApplyScatterTrees(terrainData, terrainBuffers[t]._scatterTrees); } // Set the detail layers if (terrainBuffers[t]._detailPrototypes != null) { HEU_TerrainUtility.ApplyDetailLayers(terrain, terrainData, terrainBuffers[t]._detailProperties, terrainBuffers[t]._detailPrototypes, terrainBuffers[t]._detailMaps); } terrainBuffers[t]._generatedOutput = generatedOutput; _generatedOutputs.Add(generatedOutput); SetOutputVisiblity(terrainBuffers[t]); } } }
private void GenerateTerrain(List<HEU_LoadBufferVolume> terrainBuffers) { Transform parent = this.gameObject.transform; int numVolumes = terrainBuffers.Count; for(int t = 0; t < numVolumes; ++t) { if (terrainBuffers[t]._heightMap != null) { GameObject newGameObject = new GameObject("heightfield_" + terrainBuffers[t]._tileIndex); Transform newTransform = newGameObject.transform; newTransform.parent = parent; HEU_GeneratedOutput generatedOutput = new HEU_GeneratedOutput(); generatedOutput._outputData._gameObject = newGameObject; Terrain terrain = HEU_GeneralUtility.GetOrCreateComponent<Terrain>(newGameObject); TerrainCollider collider = HEU_GeneralUtility.GetOrCreateComponent<TerrainCollider>(newGameObject); if (!string.IsNullOrEmpty(terrainBuffers[t]._terrainDataPath)) { terrain.terrainData = HEU_AssetDatabase.LoadAssetAtPath(terrainBuffers[t]._terrainDataPath, typeof(TerrainData)) as TerrainData; if (terrain.terrainData == null) { Debug.LogWarningFormat("TerrainData, set via attribute, not found at: {0}", terrainBuffers[t]._terrainDataPath); } } if (terrain.terrainData == null) { terrain.terrainData = new TerrainData(); } TerrainData terrainData = terrain.terrainData; collider.terrainData = terrainData; HEU_TerrainUtility.SetTerrainMaterial(terrain); #if UNITY_2018_3_OR_NEWER terrain.allowAutoConnect = true; // This has to be set after setting material terrain.drawInstanced = true; #endif int heightMapSize = terrainBuffers[t]._heightMapWidth; terrainData.heightmapResolution = heightMapSize; if (terrainData.heightmapResolution != heightMapSize) { Debug.LogErrorFormat("Unsupported terrain size: {0}", heightMapSize); continue; } // The terrainData.baseMapResolution is not set here, but rather left to whatever default Unity uses // The terrainData.alphamapResolution is set later when setting the alphamaps. // 32 is the default for resolutionPerPatch const int detailResolution = 1024; const int resolutionPerPatch = 32; terrainData.SetDetailResolution(detailResolution, resolutionPerPatch); terrainData.SetHeights(0, 0, terrainBuffers[t]._heightMap); // Note that Unity uses a default height range of 600 when a flat terrain is created. // Without a non-zero value for the height range, user isn't able to draw heights. // Therefore, set 600 as the value if height range is currently 0 (due to flat heightfield). float heightRange = terrainBuffers[t]._heightRange; if (heightRange == 0) { heightRange = 600; } terrainData.size = new Vector3(terrainBuffers[t]._terrainSizeX, heightRange, terrainBuffers[t]._terrainSizeY); terrain.Flush(); // Set position HAPI_Transform hapiTransformVolume = new HAPI_Transform(true); hapiTransformVolume.position[0] += terrainBuffers[t]._position[0]; hapiTransformVolume.position[1] += terrainBuffers[t]._position[1]; hapiTransformVolume.position[2] += terrainBuffers[t]._position[2]; HEU_HAPIUtility.ApplyLocalTransfromFromHoudiniToUnity(ref hapiTransformVolume, newTransform); // Set layers Texture2D defaultTexture = HEU_VolumeCache.LoadDefaultSplatTexture(); int numLayers = terrainBuffers[t]._layers.Count; #if UNITY_2018_3_OR_NEWER // Create TerrainLayer for each heightfield layer. // Note that height and mask layers are ignored (i.e. not created as TerrainLayers). // Since height layer is first, only process layers from 2nd index onwards. if (numLayers > 1) { TerrainLayer[] terrainLayers = new TerrainLayer[numLayers - 1]; for (int m = 1; m < numLayers; ++m) { TerrainLayer terrainlayer = null; HEU_LoadBufferVolumeLayer layer = terrainBuffers[t]._layers[m]; // Look up TerrainLayer file via attribute if user has set it if (!string.IsNullOrEmpty(layer._layerPath)) { terrainlayer = HEU_AssetDatabase.LoadAssetAtPath(layer._layerPath, typeof(TerrainLayer)) as TerrainLayer; if (terrainlayer == null) { Debug.LogWarningFormat("TerrainLayer, set via attribute, not found at: {0}", layer._layerPath); continue; } } if (terrainlayer == null) { terrainlayer = new TerrainLayer(); } if (!string.IsNullOrEmpty(layer._diffuseTexturePath)) { terrainlayer.diffuseTexture = HEU_MaterialFactory.LoadTexture(layer._diffuseTexturePath); } if (terrainlayer.diffuseTexture == null) { terrainlayer.diffuseTexture = defaultTexture; } terrainlayer.diffuseRemapMin = Vector4.zero; terrainlayer.diffuseRemapMax = Vector4.one; if (!string.IsNullOrEmpty(layer._maskTexturePath)) { terrainlayer.maskMapTexture = HEU_MaterialFactory.LoadTexture(layer._maskTexturePath); } terrainlayer.maskMapRemapMin = Vector4.zero; terrainlayer.maskMapRemapMax = Vector4.one; terrainlayer.metallic = layer._metallic; if (!string.IsNullOrEmpty(layer._normalTexturePath)) { terrainlayer.normalMapTexture = HEU_MaterialFactory.LoadTexture(layer._normalTexturePath); } terrainlayer.normalScale = layer._normalScale; terrainlayer.smoothness = layer._smoothness; terrainlayer.specular = layer._specularColor; terrainlayer.tileOffset = layer._tileOffset; if (layer._tileSize.magnitude == 0f && terrainlayer.diffuseTexture != null) { // Use texture size if tile size is 0 layer._tileSize = new Vector2(terrainlayer.diffuseTexture.width, terrainlayer.diffuseTexture.height); } terrainlayer.tileSize = layer._tileSize; // Note index is m - 1 due to skipping height layer terrainLayers[m - 1] = terrainlayer; } terrainData.terrainLayers = terrainLayers; } #else // Need to create SplatPrototype for each layer in heightfield, representing the textures. SplatPrototype[] splatPrototypes = new SplatPrototype[numLayers]; for (int m = 0; m < numLayers; ++m) { splatPrototypes[m] = new SplatPrototype(); HEU_LoadBufferVolumeLayer layer = terrainBuffers[t]._layers[m]; Texture2D diffuseTexture = null; if (!string.IsNullOrEmpty(layer._diffuseTexturePath)) { diffuseTexture = HEU_MaterialFactory.LoadTexture(layer._diffuseTexturePath); } if (diffuseTexture == null) { diffuseTexture = defaultTexture; } splatPrototypes[m].texture = diffuseTexture; splatPrototypes[m].tileOffset = layer._tileOffset; if (layer._tileSize.magnitude == 0f && diffuseTexture != null) { // Use texture size if tile size is 0 layer._tileSize = new Vector2(diffuseTexture.width, diffuseTexture.height); } splatPrototypes[m].tileSize = layer._tileSize; splatPrototypes[m].metallic = layer._metallic; splatPrototypes[m].smoothness = layer._smoothness; if (!string.IsNullOrEmpty(layer._normalTexturePath)) { splatPrototypes[m].normalMap = HEU_MaterialFactory.LoadTexture(layer._normalTexturePath); } } terrainData.splatPrototypes = splatPrototypes; #endif // Set the splatmaps if (terrainBuffers[t]._splatMaps != null) { // Set the alphamap size before setting the alphamaps to get correct scaling // The alphamap size comes from the first alphamap layer int alphamapResolution = terrainBuffers[t]._heightMapWidth; if (numLayers > 1) { alphamapResolution = terrainBuffers[t]._layers[1]._heightMapWidth; } terrainData.alphamapResolution = alphamapResolution; terrainData.SetAlphamaps(0, 0, terrainBuffers[t]._splatMaps); } // Set the tree scattering if (terrainBuffers[t]._scatterTrees != null) { HEU_TerrainUtility.ApplyScatter(terrainData, terrainBuffers[t]._scatterTrees); } terrainBuffers[t]._generatedOutput = generatedOutput; _generatedOutputs.Add(generatedOutput); SetOutputVisiblity(terrainBuffers[t]); } } }
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; 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; }
// <summary> /// Get the transform of an OBJ node. /// </summary> /// <param name="nodeID">The object node ID</param> /// <param name="relativeToNodeID">The object node ID of the object to which the returned transform will be relative to. -1 if want object's local transform</param> /// <param name="rstOrder">The transform order</param> /// <param name="hapiTransform">Transform info to populate</param> /// <returns>True if successfully queried transform info</returns> public virtual bool GetObjectTransform(HAPI_NodeId nodeID, HAPI_NodeId relativeToNodeID, HAPI_RSTOrder rstOrder, ref HAPI_Transform hapiTransform) { return false; }
/// <summary> /// Retrieves object info from Houdini session and updates internal state. /// New geo nodes are created, unused geo nodes are destroyed. /// Geo nodes are then refreshed to be in sync with Houdini session. /// </summary> /// <returns>True if internal state has changed (including geometry).</returns> public void UpdateObject(HEU_SessionBase session, bool bForceUpdate) { // Update the geo info if (!session.GetObjectInfo(ObjectID, ref _objectInfo)) { return; } SyncWithObjectInfo(session); // Update the object transform _objectTransform = ParentAsset.GetObjectTransform(session, ObjectID); // Container for existing geo nodes that are still in use List<HEU_GeoNode> geoNodesToKeep = new List<HEU_GeoNode>(); // Container for new geo infos that need to be created List<HAPI_GeoInfo> newGeoInfosToCreate = new List<HAPI_GeoInfo>(); if (_objectInfo.haveGeosChanged || bForceUpdate) { // Indicates that the geometry nodes have changed //Debug.Log("Geos have changed!"); // Form a list of geo infos that are now present after cooking List<HAPI_GeoInfo> postCookGeoInfos = new List<HAPI_GeoInfo>(); // Get the display geo info HAPI_GeoInfo displayGeoInfo = new HAPI_GeoInfo(); if (session.GetDisplayGeoInfo(_objectInfo.nodeId, ref displayGeoInfo, false)) { postCookGeoInfos.Add(displayGeoInfo); } else { displayGeoInfo.nodeId = HEU_Defines.HEU_INVALID_NODE_ID; } // Get editable nodes, cook em, then create geo nodes for them HAPI_NodeId[] editableNodes = null; HEU_SessionManager.GetComposedChildNodeList(session, _objectInfo.nodeId, (int)HAPI_NodeType.HAPI_NODETYPE_SOP, (int)HAPI_NodeFlags.HAPI_NODEFLAGS_EDITABLE, true, out editableNodes); if (editableNodes != null) { foreach (HAPI_NodeId editNodeID in editableNodes) { if (editNodeID != displayGeoInfo.nodeId) { session.CookNode(editNodeID, HEU_PluginSettings.CookTemplatedGeos); HAPI_GeoInfo editGeoInfo = new HAPI_GeoInfo(); if (session.GetGeoInfo(editNodeID, ref editGeoInfo)) { postCookGeoInfos.Add(editGeoInfo); } } } } // Now for each geo node that are present after cooking, we check if its // new or whether we already have it prior to cooking. int numPostCookGeoInfos = postCookGeoInfos.Count; for (int i = 0; i < numPostCookGeoInfos; i++) { bool bFound = false; for (int j = 0; j < _geoNodes.Count; j++) { string geoName = HEU_SessionManager.GetString(postCookGeoInfos[i].nameSH, session); if (geoName.Equals(_geoNodes[j].GeoName)) { _geoNodes[j].SetGeoInfo(postCookGeoInfos[i]); geoNodesToKeep.Add(_geoNodes[j]); _geoNodes.RemoveAt(j); bFound = true; break; } } if (!bFound) { newGeoInfosToCreate.Add(postCookGeoInfos[i]); } } // Whatever is left in _geoNodes is no longer needed so clean up int numCurrentGeos = _geoNodes.Count; for (int i = 0; i < numCurrentGeos; ++i) { _geoNodes[i].DestroyAllData(); } } else { Debug.Assert(_objectInfo.geoCount == _geoNodes.Count, "Expected same number of geometry nodes."); } // Go through the old geo nodes that are still in use and update if necessary. foreach (HEU_GeoNode geoNode in geoNodesToKeep) { // Get geo info and check if geo changed bool bGeoChanged = bForceUpdate || geoNode.HasGeoNodeChanged(session); if (bGeoChanged) { geoNode.UpdateGeo(session); } else { if (_objectInfo.haveGeosChanged) { // Clear object instances since the object info has changed. // Without this, the object instances were never getting updated // if only the inputs changed but not outputs (of instancers). geoNode.ClearObjectInstances(); } // Visiblity might have changed, so update that geoNode.CalculateVisiblity(IsVisible()); geoNode.CalculateColliderState(); } } // Create the new geo infos and add to our keep list foreach (HAPI_GeoInfo newGeoInfo in newGeoInfosToCreate) { geoNodesToKeep.Add(CreateGeoNode(session, newGeoInfo)); } // Overwrite the old list with new _geoNodes = geoNodesToKeep; // Updating the trasform is done in GenerateGeometry }
private void GenerateTerrain(List<HEU_LoadBufferVolume> terrainBuffers) { Transform parent = this.gameObject.transform; int numVolues = terrainBuffers.Count; for(int t = 0; t < numVolues; ++t) { if (terrainBuffers[t]._heightMap != null) { GameObject newGameObject = new GameObject("heightfield_" + terrainBuffers[t]._tileIndex); Transform newTransform = newGameObject.transform; newTransform.parent = parent; HEU_GeneratedOutput generatedOutput = new HEU_GeneratedOutput(); generatedOutput._outputData._gameObject = newGameObject; Terrain terrain = HEU_GeneralUtility.GetOrCreateComponent<Terrain>(newGameObject); TerrainCollider collider = HEU_GeneralUtility.GetOrCreateComponent<TerrainCollider>(newGameObject); terrain.terrainData = new TerrainData(); TerrainData terrainData = terrain.terrainData; collider.terrainData = terrainData; int heightMapSize = terrainBuffers[t]._heightMapSize; terrainData.heightmapResolution = heightMapSize; if (terrainData.heightmapResolution != heightMapSize) { Debug.LogErrorFormat("Unsupported terrain size: {0}", heightMapSize); continue; } terrainData.baseMapResolution = heightMapSize; terrainData.alphamapResolution = heightMapSize; const int resolutionPerPatch = 128; terrainData.SetDetailResolution(resolutionPerPatch, resolutionPerPatch); terrainData.SetHeights(0, 0, terrainBuffers[t]._heightMap); terrainData.size = new Vector3(terrainBuffers[t]._terrainSizeX, terrainBuffers[t]._heightRange, terrainBuffers[t]._terrainSizeY); terrain.Flush(); // Set position HAPI_Transform hapiTransformVolume = new HAPI_Transform(true); hapiTransformVolume.position[0] += terrainBuffers[t]._position[0]; hapiTransformVolume.position[1] += terrainBuffers[t]._position[1]; hapiTransformVolume.position[2] += terrainBuffers[t]._position[2]; HEU_HAPIUtility.ApplyLocalTransfromFromHoudiniToUnity(ref hapiTransformVolume, newTransform); // Set layers Texture2D defaultTexture = HEU_VolumeCache.LoadDefaultSplatTexture(); int numLayers = terrainBuffers[t]._layers.Count; #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[numLayers]; for (int m = 0; m < numLayers; ++m) { terrainLayers[m] = new TerrainLayer(); HEU_LoadBufferVolumeLayer layer = terrainBuffers[t]._layers[m]; if (!string.IsNullOrEmpty(layer._diffuseTexturePath)) { // Using Resources.Load is much faster than AssetDatabase.Load //terrainLayers[m].diffuseTexture = HEU_MaterialFactory.LoadTexture(layer._diffuseTexturePath); terrainLayers[m].diffuseTexture = Resources.Load<Texture2D>(layer._diffuseTexturePath); } if (terrainLayers[m].diffuseTexture == null) { terrainLayers[m].diffuseTexture = defaultTexture; } terrainLayers[m].diffuseRemapMin = Vector4.zero; terrainLayers[m].diffuseRemapMax = Vector4.one; if (!string.IsNullOrEmpty(layer._maskTexturePath)) { // Using Resources.Load is much faster than AssetDatabase.Load //terrainLayers[m].maskMapTexture = HEU_MaterialFactory.LoadTexture(layer._maskTexturePath); terrainLayers[m].maskMapTexture = Resources.Load<Texture2D>(layer._maskTexturePath); } terrainLayers[m].maskMapRemapMin = Vector4.zero; terrainLayers[m].maskMapRemapMax = Vector4.one; terrainLayers[m].metallic = layer._metallic; if (!string.IsNullOrEmpty(layer._normalTexturePath)) { terrainLayers[m].normalMapTexture = HEU_MaterialFactory.LoadTexture(layer._normalTexturePath); } 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 && terrainLayers[m].diffuseTexture != null) { // Use texture size if tile size is 0 layer._tileSize = new Vector2(terrainLayers[m].diffuseTexture.width, terrainLayers[m].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[numLayers]; for (int m = 0; m < numLayers; ++m) { splatPrototypes[m] = new SplatPrototype(); HEU_LoadBufferVolumeLayer layer = terrainBuffers[t]._layers[m]; Texture2D diffuseTexture = null; if (!string.IsNullOrEmpty(layer._diffuseTexturePath)) { diffuseTexture = HEU_MaterialFactory.LoadTexture(layer._diffuseTexturePath); } if (diffuseTexture == null) { diffuseTexture = defaultTexture; } splatPrototypes[m].texture = diffuseTexture; splatPrototypes[m].tileOffset = layer._tileOffset; if (layer._tileSize.magnitude == 0f && diffuseTexture != null) { // Use texture size if tile size is 0 layer._tileSize = new Vector2(diffuseTexture.width, diffuseTexture.height); } splatPrototypes[m].tileSize = layer._tileSize; splatPrototypes[m].metallic = layer._metallic; splatPrototypes[m].smoothness = layer._smoothness; if (!string.IsNullOrEmpty(layer._normalTexturePath)) { splatPrototypes[m].normalMap = HEU_MaterialFactory.LoadTexture(layer._normalTexturePath); } } terrainData.splatPrototypes = splatPrototypes; #endif terrainData.SetAlphamaps(0, 0, terrainBuffers[t]._splatMaps); //string assetPath = HEU_AssetDatabase.CreateAssetCacheFolder("terrainData"); //AssetDatabase.CreateAsset(terrainData, assetPath); //Debug.Log("Created asset data at " + assetPath); terrainBuffers[t]._generatedOutput = generatedOutput; _generatedOutputs.Add(generatedOutput); SetOutputVisiblity(terrainBuffers[t]); } } }
/// <summary> /// Retrieves object info from Houdini session and updates internal state. /// New geo nodes are created, unused geo nodes are destroyed. /// Geo nodes are then refreshed to be in sync with Houdini session. /// </summary> /// <returns>True if internal state has changed (including geometry).</returns> internal void UpdateObject(HEU_SessionBase session, bool bForceUpdate) { if (ParentAsset == null) { return; } // Update the geo info if (!session.GetObjectInfo(ObjectID, ref _objectInfo)) { return; } SyncWithObjectInfo(session); // Update the object transform _objectTransform = ParentAsset.GetObjectTransform(session, ObjectID); // Container for existing geo nodes that are still in use List<HEU_GeoNode> geoNodesToKeep = new List<HEU_GeoNode>(); // Container for new geo infos that need to be created List<HAPI_GeoInfo> newGeoInfosToCreate = new List<HAPI_GeoInfo>(); if (_objectInfo.haveGeosChanged || bForceUpdate) { // Indicates that the geometry nodes have changed //HEU_Logger.Log("Geos have changed!"); // Form a list of geo infos that are now present after cooking List<HAPI_GeoInfo> postCookGeoInfos = new List<HAPI_GeoInfo>(); bool useOutputNodes = true; if (ParentAsset) useOutputNodes = ParentAsset.UseOutputNodes; HEU_HAPIUtility.GatherAllAssetGeoInfos(session, ParentAsset.AssetInfo, _objectInfo, useOutputNodes, ref postCookGeoInfos); // Now for each geo node that are present after cooking, we check if its // new or whether we already have it prior to cooking. int numPostCookGeoInfos = postCookGeoInfos.Count; for (int i = 0; i < numPostCookGeoInfos; i++) { bool bFound = false; for (int j = 0; j < _geoNodes.Count; j++) { string geoName = HEU_SessionManager.GetString(postCookGeoInfos[i].nameSH, session); if (geoName.Equals(_geoNodes[j].GeoName)) { _geoNodes[j].SetGeoInfo(postCookGeoInfos[i]); geoNodesToKeep.Add(_geoNodes[j]); _geoNodes.RemoveAt(j); bFound = true; break; } } if (!bFound) { newGeoInfosToCreate.Add(postCookGeoInfos[i]); } } // Whatever is left in _geoNodes is no longer needed so clean up int numCurrentGeos = _geoNodes.Count; for (int i = 0; i < numCurrentGeos; ++i) { _geoNodes[i].DestroyAllData(); } } else { Debug.Assert(_objectInfo.geoCount == _geoNodes.Count, "Expected same number of geometry nodes."); } // Go through the old geo nodes that are still in use and update if necessary. foreach (HEU_GeoNode geoNode in geoNodesToKeep) { // Get geo info and check if geo changed bool bGeoChanged = bForceUpdate || geoNode.HasGeoNodeChanged(session); if (bGeoChanged) { geoNode.UpdateGeo(session); } else { if (_objectInfo.haveGeosChanged) { // Clear object instances since the object info has changed. // Without this, the object instances were never getting updated // if only the inputs changed but not outputs (of instancers). geoNode.ClearObjectInstances(); } // Visiblity might have changed, so update that geoNode.CalculateVisiblity(IsVisible()); geoNode.CalculateColliderState(); } } // Create the new geo infos and add to our keep list foreach (HAPI_GeoInfo newGeoInfo in newGeoInfosToCreate) { geoNodesToKeep.Add(CreateGeoNode(session, newGeoInfo)); } // Overwrite the old list with new _geoNodes = geoNodesToKeep; // Updating the trasform is done in GenerateGeometry }