Split 2D mesh with polyline

Hi guys,

I am trying to find a way to split simple 2D mesh into two parts.
I have a mesh constructed from 4 or more points and I have a simple 2D poly line.
Poly line is nothing more than a series of nodes(Vector3) and edges.

I can detect intersection points between mesh and the line, but I can’t find a way to separate all points into two set?
Two sets of points have to be in order as I have ear clipping algorithm to make a mesh from them.

Here is a small sketch to illustrate what I want to get.

A quite brute force way would be to construct two big shapes from the cut expanding outwards (you need to make sure they cover all the points on each side). Then you can the detect which points are inside and assign those to one shape or another.

Here is a sketch for one side:


As I said, it is quite brute force but easy to implement. Checking if a point is inside a 2d polygon is pretty fast (I drew the red tris which you would have to check). Don’t forget to duplicate the cut points for the other shape :wink:

Here is a utils class I use for such purposes:

using System.Collections.Generic;
using System.Linq;
using UnityEngine;

namespace Kamgam.Helpers
{
    public static class UtilsPolygons
    {
        /// <summary>
        /// Is the point (p) within the polygon (polyPoints in X/Y plane) and between the given z limits.
        /// The polygon can be (concave or convex).
        /// From https://wiki.unity3d.com/index.php?title=PolyContainsPoint
        /// </summary>
        /// <param name="polyPoints"></param>
        /// <param name="p"></param>
        /// <param name="minZ">Point p is considered outside if below minZ.</param>
        /// <param name="maxZ">Point p is considered outside if above maxZ.</param>
        /// <returns></returns>
        public static bool ContainsPoint(IList<Vector3> polyPoints, Vector3 p, float minZ = float.MinValue, float maxZ = float.MaxValue)
        {
            if (p.z < minZ || p.z > maxZ)
                return false;

            var j = polyPoints.Count - 1;
            var inside = false;
            Vector2 pi, pj;
            for (int i = 0; i < polyPoints.Count; j = i++)
            {
                pi = polyPoints[i];
                pj = polyPoints[j];
                if (((pi.y <= p.y && p.y < pj.y) || (pj.y <= p.y && p.y < pi.y)) &&
                    (p.x < (pj.x - pi.x) * (p.y - pi.y) / (pj.y - pi.y) + pi.x))
                    inside = !inside;
            }
            return inside;
        }

        /// <summary>
        /// Checks if a point is within the polygon (concave or convex).
        /// From https://wiki.unity3d.com/index.php?title=PolyContainsPoint
        /// </summary>
        /// <param name="polyPoints"></param>
        /// <param name="p"></param>
        /// <returns></returns>
        public static bool ContainsPoint(IList<Vector2> polyPoints, Vector2 p)
        {
            var j = polyPoints.Count - 1;
            var inside = false;
            Vector2 pi, pj;
            for (int i = 0; i < polyPoints.Count; j = i++)
            {
                pi = polyPoints[i];
                pj = polyPoints[j];
                if (((pi.y <= p.y && p.y < pj.y) || (pj.y <= p.y && p.y < pi.y)) &&
                    (p.x < (pj.x - pi.x) * (p.y - pi.y) / (pj.y - pi.y) + pi.x))
                    inside = !inside;
            }
            return inside;
        }

        public static float TriangleArea(Vector2 p0, Vector2 p1, Vector2 p2)
        {
            return 0.5f * (p1 - p0).magnitude * (p2 - p0).magnitude * Mathf.Sin(Mathf.Deg2Rad * Vector2.Angle(p1 - p0, p2 - p0));
        }
    }
}

Happy coding!

1 Like

It’s also possible to use a free 3rd party library such as Clipper to do the heavy lifting.

1 Like

Oh thank you. I getting in love working with meshes, but looks like clipper will save a lot of time.
Have you tried to work with it in Unity?

We use the native C++ of Clipper internal inside Unity for several components. It’s fast and stable.

2 Likes

I have been using clipper c# in Unity for a while too. It works just fine.
There is a c# port within the download, see here:
http://www.angusj.com/delphi/clipper.php
https://sourceforge.net/projects/polyclipping/

2 Likes

Cool thank you

Hi @_geo1
Do you have a simple unity example of usage the library?
The things I got from documentation, is does not make triangulation? It provides list of point to work with after the slice operation?

Also not quite sure what the difference between Path and Paths

Thank you

Tessellation of the path(s) can be achieved using something like: GitHub - speps/LibTessDotNet: C# port of the famous GLU Tessellator - prebuilt binaries now available in "releases" tab

For Clipper; you can see the docs here for Path/Paths. Path being a single path of points, Paths being multiple paths; the literal plural.

Thank you. Tessellation looks good. Will have to look into it. I made my own ear clipping script. But it takes only ordered list of points

An alternative to LibTess is Poly2Tri:
http://github.com/MaulingMonkey/poly2tri-cs

I am using both but prefer poly2tri because I can add arbitrary points for further subdivision inside (Steiner Points). I don’t recall if libTess can do that too.
Hint: If you hand a list of points to poly2tri then make sure to remove the last point if it is identical with the first (poly2tri does not like that).

For LibTess I have written some wrappers for Unity (mainly for Vector type conversions):

/*
** SGI FREE SOFTWARE LICENSE B (Version 2.0, Sept. 18, 2008)
** Copyright (C) 2011 Silicon Graphics, Inc.
** All Rights Reserved.
**
** Permission is hereby granted, free of charge, to any person obtaining a copy
** of this software and associated documentation files (the "Software"), to deal
** in the Software without restriction, including without limitation the rights
** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
** of the Software, and to permit persons to whom the Software is furnished to do so,
** subject to the following conditions:
**
** The above copyright notice including the dates of first publication and either this
** permission notice or a reference to http://oss.sgi.com/projects/FreeB/ shall be
** included in all copies or substantial portions of the Software.
**
** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
** INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
** PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL SILICON GRAPHICS, INC.
** BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
** OR OTHER DEALINGS IN THE SOFTWARE.
**
** Except as contained in this notice, the name of Silicon Graphics, Inc. shall not
** be used in advertising or otherwise to promote the sale, use or other dealings in
** this Software without prior written authorization from Silicon Graphics, Inc.
*/
/*
** Original Author: Eric Veach, July 1994.
** libtess2: Mikko Mononen, http://code.google.com/p/libtess2/.
** LibTessDotNet: Remi Gillig, https://github.com/speps/LibTessDotNet
*/

using System;
using System.Collections.Generic;
using System.Diagnostics;


namespace Kamgam.SBR2.LibTessDotNet
{
    /// <summary>
    /// Changed _vertices, _vertexCount, _elements and _elementCount
    /// from private to protected in the original Tess class.
    ///
    /// Added IsPointInTriangle(...) and IsPointInShape(...).
    /// </summary>
    public class ExtendedTess : Tess
    {
        /// <summary>
        /// Thanks to "Glenn Slayden" https://stackoverflow.com/questions/2049582/how-to-determine-if-a-point-is-in-a-2d-triangle
        /// </summary>
        /// <param name="p"></param>
        /// <param name="p0"></param>
        /// <param name="p1"></param>
        /// <param name="p2"></param>
        /// <returns></returns>
        public bool IsPointInTriangle(float px, float py, float p0x, float p0y, float p1x, float p1y, float p2x, float p2y)
        {
            var s = p0y * p2x - p0x * p2y + (p2y - p0y) * px + (p0x - p2x) * py;
            var t = p0x * p1y - p0y * p1x + (p0y - p1y) * px + (p1x - p0x) * py;

            if ((s < 0) != (t < 0))
            {
                return false;
            }

            var A = -p1y * p2x + p0y * (p2x - p1x) + p0x * (p1y - p2y) + p1x * p2y;

            return A < 0 ?
                    (s <= 0 && s + t >= A) :
                    (s >= 0 && s + t <= A);
        }

        public bool IsPointInShape(UnityEngine.Vector2 point)
        {
            float p0x, p0y, p1x, p1y, p2x, p2y;
            for (int i = 0; i < _elementCount; i++)
            {
                p0x = _vertices[_elements[i * 3]].Position.X;
                p0y = _vertices[_elements[i * 3]].Position.Y;
                p1x = _vertices[_elements[i * 3 + 1]].Position.X;
                p1y = _vertices[_elements[i * 3 + 1]].Position.Y;
                p2x = _vertices[_elements[i * 3 + 2]].Position.X;
                p2y = _vertices[_elements[i * 3 + 2]].Position.Y;
                if (IsPointInTriangle(point.x, point.y, p0x, p0y, p1x, p1y, p2x, p2y))
                {
                    return true;
                }
            }

            return false;
        }
    }
}
using UnityEngine;
using System.Collections.Generic;

namespace Kamgam.SBR2.LibTessDotNet
{
    public class TriangulatedShape2D
    {
        protected ExtendedTess _tess;

        public int[] Triangles
        {
            get { return _tess.Elements; }
            set { }
        }

        public int TriangleCount
        {
            get { return _tess.ElementCount; }
            set { }
        }

        public ContourVertex[] Vertices
        {
            get { return _tess.Vertices; }
            set { }
        }

        public int VertexCount
        {
            get { return _tess.VertexCount; }
            set { }
        }

        public TriangulatedShape2D(List<Vector2> pointList)
        {
            tesselate(new List<List<Vector2>>() { pointList });
        }

        public TriangulatedShape2D(List<List<Vector2>> pointLists)
        {
            tesselate(pointLists);
        }

        public TriangulatedShape2D(List<Vector3> pointList)
        {
            tesselate(new List<List<Vector3>>() { pointList });
        }

        public TriangulatedShape2D(List<List<Vector3>> pointLists)
        {
            tesselate(pointLists);
        }

        protected void tesselate(List<List<Vector3>> pointLists)
        {
            _tess = new LibTessDotNet.ExtendedTess();
            for (int p = 0; p < pointLists.Count; p++)
            {
                var contour = new LibTessDotNet.ContourVertex[pointLists[p].Count];
                for (int i = 0; i < pointLists[p].Count; i++)
                {
                    contour[i].Position = new LibTessDotNet.Vec3(pointLists[p][i].x, pointLists[p][i].y, pointLists[p][i].z);
                }
                _tess.AddContour(contour, LibTessDotNet.ContourOrientation.Original);
            }
            _tess.Tessellate(LibTessDotNet.WindingRule.EvenOdd, LibTessDotNet.ElementType.Polygons, 3);
        }

        protected void tesselate(List<List<Vector2>> pointLists)
        {
            _tess = new LibTessDotNet.ExtendedTess();
            for (int p = 0; p < pointLists.Count; p++)
            {
                var contour = new LibTessDotNet.ContourVertex[pointLists[p].Count];
                for (int i = 0; i < pointLists[p].Count; i++)
                {
                    contour[i].Position = new LibTessDotNet.Vec3(pointLists[p][i].x, pointLists[p][i].y, 0);
                }
                _tess.AddContour(contour, LibTessDotNet.ContourOrientation.Original);
            }
            _tess.Tessellate(LibTessDotNet.WindingRule.EvenOdd, LibTessDotNet.ElementType.Polygons, 3);
        }

        public bool IsPointInShape(UnityEngine.Vector2 point)
        {
            return _tess.IsPointInShape(point);
        }
    }
}
1 Like

This is so helpful guys. You saved me so much time. I was trying to code my own solutions for the last 3 month .:frowning:
Here is what I am working on btw.
www.thebrief.space

1 Like