Unity3D Building Damage Tutorial- Part XI - Hovering parts destruction

Today I'll try to cope with parts hovering as a result of fire. But before, I've made a tiny change in way the Aliases are updated. Before, the TellAliasesToUpdate method was called every time the Building class is updated. Than, the update is passed to Aliases if an internal times comes to certain second, now 1. if(time>1){ for(int k=0; k<Aliases.Count;k++){ Aliases[k].Update; } 			time = 0; }else{ time += Time.deltaTime; } Now, i decided that I want to call this update when I want. Lets say when i press the "A" key.

Custom Alias Update calling
//In Building.cs         protected override void Update  { base.Update; ProcessBuildQueue; if( Input.GetKey(KeyCode.A) ){ for(int i=0; i1){ for(int k=0; k0){ DebGeneralNumber++; DebGeneralDestroyTime += DebDestroyTime; } 	} So every time this method is called, Aliases are updated.

Now, to simulate fire, First Click, to start fire, than click A to update.

Hovering Elements
So, lets say that we start fire sequence. After some moments, we will have hovering parts of mesh. This is of course natural consequence of the way our fire is spreading. But as it looks unrealistic, we have to find a way to localise these parts and destroy them.

SetGroundAliases method
But first, we have to acknowledge what in fact are "hovering" parts. I decided to introduce "Ground Aliases". These are special, Indestructible Aliases that symbolise the points where mesh connects to the ground. private void SetGroundAliases(float percent){//You give the percent of height from which Aliases becomes Ground-Aliases. 0.1f makes lowest 10% of Aliases a Ground-Aliases percent = 1 - percent; float CuttingPosition = MaximumHeight - (MaximumHeight-MinimumHeight)*percent; foreach(Verticle v in Aliases){ if(v.positionAbsolute.y<CuttingPosition){ v.state = VerticleState.Ground; NumberOfGroundAliases++; } 		} 	} This function is called in contructor public MeshManager (ref MeshFilter meshFilter){//constructor Initialise; ParentTransform = meshFilter.transform; filter = meshFilter; mesh = meshFilter.mesh; ManageVerticles(mesh.vertices); ManageTriangles(mesh.triangles); CalcualateMinimumHeight; CalculateNormals; SetGroundAliases(0.1f); And change the state of given percent of aliases. A new VerticleState is added public enum VerticleState { Standard, Burning, Destroyed, Ground} There is a slight cahnge in Verticles class too, as Ground Aliases should not be destroyed. public void Update{ if(health == 9999f){//it is still unassigned health = CalculateHealth; } 			if(state == VerticleState.Destroyed){ return; } 			if(state == VerticleState.Ground){//  <-- here return; }

So, now a "Hovering Alias" is an Alias that has no connection via LinkedAliases or LinksToOtherBranches to a "Ground Alias".

ManageHoveringParts method
Some simple general function. private void ManageHoveringParts{ if( NumberOfGroundAliases == 0 ){//without groud aliases everything would fall, so there is no point in calculating return; } 		List HoveringAliases = FindHoveringAliases; foreach(int i in HoveringAliases){ Aliases[i].ImmediatelyDestroy; } 		//We dont have to update mesh.traingles of Traingles List here as it will be called after this function is started //And in Alises we use just LinkedAliases and LinksToOtherBranches which are updated on-spot }

FindHoveringAliases
private List FindHoveringAliases{//returns list containing numbers of Aliases that doesn't have connections to the GroundAliases List OutList = new List ; List AliasesArleadyChecked = new List ; int HowManyAliasesToCheck; if( IsMeshThick ){ HowManyAliasesToCheck = Aliases.Count/2; } else { HowManyAliasesToCheck = Aliases.Count; }//I just dont want to check twins, as they do the same as Aliases they are connected to 		for( int i = 0; i<Aliases.Count; i++ ){//lets check all aliases, foreach loop is not necessary here bool ThereIsGroundAlias = false; if( !AliasesArleadyChecked.Contains(i) ){ List AliasesCurrentlyChecked = new List ;//a list of Aliases that are checked this time for-loop is made. CheckForGroundAliasesAndSentFurther(i, AliasesArleadyChecked, AliasesCurrentlyChecked, ref ThereIsGroundAlias); if(ThereIsGroundAlias == false){ OutList = OutList.Concat(AliasesCurrentlyChecked).ToList; } 			} 		} 		return OutList; } Well, the point of this function is to make a list of ints - numbers of Aliases which are "hovering". It is implemented in similar way to dividing aliases into branches. Most important function called is CheckForGroundAliasesAndSentFurther.

CheckForGroundAliasesAndSendFurther
private void CheckForGroundAliasesAndSentFurther(int NumberBeingChecked, List AliasesArleadyChecked, List AliasesThatAreCheckInThisLoop, ref bool ThereIsGroundAlias){ if( !Aliases[NumberBeingChecked].IsATwin ){//We shoudnt check twins, as they are just like its Main Alias AliasesThatAreCheckInThisLoop.Add(NumberBeingChecked); AliasesArleadyChecked.Add (NumberBeingChecked);//telling that we checked it if( Aliases[NumberBeingChecked].state == VerticleState.Ground ){//checking ThereIsGroundAlias = true; } 			foreach(int i in Aliases[NumberBeingChecked].LinkedAliases){//Sending further (if necessary) if( !AliasesArleadyChecked.Contains(i) ){ CheckForGroundAliasesAndSentFurther(i, AliasesArleadyChecked, AliasesThatAreCheckInThisLoop, ref ThereIsGroundAlias); } 			} 			foreach(int i in Aliases[NumberBeingChecked].LinksToOtherBranches){//Again Sending further (if necessary) if( !AliasesArleadyChecked.Contains(i) ){ CheckForGroundAliasesAndSentFurther(i, AliasesArleadyChecked, AliasesThatAreCheckInThisLoop, ref ThereIsGroundAlias); } 			} 		} 	} Maybe I'll start from the parameters: Two foreach is bottom of function is calling CheckForGroundAliasesAndSendFurther for LinkedAliases and LinksToOtherBranches.
 * is a number of alias we will be checking in this method, and from which we will be sending check to its LinkedAliases.
 * is a list of Aliases numbers Generally checked, this list prevents allowing calling this function twice on this Alias.
 * Function checked in current  loop in   method. Very important list, talk about it in a second.
 * A ref to bool from  Just when a GroundAlias is found in this "branch" there bool in   is turned to true.

Back to FindHoveringAliases
for( int i = 0; i<Aliases.Count; i++ ){//lets check all aliases, foreach loop is not necessary here bool ThereIsGroundAlias = false; if( !AliasesArleadyChecked.Contains(i) ){ List AliasesCurrentlyChecked = new List ;//a list of Aliases that are checked this time for-loop is made. CheckForGroundAliasesAndSentFurther(i, AliasesArleadyChecked, AliasesCurrentlyChecked, ref ThereIsGroundAlias); if(ThereIsGroundAlias == false){ OutList = OutList.Concat(AliasesCurrentlyChecked).ToList; } 			} 		} When after checking a "branch" and still not finding any Ground Alias, the Aliases in checked "branch" (AliasesCurrentlyChecked) are added to OutList.

Than, this list is returned.

Back to ManageHoveringParts
foreach(int i in HoveringAliases){ Aliases[i].ImmediatelyDestroy; } 		//We dont have to update mesh.traingles of Traingles List here as it will be called after this function is started //And in Alises we use just LinkedAliases and LinksToOtherBranches which are updated on-spot } For each Hovering Alias, a new method, ImmediatelyDestroy is called

ImmediatelyDestroy method (Vecticle.cs)
public void ImmediatelyDestroy{//don,t bother sending info to other aliases, just change Traingles list in MeshManager and verticleState DestroyTriangles; state = VerticleState.Destroyed; for(int i = 0; i<LinkedAliases.Count; i++){ OwnerManager.Aliases[ LinkedAliases[i]  ].RemoveLink(number); foreach(int l in Triangles){ OwnerManager.Aliases[ LinkedAliases[i]  ].RemoveLink(number); } 				RemoveLink( LinkedAliases[i]  ); } 			RemoveAllLinksToOtherBranches; if((OwnerManager.IsMeshThick==true)&&IsATwin==false){	//send info to twin OwnerManager.Aliases[number+(OwnerManager.Aliases.Count/2)].ImmediatelyDestroy; } 		} For now, it just differs from standard Destroy by not having ProduceTrianglesBetweenWalls, as Aliases its linked to will be destroyed aswell. Moreover, I think that removing links is not necessary, and maybe I'll delete it in some future oprimalisation

Okay, I changed the TellAliasesToUpdate method to contain call to ManageHoveringParts public void TellAliasesToUpdate{ for(int k=0; k<Aliases.Count/2;k++){ Aliases[k].Update; } 		if(MeshWasChanged){ ManageHoveringParts; MakeWallsFromList; UpdateTrianglesList; TranslateFromMeshToManager; MeshWasChanged = false; }	         } Click on building, presing A and... we have a problem

Planks
For me, the hovering chunks were disappearing as should, but there was slight problem as ilustrated on the picture. Sometimes there "happen" a thin two-sided wall. I called them "planks". After some debuging I realised that this are in fact walls made when two Aliases on oposite sides of "edge" were destroyed. As the aliases on ends of "wall" are intact, this structure still exist, but in my opinion should somehow be erased.

DestroyPlanks method
How to identify a "plank". Well, it is only "wall" that in mesh have two sides. So we just have to find custom-added traingles that currently have two sides. Of course I won't work on Twins, as they do what their Aliases do.

Moreover, as you may remember, for evry wall made, two triangles were created: One between 2 standard Aliases and one twin, and one between two twins and one standard Alias. It will be enough to just locate the first one, as the second is "placed" in mesh.traingles & Traingles List directly after the first one. private bool DestroyPlanks{ //Anyway, Planks are the only two-sided triangles except for the "Filling traingles", but we are not checking them //as we are only checking with traingles with numbers higher Dictionary DictionaryOfWalls = new Dictionary;//Similar to finding edges, isn't it. //Well, now we are going check pair of Aliases used in walls. If the same pair of Aliases was used twice, it is a "plank" //Kay will be produced from Aliases numbers, and List will store the numbers of triangles for(int i = (Triangles.Count - ( NumberOfFillingTraingles/3 ) ); i<Triangles.Count; i++){//these calculations I mean checking the "walls" Vector2 Aliases = GetAliasesNumbersFromTraingle( Triangles[i] ); if(Aliases == Vector2.zero){ //Do nthing }else{ float key = ProduceSeed( (int) Aliases.x, (int) Aliases.y ); if( DictionaryOfWalls.ContainsKey(key) ){ DictionaryOfWalls[key].Add(i); }else{ DictionaryOfWalls.Add (key, new List ); DictionaryOfWalls[key].Add (i); } 			} 		} 		int[] NewTrianglesList = new int[mesh.triangles.Length]; //list on which we will perform modifications mesh.triangles.CopyTo(NewTrianglesList,0); bool modified = false; foreach(KeyValuePair< float, List > pair in DictionaryOfWalls){ if(pair.Value.Count>1){ modified = true; foreach(int i in pair.Value){ //We cannot just modify Triangles List as UpdateTrianglesList modifies only the Original Traingles (not walls or filling ones) NewTrianglesList[i*3 ] = 0; NewTrianglesList[i*3+1] = 0; NewTrianglesList[i*3+2] = 0; //Well, now intresting thing. We deleted the triangle with two Aliases. But what with the one with two twins and one alias??? //Intresting thing: The traingle with two twins in added directly After the one with 2 Aliases and one twin. NewTrianglesList[i*3+3] = 0; NewTrianglesList[i*3+4] = 0; NewTrianglesList[i*3+5] = 0; } 			} 		} 		if(modified == true){ mesh.triangles = NewTrianglesList; } 		return modified; } In dictionary I'll use kay generated in the same way as in "Open Edges", Tutorial part X. The value is a list as sometimes from a pair of Aliases two traingles may be generated (effectively a double-side traingle, the ones we are looking for!). Well, this is an intresting part. What is i at the begining of loop. We have to realise in what order the traingles are placed in mesh.traingles and Triangles list. Note that destroyed traingles are not removed from the array and list, effecting the order. They are just converted to zeros.
 * 1) First, there are original traingles from the mesh
 * 2) Than, the traingles filling "Open Edges"
 * 3) Finally, traingles from "walls" added during the Alias destruction.

Note two more things:


 * 1) We destroy "planks" inside the function, working with copy of mesh.traingles and than updating mesh.traingles. As a results, we will have to update Traingles List etc.
 * 2) We return a bool telling us if some planks were searched&destroyed

GetAliasesNumbersFromTraingle
private Vector2 GetAliasesNumbersFromTraingle(Vector3 vec){//returns Two numbers which are NOT twins numbers, or Vector2.zero if there was only one Alias and two twins in Traingle if(vec == Vector3.zero){//its arleady destroyed, no point in continuing return Vector2.zero; } 		int[] TempArray = new int[3]; //3 not 2 is becouse sometimes there might be errors when triangle with 3 normal aliases was given int HowManyAliasesWeArleadyHave = 0; for(int i=0; i<3; i++){ if( VerticleToAliasArray[ (int) vec[i] ] < Aliases.Count/2 ){ TempArray[HowManyAliasesWeArleadyHave] = VerticleToAliasArray[ (int) vec[i] ]; HowManyAliasesWeArleadyHave++; } 		} 		if(HowManyAliasesWeArleadyHave == 3){ Debug.Log("A good Traingle was passed: It had 3 Aliases and 0 Twins"); return Vector2.zero; }else if(HowManyAliasesWeArleadyHave == 2){ return new Vector2(	TempArray[0], TempArray[1]); }else{ return Vector2.zero; } 	} Well, this method just check if given traingle "consists" of two normal aliases and one triangle, if so returns these Aliases' numbers, if not, returns Vector2.zero

TellAliasesToUpdate method
Final version of this method. (for now) public void TellAliasesToUpdate{ for(int k=0; k<Aliases.Count/2;k++){ Aliases[k].Update; } 		if(MeshWasChanged){ //ManageHoveringParts; MakeWallsFromList; UpdateTrianglesList; TranslateFromMeshToManager; bool result = DestroyPlanks; if( result ){ ManageHoveringParts; UpdateTrianglesList; TranslateFromMeshToManager; } 			MeshWasChanged = false; } 	} New part is propably not optimalised at all. Anyway, I think that to overcome these split-second freezes during updates of fire, I'll introduce multithreading.

For files, go as allways to Github Repo, Version 9