使用 MultiFastMesh 进行 EDM 仿真
在本文中,使用Eyeshot探索新的MultiFastMesh实体在现实场景中的实际应用,特别关注为线切割 EDM 模拟创建快速高效的 3D 可视化。
在深入了解详细信息之前,我们强烈建议您阅读有关 MultiFastMesh 的介绍性文章。
MultiFastMesh提供了一种将代表每个模拟步骤的所有网格体批处理为单个实体的方法,同时提供了切换各个子网格体可见性的可能性。这使我们能够将所有网格分组为一个实体,并随着模拟的进行逐渐打开每个子网格的可见性。
在这里,您将找到继承自MultiFastMesh 且专门用于显示线切割 EDM 模拟的实体的实现。
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.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;
}
}
以下是一个小示例,展示了如何使用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();