EDM Simulation using MultiFastMesh

EDMsim.png

 

In this article, we explore a practical application of the new MultiFastMesh entity in a real-world scenario, specifically focusing on creating a fast and efficient 3D visualization for a wire-cut EDM simulation.

Before diving into the details, we highly recommend reading our introductory article on MultiFastMesh.

MultiFastMesh provides a way to batch all the meshes that represent each simulation step into a single entity while offering the possibility to toggle the visibility of individual sub-meshes. This allows us to group all the meshes into a single entity and gradually turn on the visibility of each sub-mesh as the simulation progresses.

Here you'll find the implementation of an entity that inherits from MultiFastMesh and is specifically designed to display wire-cut EDM simulations.

public class EdmSimMesh : MultiFastMesh
{
    #region Static Utils
    private static void WriteVertex(float[] vertices, int offset, Point3D point)
    {
        vertices[offset + 0] = (float)point.X;
        vertices[offset + 1] = (float)point.Y;
        vertices[offset + 2] = (float)point.Z;
    }
    private static void WriteNormal(float[] normals, int offset, Vector3D normal)
    {
        normals[offset + 0] = (float)normal.X;
        normals[offset + 1] = (float)normal.Y;
        normals[offset + 2] = (float)normal.Z;
    }
    private static Vector3D ComputeNormal(Vector3D tu, Vector3D tv)
    {
        Vector3D n;
        n = Vector3D.Cross(tu, tv);
        n.Normalize();
        return n;
    }

    private static void ComputeNormal(
        Line prev, Line step, Line next,
        out Vector3D normalAtStartPoint,
        out Vector3D normalAtEndPoint
    )
    {
        // Computes the normal at the start and end point
        // of a step (a line) given its prev and next step.

        if (step != null && prev != null && next != null)
        {
            Vector3D s0 = (step.StartPoint - prev.StartPoint).AsVector;
            s0.Normalize();
            Vector3D e0 = (next.StartPoint - step.StartPoint).AsVector;
            e0.Normalize();
            Vector3D v0 = (s0 + e0);
            v0.Normalize();

            Vector3D s1 = (step.EndPoint - prev.EndPoint).AsVector;
            s1.Normalize();
            Vector3D e1 = (next.EndPoint - step.EndPoint).AsVector;
            e1.Normalize();
            Vector3D v1 = (s1 + e1);
            v1.Normalize();

            normalAtStartPoint = ComputeNormal(v0, (step.EndPoint - step.StartPoint).AsVector);
            normalAtEndPoint = ComputeNormal(v1, (step.EndPoint - step.StartPoint).AsVector);

        }
        else// TODO
        {
            normalAtStartPoint = Vector3D.AxisX;
            normalAtEndPoint = Vector3D.AxisX;
        }
    }

    private static MultiFastMesh BuildFastMeshes(IList<Line> steps)
    {
        // Builds a mesh from a list of steps (lines)
        // as a continuous strip.

        float[] vertices = new float[steps.Count * 3 * 2]; // 2 vertices per line, 3 components per vertex
        float[] normals = new float[steps.Count * 3 * 2];
        int[] triangles = new int[(steps.Count - 1) * 2 * 3];

        for (int i = 0; i < steps.Count; i++)
        {
            int vertIdx0 = i * 3 * 2 + 0;
            int vertIdx1 = i * 3 * 2 + 3;

            // Copy the line's vertices
            WriteVertex(vertices, vertIdx0, steps[i].StartPoint);
            WriteVertex(vertices, vertIdx1, steps[i].EndPoint);

            ComputeNormal(
                i > 0 ? steps[i - 1] : null,
                steps[i],
                i >= steps.Count - 1 ? null : steps[i + 1],
                out Vector3D n0, out Vector3D n1);

            WriteNormal(normals, vertIdx0, n0);
            WriteNormal(normals, vertIdx1, n1);
        }

        for (int i = 0; i < steps.Count - 1; i++)
        {
            // Build 2 tris (ccw)
            triangles[i * 6 + 0] = (i + 0) * 2 + 0;
            triangles[i * 6 + 1] = (i + 1) * 2 + 1;
            triangles[i * 6 + 2] = (i + 0) * 2 + 1;

            triangles[i * 6 + 3] = (i + 0) * 2 + 0;
            triangles[i * 6 + 4] = (i + 1) * 2 + 0;
            triangles[i * 6 + 5] = (i + 1) * 2 + 1;
        }

        /* IMPORTANT */
        /* --------------------------------------------
         A MultiFastMesh allows ranged drawing: 
         you can specify color and visibility
         for different portions of the mesh.
         To achieve that, we must specify the 
         list of `sub-meshes`: here we are 
         saying that each quad of the strip
         we just built is a sub-mesh, for a total
         of steps.Count - 1 sub-meshes.
         With this setup, we can later ask
         the MultiFastMesh to draw only the
         sub-meshes corresponding to steps [0, N] to 
         show only the first N movements.
        -------------------------------------------- */

        Tuple<int, int>[] subMeshes = new Tuple<int, int>[steps.Count - 1];
        for (int i = 0; i < subMeshes.Length; i++)
        {
            // a sub-mesh is defined as a range of 
            // indices in the triangles array:
            // sub-mesh[i] goes from index i*6 to i*6+1 (2 tris)
            subMeshes[i] = new Tuple<int, int>(i * 6 + 0, i * 6 + 6);
        }

        return new MultiFastMesh(vertices, normals, triangles, subMeshes);
    }

    #endregion

    private int _currentStep = 0;
    private int _stepCount = 0;
    private Line[] _steps = null;

    public Color BaseColor = System.Drawing.Color.Gray;
    public Color HighlightColor = System.Drawing.Color.OrangeRed;

    // Used to draw some extras such as two cones
    // at the start and the end of the cutting wire
    private FastMesh _myPrivateMesh = null;
    private Point3D[] _myPoints = null;
    private Vector3D[] _myNormals = null;

    private void InitPrivateMesh(double lineLen)
    {
        Mesh cone0 = Mesh.CreateCone(lineLen * 0.1, 0.0001, Point3D.Origin, Point3D.Origin + Vector3D.AxisZ * 0.25 * lineLen, 6);
        Mesh cone1 = Mesh.CreateCone(lineLen * 0.1, 0.0001, Point3D.Origin, Point3D.Origin + Vector3D.AxisZ * 0.25 * lineLen, 6);

        cone0.Translate(0, 0, -0.5 * lineLen - 0.25 * lineLen);
        cone1.Rotate(UtilityEx.PI, Vector3D.AxisX);
        cone1.Translate(0, 0, 0.5 * lineLen + 0.25 * lineLen);

        cone0.MergeWith(cone1, false, false);

        _myPrivateMesh = cone0.ConvertToFastMesh(false);
        _myPoints = new Point3D[_myPrivateMesh.PointArray.Length / 3];
        _myNormals = new Vector3D[_myPrivateMesh.NormalArray.Length / 3];

        for (int i = 0; i < _myPrivateMesh.PointArray.Length; i += 3)
        {
            _myPoints[i / 3] = new Point3D
            (
                _myPrivateMesh.PointArray[i + 0],
                _myPrivateMesh.PointArray[i + 1],
                _myPrivateMesh.PointArray[i + 2]
            );

            // one normal per vertex
            _myNormals[i / 3] = new Vector3D
            (
                _myPrivateMesh.NormalArray[i + 0],
                _myPrivateMesh.NormalArray[i + 1],
                _myPrivateMesh.NormalArray[i + 2]
            );
        }
    }

    /// <summary>
    /// Standard constructor.
    /// </summary>
    /// <param name="steps">A list of ordered cutting steps. Each line defines the position of the wire at that step.</param>
    /// <param name="baseColor">The color applied to the simulation mesh or null if the default color should be used. </param>
    /// <param name="highlightColor">The color applied to the latest step represented by the simulation mesh or null if the default color should be used.</param>
    public EdmSimMesh(IList<Line> steps, Color? baseColor, Color? highlightColor) : base(BuildFastMeshes(steps))
    {
        _stepCount = steps.Count;
        _steps = steps.ToArray();

        if (baseColor.HasValue) BaseColor = baseColor.Value;
        if (highlightColor.HasValue) HighlightColor = highlightColor.Value;

        InitPrivateMesh(steps.First().Length() * 2.5);
        MoveToStep(_stepCount - 1);
    }

    /// <summary>
    /// Moves the simulation to step <paramref name="n"/>, showing the result
    /// of the cutting process from step 0 to the specified step.
    /// </summary>
    /// <param name="n"> The index of the step.</param>
    public void MoveToStep(int n)
    {
        if (n < 0) throw new ArgumentException("Invalid step: step index must be positive or 0", nameof(n));
        n = Math.Min(_stepCount - 1, n);
        _currentStep = n;

        if (n == 0)
        {
            SubMeshColors = new Tuple<int, int, Color>[] { };
        }
        else if (n == 1)
        {
            SubMeshColors = new[] { new Tuple<int, int, Color>(0, 0, HighlightColor) };
        }
        else 
        {
            base.SubMeshColors = new Tuple<int, int, Color>[2];
            SubMeshColors[0] = new Tuple<int, int, Color>(0, n - 2, BaseColor);
            SubMeshColors[1] = new Tuple<int, int, Color>(n-1, n-1, HighlightColor);
        }
        
    }

    private void DrawEdmWire(DrawParams data, Line l)
    {
        // Draws a line representing the cutting wire
        // and 2 cones at the start and end of the wire.
        Line myl = (Line)l.Clone();
        myl.Scale(myl.MidPoint, 2.5);

        // Draw the cutting wire
         data.RenderContext.PushShader();

         data.RenderContext.SetColorWireframe(HighlightColor);
         float prev = data.RenderContext.SetLineSize(4.0f, true, false);
         data.RenderContext.DrawLine(myl.StartPoint, myl.EndPoint);
         data.RenderContext.SetLineSize(prev);
      
        // Draw the cones
        var transform = new Align3D(Plane.XY, new Plane(l.MidPoint, l.Tangent));

        data.RenderContext.SetShader(shaderType.NoLights);

        // Far from the optimal approach, 
        // however, the amount of vertices is
        // really low...
        data.RenderContext.DrawTriangles(

        _myPoints.Select(p =>
        {
            Point3D newP = (Point3D)p.Clone();
            newP.TransformBy(transform);
            return newP;
        }).ToArray()

        , _myNormals.Select(p =>
        {
            Vector3D newP = (Vector3D)p.Clone();
            newP.TransformBy(transform);
            return newP;
        }).ToArray());

        data.RenderContext.PopShader();
    }

    private bool _suspendExtras = false;

    public override void Draw(DrawParams data)
    {
        base.Draw(data);

        // Don't show the cutting wire in the 
        // planar reflections
        _suspendExtras |= data.PlanarReflections;

        if (!_suspendExtras)
            DrawEdmWire(data, _steps[_currentStep]);

        _suspendExtras = false;

    }

    public override void DrawForShadow(RenderParams data)
    {
        _suspendExtras = true;
        base.DrawForShadow(data);
        _suspendExtras = false;
    }

    public override void DrawForSelection(DrawForSelectionParams data)
    {
        _suspendExtras = true;
        base.DrawForSelection(data);
        _suspendExtras = false;
    }

    public override void DrawSelected(DrawParams data)
    {
        _suspendExtras = true;
        base.DrawSelected(data);
        _suspendExtras = false;
    }
}

 

2023-07-05_17-36-30.gif

The following is a small sample that shows how you could use EdmSimMesh:

// some settings to improve performance
design1.Rendered.ShadowMode = shadowType.None;
design1.Rendered.SilhouettesDrawingMode = silhouettesDrawingType.Never;

// Creating some geometry to build the EdmMeshes
// -----------------------------------------------------------------------------
Brep stock = Brep.CreateBox(6, 3, 1);
stock.Translate(-1.5, -1.5, 0);

Region r1 = new Region(new Circle(Plane.XY, Point2D.Origin, 1));
Region r2 = new Region(CompositeCurve.CreateRoundedRectangle(2.5, 0.7, 0.3, true));
r2.Translate(1.75, 0, 0);

Region rBot = devDept.Eyeshot.Entities.Region.Union(r1, r2)[0];
rBot.Regen(0.001);

Region rTop = (Region) rBot.Clone();
rTop.Translate(0, 0, 1);
rTop.Regen(0.001);

// Decrease/increase the argument of GetPointsByLength to
// increase/decrease the number of steps.
double subdv = 0.1;
LinearPath topPath = new LinearPath(rTop.ContourList[0].GetPointsByLength(subdv));
LinearPath botPath = new LinearPath(rBot.ContourList[0].GetPointsByLength(subdv));
topPath.LineWeight = 4.0f;
botPath.LineWeight = 4.0f;
topPath.LineWeightMethod = colorMethodType.byEntity;
botPath.LineWeightMethod = colorMethodType.byEntity;
botPath.Scale(1.3, 1.3, 1);
rBot.Scale(1.3, 1.3, 1);

List<Line> lines = new List<Line>();
for (int i = 0; i < topPath.Vertices.Length; i++)
{
    lines.Add(new Line(botPath.Vertices[i], topPath.Vertices[i]));
}
// -----------------------------------------------------------------------------

// Adding the entities to the scene
EdmSimMesh edmMesh = new EdmSimMesh(lines, Color.CadetBlue, Color.Magenta);
design1.Entities.Add(edmMesh, Color.FromArgb(255, Color.Black));
design1.Entities.Add(topPath, Color.FromArgb(160, Color.Aquamarine));
design1.Entities.Add(botPath, Color.FromArgb(160, Color.Aquamarine));
design1.Entities.Add(stock, Color.FromArgb(60, Color.DarkGray));

// You can download QuickForms (released by devDept) from NuGet.
QuickForm qkf = new QuickForm();

qkf.AddTrackBar("Step", 0, lines.Count - 1, 1, d =>
{
    int i = (int)d;
    edmMesh.MoveToStep(i);
    design1.Invalidate();
});

qkf.Show();

Was this article helpful?
1 out of 1 found this helpful

Comments

1 comment
Date Votes
  • this is amazing  stuff .. top and bottom curves can be iges or step ?

    0

Please sign in to leave a comment.

Articles in this section

See more