/// <summary> /// create pool (array) of fragment game objects with all necessary components /// </summary> /// <param name="poolSize">number of fragments</param> public void Allocate(int poolSize) { Exploder2DUtils.Assert(poolSize > 0, ""); if (pool == null || pool.Length < poolSize) { DestroyFragments(); pool = new Fragment2D[poolSize]; for (int i = 0; i < poolSize; i++) { var fragment = new GameObject("fragment_" + i); fragment.AddComponent <SpriteRenderer>(); fragment.AddComponent <PolygonCollider2D>(); fragment.AddComponent <Rigidbody2D>(); fragment.AddComponent <Exploder2DOption>(); var fragmentComponent = fragment.AddComponent <Fragment2D>(); fragment.transform.parent = gameObject.transform; pool[i] = fragmentComponent; Exploder2DUtils.SetActiveRecursively(fragment.gameObject, false); fragmentComponent.RefreshComponentsCache(); fragmentComponent.Sleep(); } } }
public void OnExplosionFinished(int id) { var explosion = queue.Dequeue(); Exploder2DUtils.Assert(explosion.id == id, "Explosion id mismatch!"); ProcessQueue(); }
private int GetLevel(float distance, float radius) { Exploder2DUtils.Assert(distance >= 0.0f, ""); Exploder2DUtils.Assert(radius >= 0.0f, ""); var normDistance = distance / radius * 6; var level = (int)normDistance / 2 + 1; return(Mathf.Clamp(level, 0, 10)); }
/// <summary> /// explode cracked objects /// Use this method in combination with Crack() /// Purpose of this method is to get higher performance of explosion, Crack() will /// calculate the explosion and prepare all fragments. Calling ExplodeCracked() will /// then start the explosion (flying fragments...) immediately /// </summary> public void ExplodeCracked(OnExplosion callback) { Exploder2DUtils.Assert(crack, "You must call Crack() first!"); if (cracked) { PostCrackExplode(callback); crack = false; } }
/// <summary> /// crack will calculate fragments and prepare object for explosion /// Use this method in combination with ExplodeCracked() /// Purpose of this method is to get higher performance of explosion, Crack() will /// calculate the explosion and prepare all fragments. Calling ExplodeCracked() will /// then start the explosion (flying fragments...) immediately /// </summary> public void Crack(OnCracked callback) { Exploder2DUtils.Assert(!crack, "Another crack in progress!"); if (!crack) { CrackedCallback = callback; crack = true; cracked = false; Explode(null); } }
/// <summary> /// callback from queue, do not call this unles you know what you are doing! /// </summary> public void StartExplosionFromQueue(Vector2 pos, int id, OnExplosion callback) { Exploder2DUtils.Assert(state == State.None, "Wrong state: " + state); mainCentroid = pos; explosionID = id; state = State.Preprocess; ExplosionCallback = callback; #if DBG processingTime = 0.0f; preProcessingTime = 0.0f; postProcessingTime = 0.0f; postProcessingTimeEnd = 0.0f; isolatingIslandsTime = 0.0f; cuts = 0; #endif }
/// <summary> /// returns list of fragments with requested size /// this method pick fragments hidden from camera or sleeping rather then visible /// </summary> /// <param name="size">number of requested fragments</param> /// <returns>list of fragments</returns> public List <Fragment2D> GetAvailableFragments(int size) { if (size > pool.Length) { Debug.LogError("Requesting pool size higher than allocated! Please call Allocate first! " + size); return(null); } if (size == pool.Length) { return(new List <Fragment2D>(pool)); } var fragments = new List <Fragment2D>(); int counter = 0; // get deactivated fragments first foreach (var fragment in pool) { // get invisible fragments if (!fragment.activeObj) { fragments.Add(fragment); counter++; } if (counter == size) { return(fragments); } } foreach (var fragment in pool) { // get invisible fragments if (!fragment.visible) { fragments.Add(fragment); counter++; } if (counter == size) { return(fragments); } } // there are still live fragments ... get sleeping ones if (counter < size) { foreach (var fragment in pool) { if (fragment.IsSleeping() && fragment.visible) { Exploder2DUtils.Assert(!fragments.Contains(fragment), "!!!"); fragments.Add(fragment); counter++; } if (counter == size) { return(fragments); } } } // there are still live fragments... if (counter < size) { foreach (var fragment in pool) { if (!fragment.IsSleeping() && fragment.visible) { Exploder2DUtils.Assert(!fragments.Contains(fragment), "!!!"); fragments.Add(fragment); counter++; } if (counter == size) { return(fragments); } } } Exploder2DUtils.Assert(false, "ERROR!!!"); return(null); }
void Update() { long cuttingTime = 0; switch (state) { case State.None: break; case State.Preprocess: { timer.Reset(); timer.Start(); #if DBG var watch = new Stopwatch(); watch.Start(); #endif // get mesh data from game object in radius var readyToCut = Preprocess(); #if DBG watch.Stop(); preProcessingTime = watch.ElapsedMilliseconds; processingFrames = 0; #endif // nothing to explode if (!readyToCut) { #if DBG Exploder2DUtils.Log("Nothing to explode " + mainCentroid); #endif OnExplosionFinished(false); } else { // continue to cutter, don't wait to another frame state = State.ProcessCutter; goto case State.ProcessCutter; } } break; case State.ProcessCutter: { #if DBG processingFrames++; var watch = new Stopwatch(); watch.Start(); #endif // process main cutter var cuttingFinished = false; switch (CuttingStrategy) { case CutStrategy.Randomized: cuttingFinished = ProcessCutterRandomized(out cuttingTime); break; // case CutStrategy.Grid: // cuttingFinished = ProcessCutterGrid(out cuttingTime); // break; default: Exploder2DUtils.Assert(false, "Invalid cutting strategy"); break; } #if DBG watch.Stop(); processingTime += watch.ElapsedMilliseconds; #endif if (cuttingFinished) { poolIdx = 0; postList = new List <CutMesh>(meshSet); // continue to next state if the cutter is finished if (splitMeshIslands) { #if DBG isolatingIslandsFrames = 0; isolatingIslandsTime = 0.0f; #endif islands = new List <CutMesh>(meshSet.Count); state = State.IsolateMeshIslands; goto case State.IsolateMeshIslands; } state = State.PostprocessInit; goto case State.PostprocessInit; } } break; case State.IsolateMeshIslands: { #if DBG var watchPost = new Stopwatch(); watchPost.Start(); #endif var isolatingFinished = IsolateMeshIslands(ref cuttingTime); #if DBG watchPost.Stop(); isolatingIslandsFrames++; isolatingIslandsTime += watchPost.ElapsedMilliseconds; #endif if (isolatingFinished) { // goto next state state = State.PostprocessInit; goto case State.PostprocessInit; } } break; case State.PostprocessInit: { InitPostprocess(); state = State.Postprocess; goto case State.Postprocess; } case State.Postprocess: { #if DBG var watchPost = new Stopwatch(); watchPost.Start(); #endif if (Postprocess(cuttingTime)) { timer.Stop(); } #if DBG watchPost.Stop(); postProcessingTime += watchPost.ElapsedMilliseconds; #endif } break; } }
private bool ProcessCutterRandomized(out long cuttingTime) { Exploder2DUtils.Assert(state == State.ProcessCutter || state == State.DryRun, "Wrong state!"); var stopWatch = new Stopwatch(); stopWatch.Start(); bool cutting = true; bool timeBudgetStop = false; var cycleCounter = 0; cuttingTime = 0; while (cutting) { cycleCounter++; if (cycleCounter > TargetFragments) { Exploder2DUtils.Log("Explode Infinite loop!"); break; } newFragments.Clear(); meshToRemove.Clear(); cutting = false; var fragmentsCount = meshSet.Count; foreach (var mesh in meshSet) { if (levelCount[mesh.level] > 0) { var randomLineNormalized = Random.insideUnitCircle; if (!mesh.transform) { continue; } var plane = Core.Math.Line2D.CreateNormalPoint(randomLineNormalized, mesh.centroidLocal); #if DBG cuts++; #endif if (mesh.option) { splitMeshIslands |= mesh.option.SplitMeshIslands; } List <CutterMesh> meshes = null; cutter.Cut(mesh.spriteMesh, mesh.transform, plane, ref meshes); cutting = true; if (meshes != null) { foreach (var cutterMesh in meshes) { newFragments.Add(new CutMesh { spriteMesh = cutterMesh.mesh, centroidLocal = cutterMesh.centroidLocal, sprite = mesh.sprite, vertices = mesh.vertices, transform = mesh.transform, distance = mesh.distance, level = mesh.level, fragments = mesh.fragments, original = mesh.original, parent = mesh.transform.parent, position = mesh.transform.position, rotation = mesh.transform.rotation, localScale = mesh.transform.localScale, sortingLayer = mesh.sortingLayer, orderInLayer = mesh.orderInLayer, color = mesh.color, option = mesh.option, }); } meshToRemove.Add(mesh); levelCount[mesh.level] -= 1; // stop this madness! if (fragmentsCount + newFragments.Count - meshToRemove.Count >= TargetFragments) { cuttingTime = stopWatch.ElapsedMilliseconds; meshSet.ExceptWith(meshToRemove); meshSet.UnionWith(newFragments); return(true); } // computation took more than FrameBudget ... if (stopWatch.ElapsedMilliseconds > FrameBudget) { timeBudgetStop = true; break; } } } } meshSet.ExceptWith(meshToRemove); meshSet.UnionWith(newFragments); if (timeBudgetStop) { break; } } cuttingTime = stopWatch.ElapsedMilliseconds; // explosion is finished if (!timeBudgetStop) { return(true); } return(false); }