private void TriangulateButton_OnClick(object sender, RoutedEventArgs e)
        {
            List <int> triangleIndices;

            // We will manually create the triangle indices with Triangulator
            // This way we can catch the FormatException in case the _points are not correct (for example if lines intersect each other)
            // If we do not need to show the exception message we could simply call the CreateExtrudedMeshGeometry without triangleIndices as second parameter.
            // Than in case the _points were not correct we would simply get null back.


            // Triangulator takes a list of 2D points and connects them with creating triangles (it creates triangle indices that are declared as Int32Collection).

            // If we would only need triangle indices, we could also use static Triangulate method, but we need IsClockwise property later
            //triangleIndices = Ab3d.Utilities.Triangulator.Triangulate(_points);


            // Adjust shape positions so that (0,0) is at the center of the area
            // We also invert y so that y increased upwards (as the blue arrows shows) and not downwards as in Canvas coordinate system
            var centeredShapePositions = InvertYAndCenterPoints(_shapePositions);

            Ab3d.Utilities.Triangulator triangulator = new Ab3d.Utilities.Triangulator(centeredShapePositions);

            try
            {
                triangleIndices = triangulator.CreateTriangleIndices();
            }
            catch (FormatException ex)
            {
                // Usually thrown when the polygon lines intersect each other

                MessageBox.Show(ex.Message);
                return;
            }


            Vector3D extrudeVector, shapeYVector3D;

            bool isSuccess = GetExtrudeAndUpVectors(out extrudeVector, out shapeYVector3D);

            if (!isSuccess)
            {
                return;
            }

            // Get the 3D vector for the positions x axis direction (upVector3D is direction of y axis)
            Vector3D shapeXVector3D = Vector3D.CrossProduct(extrudeVector, shapeYVector3D);

            if (shapeXVector3D.LengthSquared < 0.001)
            {
                MessageBox.Show("Cannot extrude shape because extrudeVector and shapeYVector are parallel and not perpendicular.");
                return;
            }

            // Convert list of 2D positions into a list of 3D positions with specifying custom X axis and Y axis
            Point3DCollection positions = Convert2DTo3DPositions(centeredShapePositions, shapeXVector3D, shapeYVector3D);

            //for (int i = 0; i < _shapePositions.Count; i++)
            //    positions.Add(new Point3D(_shapePositions[i].X + xOffset, yOffset, _shapePositions[i].Y + zOffset));

            var mesh = new MeshGeometry3D();

            mesh.Positions       = positions;
            mesh.TriangleIndices = new Int32Collection(triangleIndices);

            CreateGeometryModel(mesh);

            _isExtrudedModelShown = false;
        }
        public ExtrudeAlongPathSample()
        {
            InitializeComponent();


            // First prepare two 2D shapes:
            var letterTShapePositions = new Point[]
            {
                new Point(5, 0),
                new Point(-5, 0),
                new Point(-5, 40),
                new Point(-20, 40),
                new Point(-20, 50),
                new Point(20, 50),
                new Point(20, 40),
                new Point(5, 40),
            };

            var ellipsePositionList = new List <Point>();

            for (int i = 0; i < 360; i += 20)
            {
                ellipsePositionList.Add(new Point(Math.Sin(i / 180.0 * Math.PI) * 20, Math.Cos(i / 180.0 * Math.PI) * 10));
            }


            // Now define a simple 3D path:
            var extrudePath = new Point3D[]
            {
                new Point3D(0, 0, 0),
                new Point3D(0, 90, 0),
                new Point3D(-20, 110, 0),
                new Point3D(-50, 130, 20),
                new Point3D(-50, 130, 100),
            };


            // Create extruded models:

            MeshGeometry3D extrudedMesh1 = Mesh3DFactory.CreateExtrudedMeshGeometry(
                shapePositions: ellipsePositionList,
                extrudePathPositions: extrudePath,
                shapeYVector3D: new Vector3D(0, 0, -1),
                isClosed: true,
                isSmooth: true);

            CreateGeometryModel(extrudedMesh1, offset: new Vector3D(-150, 0, -50), setBackMaterial: false);



            var extrudedMesh2 = Mesh3DFactory.CreateExtrudedMeshGeometry(
                shapePositions: ellipsePositionList,
                extrudePathPositions: extrudePath,
                shapeYVector3D: new Vector3D(0, 0, -1),
                isClosed: false,
                isSmooth: false);

            // Because this mesh will not be closed, we will be able to see inside - so set the back material to dim gray.
            CreateGeometryModel(extrudedMesh2, offset: new Vector3D(0, 0, -50), setBackMaterial: true);



            // Until now we only provided the shape positions to the CreateExtrudedMeshGeometry.
            // This method then triangulated the shape in case it was closed.
            // Here we manually triangulate the shape and provide the shapeTriangleIndices to CreateExtrudedMeshGeometry:
            var triangulator = new Ab3d.Utilities.Triangulator(letterTShapePositions);

            // NOTE: CreateTriangleIndices can throw FormatException when the positions are not correctly defined (for example if the lines intersect each other).
            List <int> triangleIndices = triangulator.CreateTriangleIndices();

            MeshGeometry3D extrudedMesh = Mesh3DFactory.CreateExtrudedMeshGeometry(
                shapePositions: letterTShapePositions,
                shapeTriangleIndices: triangleIndices,
                extrudePathPositions: extrudePath,
                shapeYVector3D: new Vector3D(0, 0, -1),
                isClosed: true,
                isSmooth: false,
                flipNormals: triangulator.IsClockwise); // If true than normals are flipped - used when positions are defined in a counter clockwise order

            CreateGeometryModel(extrudedMesh, offset: new Vector3D(150, 0, -50), setBackMaterial: false);
        }