private void Update() { if (Input.GetKeyDown(KeyCode.Z)) { SceneManager.LoadScene(0); } RaycastHit hit; if (!Input.GetMouseButtonDown(0) || !Physics.Raycast(Camera.main.ScreenPointToRay(Input.mousePosition), out hit)) { return; } var building = hit.transform.name == "Cottage" || hit.transform.name == "house1" || hit.transform.name == "house2"; var explosionRange = building ? 2 : 0.5f; var sw = Stopwatch.StartNew(); var commands = new ScamScatter.ScatterCommands(); commands.Add(hit.transform.gameObject); new ScamScatter.Scatter { TargetArea = 0.8f, MaxTimeMs = 40 } .Run(this, _ => { _info = _.NewGameObjects > 0 ? $"Scattered {hit.transform.name} ({_.SourceTriangles} triangles) into {_.NewGameObjects} new game objects in {sw.ElapsedMilliseconds} ms." : "Nothing to scatter."; ScamScatter.Explode.Run(hit.point, 1.5f, 2); }, commands); }
/// <summary> /// Creates a number of new game objects with meshes that will resemble the original mesh. /// The new parts may then be moved around separately. /// </summary> /// <param name="gameObject">The game object to scatter. Must have a read/write enabled mesh and must not be marked as static.</param> /// <param name="parts">Aim at creating this number of new parts. Depending on maxArea, the result may be much larger.</param> /// <param name="maxArea">The approx area of the new parts. This is the area of the front side.</param> /// <param name="thicknessMin">Min thickness of the newmeshes (random range)</param> /// <param name="thicknessMax">Max thickness of the new meshes(random range)</param> /// <param name="parentTransform">Attach the new objects to this transform.</param> /// <param name="destroyOriginal">If the original object shall be destroyed automatically</param> /// <returns></returns> public void Run( MonoBehaviour mb, Action <Stats> whenDone, ScatterCommands commands) { mb.StartCoroutine(run(commands, whenDone)); }
public void PrepareScatter(ScatterCommands commands) { }
public static void Run( ScatterCommands commands) { new Scatter().run(commands, null).MoveNext(); }
private IEnumerator run( ScatterCommands commands, Action <Stats> whenDone) { var sw = Stopwatch.StartNew(); var stats = new Stats(); var objectCount = 0; foreach (var cmd in commands) { var gameObject = cmd.GameObject; // already scattered? if (gameObject == null || gameObject.name.StartsWith(FragmentNamePrefix) || gameObject.name.StartsWith(DebrisNamePrefix)) { continue; } var targetPartCount = cmd.TargetPartCount.GetValueOrDefault(TargetPartCount); var targetArea = cmd.TargetArea.GetValueOrDefault(TargetArea); var newThicknessMin = cmd.NewThicknessMin.GetValueOrDefault(NewThicknessMin); var newThicknessMax = cmd.NewThicknessMax.GetValueOrDefault(NewThicknessMax); Debug.Log(targetArea); var meshData = new meshData(cmd.Mesh, cmd.MeshScale); stats.SourceTriangles += meshData.TotalTriangleCount; for (var submeshIndex = 0; submeshIndex < cmd.Mesh.subMeshCount; submeshIndex++) { meshData.SelectSubmesh(submeshIndex); var maxTris = Mathf.Max(2, meshData.TotalTriangleCount / targetPartCount); while (meshData.Triangles.Any()) { var newTriangles = new List <triangle>(); var newVerticies = new List <vertex>(); var quota = Mathf.Min(meshData.Triangles.Count, Random.Range(maxTris - 1, maxTris + 2)); extractTrianglesAndVerticies(newVerticies, newTriangles, quota, meshData, targetArea); // at this point, we have a continous surface in newTriangles+newVerticies which // are a subset of the whole mesh // now let's create some SCAM! // first, calculate the average normal of the surface and the midpoint var frontNormal = Vector3.zero; var frontMidpoint = Vector3.zero; foreach (var t in newTriangles) { var p1 = newVerticies[t.I0].Pos; var p2 = newVerticies[t.I1].Pos; var p3 = newVerticies[t.I2].Pos; frontMidpoint += p1 + p2 + p3; frontNormal += Vector3.Cross(p2 - p1, p3 - p1); } var backVector = -frontNormal.normalized * Random.Range(newThicknessMin, newThicknessMax); var backMidpoint = frontMidpoint / (newTriangles.Count * 3) + backVector; // now we create a backside by creating a flipped backside, pushed backward by // the user's specified thickness. we also contract it somewhat, to avoid z-fighting // with orthogonal surfaces var vertLength = newVerticies.Count; for (var k = 0; k < vertLength; k++) { newVerticies.Add(new vertex { Pos = Vector3.Lerp(newVerticies[k].Pos + backVector, backMidpoint, 0.2f), Uv = Vector2.one - newVerticies[k].Uv, Normal = -newVerticies[k].Normal, Tangent = newVerticies[k].Tangent }); } // create all the new scam triangles var tlen = newTriangles.Count; for (var ti = 0; ti < tlen; ti++) { var t = newTriangles[ti]; // this is triangle for the backside (only verticies has been created this far) newTriangles.Add(new triangle(t.I0 + vertLength, t.I2 + vertLength, t.I1 + vertLength)); // for each side of the front triangle, investigate if it is an outer // side, and if so, create two triangles connecting the front with the back buildSideRect(newVerticies, vertLength, newTriangles, ti, t.I0, t.I1); buildSideRect(newVerticies, vertLength, newTriangles, ti, t.I1, t.I2); buildSideRect(newVerticies, vertLength, newTriangles, ti, t.I2, t.I0); // the new triangles created have completely wrong normals. fixing that would // slow things down. just sayin' } // a micro optimizition here would be to stop using LINQ var theNewMesh = new Mesh { vertices = newVerticies.Select(_ => _.Pos).ToArray(), normals = newVerticies.Select(_ => _.Normal).ToArray(), uv = newVerticies.Select(_ => _.Uv).ToArray(), tangents = newVerticies.Select(_ => _.Tangent).ToArray(), triangles = newTriangles.SelectMany(_ => new[] { _.I0, _.I1, _.I2 }).ToArray() }; var newFragment = new GameObject($"{FragmentNamePrefix}{++objectCount}"); newFragment.transform.parent = cmd.NewTransformParent; newFragment.transform.position = cmd.Renderer.transform.position; newFragment.transform.rotation = cmd.Renderer.transform.rotation; #if UNITY_EDITOR newFragment.AddComponent <MeshRenderer>().sharedMaterial = cmd.Renderer.sharedMaterials[submeshIndex % cmd.Renderer.sharedMaterials.Length]; #else newFragment.AddComponent <MeshRenderer>().material = cmd.Renderer.materials[submeshIndex % cmd.Renderer.materials.Length]; #endif newFragment.AddComponent <MeshFilter>().mesh = theNewMesh; newFragment.AddComponent <BoxCollider>(); stats.NewGameObjects++; stats.NewTriangles += newTriangles.Count; if (MaxTimeMs > 0 && sw.ElapsedMilliseconds > MaxTimeMs) { Debug.Log("Yield"); yield return(null); sw.Restart(); } } } if (cmd.DestroyMesh) { Object.Destroy(cmd.Mesh); } if (cmd.Destroy) { Object.Destroy(gameObject); } } whenDone?.Invoke(stats); }