예제 #1
0
        /// <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));
        }
예제 #2
0
        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();
        }