# EDM Simulation using MultiFastMesh

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.
-------------------------------------------- */

IntInterval[] subMeshes = new IntInterval[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+1)*6 (2 tris)
subMeshes[i] = new IntInterval(i * 6 + 0, i * 6 + 5);
}

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

#endregion

private const double PI = 3.14159265359;

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(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)
{
SubMeshIntervals = new List<SubMeshInterval>{ };
}
else if (n == 1)
{
SubMeshIntervals = new List<SubMeshInterval> { new SubMeshInterval(0, 0, HighlightColor) };
}
else
{
SubMeshIntervals = new List<SubMeshInterval>
{
new SubMeshInterval(0, n - 2, BaseColor),
new SubMeshInterval(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.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));

// 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());

}

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;

}
{
_suspendExtras = true;
_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;
}
}``````

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

``````// some settings to improve performance
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++)
{
}
// -----------------------------------------------------------------------------

// Adding the entities to the scene
EdmSimMesh edmMesh = new EdmSimMesh(lines, Color.CadetBlue, Color.Magenta);

QuickForm qkf = new QuickForm();

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

qkf.Show();

``````

1 out of 1 found this helpful