/// <summary> /// Triangulate vertsEnumerable, creating a number of trigs. /// Triangle winding order will be determined by normal of the first vert. /// @TODO: this can really use a better triangulation and winding algorithm. /// </summary> /// <param name="vertsEnumerable"></param> private void CommitSurface(IEnumerable <Vert> vertsEnumerable) { // create list so vertices can be sorted based on winding order var verts = new List <Vert>(vertsEnumerable); // this will be used as a normal for winding order calculation var windingNormal = verts.First().normal; // calculate centroid of verts var center = LightsaberDemoLib.CalculateVectorsCentroid(verts.Select(a => a.point)); // sort vertices based on their angle from centroid, leaving them in CW order verts.Sort((first, second) => { var a = first.point - center; var b = second.point - center; return(Vector3.SignedAngle(center, a, windingNormal).CompareTo(Vector3.SignedAngle(center, b, windingNormal))); }); // triangulate and push indices into _meshTrigs list for (int i = 0; i < verts.Count - 2; i += 1) { _meshTrigs.Add(_meshVerts.Count); _meshTrigs.Add(_meshVerts.Count + i + 1); _meshTrigs.Add(_meshVerts.Count + i + 2); } // add vertices and normals _meshVerts.AddRange(verts.Select(a => a.point)); _meshNormals.AddRange(verts.Select(a => a.normal)); }
private void PerformCutBasedOnSwipeData() { // calculate slope based on linear regression based on all _swipePoints to act as a normal of the plane float slope = LightsaberDemoLib.CalculateLinearRegressionSlope(_swipePoints); // calculate centroid of _swipePoints to act as a center of the plane var centroid2D = _swipePoints.Aggregate(Vector2.zero, (acc, vec) => acc + vec) / _swipePoints.Count; var centroid = new Vector3(centroid2D.x, centroid2D.y, 0f); var a = new Vector3(-1f, -slope, -1f); var b = new Vector3(1f, slope, 1f); var c = new Vector3(0f, 0f, 2f); // pass plane to the target controller which will perform the mesh split targetObjectsController.Cut(new Plane(a, b, c).normal, centroid); }
/// <summary> /// Split mesh in two by plane. Will populate mesh builders with data, which then can be used to cook /// debris mesh. /// </summary> /// <param name="plane">Plane in local-space</param> /// <param name="mesh">Mesh to cut</param> /// <param name="aboveMeshBuilder">Everything above the plane will go in this builder</param> /// <param name="belowMeshBuilder">Everything below the plane will go in this builder</param> private void SplitMesh(Plane plane, Mesh mesh, MeshBuilder aboveMeshBuilder, MeshBuilder belowMeshBuilder) { var trigs = mesh.GetTriangles(0); var verts = mesh.vertices; var normals = mesh.normals; /* * Iterate over each triangle of the mesh. There are three cases: * - triangle is completely above the plane - it's not cut, added as-in into respective mesh builder * - triangle is completely below the plane - same as previous * - triangle intersects plane, making it one vertex on one side and two on the others - triangle is cut in two by * generating new vertices, one half of it goes into above builder and other into below builder */ for (var trigIdx = 0; trigIdx < trigs.Length; trigIdx += 3) { // mask that indicates which edges has already been checked (3 bits total) var checkedEdgeMask = 0; /* * Iterate over each edge of the triangle to determine which edges should actually be cut. * Each edge is processed only once, no matter the order of the vertices. * If it's determined that edge intersects the cutting plane, new vertex is added at the intersection point, * and respective mesh builders receive new and existing vertices based on their position relative to the plane. * After all edges has been processed mesh builder's `CommitExistingSurface` method will be called, which will * triangulate all vertices inserted during this loop to create actual trigs. */ for (var vertAIdx = 0; vertAIdx < 3; vertAIdx++) { // get the vert data var vertA = verts[trigs[trigIdx + vertAIdx]]; var vertANormal = normals[trigs[trigIdx + vertAIdx]]; // figure out whether vert is below or above the plane and insert it into // respective mesh builder var vertAAbove = plane.GetSide(vertA); (vertAAbove ? aboveMeshBuilder : belowMeshBuilder).InsertExistingSurfaceVert(vertA, vertANormal); for (var vertBIdx = 0; vertBIdx < 3; vertBIdx++) { // iterate over each edge connected to the vert if (vertAIdx == vertBIdx || (checkedEdgeMask & 1 << (vertAIdx + vertBIdx)) > 0) { // skip edges that were already processed as indicated by `checkedEdgeMask` continue; } // get other vert data var vertB = verts[trigs[trigIdx + vertBIdx]]; var vertBAbove = plane.GetSide(vertB); // vert A and vert B are not on the same side of the plane, meaning that // they intersect with it, the point of intersection being the vert we need to create if (vertAAbove != vertBAbove) { // calculate intersection vert var intersection = LightsaberDemoLib.CalculateEdgePlaneIntersection( plane, vertAAbove ? vertA : vertB, !vertBAbove ? vertB : vertA ); // insersection vert will go into both mesh builders since it should be // present on both above and below meshes // insert new vert into builders to create new edges on the existing surfaces aboveMeshBuilder.InsertExistingSurfaceVert(intersection, vertANormal); belowMeshBuilder.InsertExistingSurfaceVert(intersection, vertANormal); // insert new vert to create intersection area surface in the end (actual area of the cut) aboveMeshBuilder.InsertIntersectionSurfaceVert(intersection, -plane.normal); belowMeshBuilder.InsertIntersectionSurfaceVert(intersection, plane.normal); } // update checked edge bitmask. no matter the order the position of the bit will always be the same, // therefore A-B and B-A will result in single iteration, making a total of 3 for each trig checkedEdgeMask |= 1 << (vertAIdx + vertBIdx); } } // commit existing surface on both builders aboveMeshBuilder.CommitExistingSurface(); belowMeshBuilder.CommitExistingSurface(); } // commit cut area surface on both builders aboveMeshBuilder.CommitIntersectionSurface(); belowMeshBuilder.CommitIntersectionSurface(); }