Put back the "snap to vertex" stuff to remove tee intersections
that the BSP-based Booleans create. [git-p4: depot-paths = "//depot/solvespace/": change = 1960]
This commit is contained in:
parent
ddbd0ff77b
commit
7536ccb054
|
@ -68,6 +68,10 @@ void SolveSpace::ExportSectionTo(char *filename) {
|
||||||
SBezierList bl;
|
SBezierList bl;
|
||||||
ZERO(&bl);
|
ZERO(&bl);
|
||||||
|
|
||||||
|
// If there's a mesh, then grab the edges from it.
|
||||||
|
g->runningMesh.MakeEdgesInPlaneInto(&el, n, d);
|
||||||
|
|
||||||
|
// If there's a shell, then grab the edges and possibly Beziers.
|
||||||
g->runningShell.MakeSectionEdgesInto(n, d,
|
g->runningShell.MakeSectionEdgesInto(n, d,
|
||||||
&el,
|
&el,
|
||||||
(SS.exportPwlCurves || fabs(SS.exportOffset) > LENGTH_EPS) ? NULL : &bl);
|
(SS.exportPwlCurves || fabs(SS.exportOffset) > LENGTH_EPS) ? NULL : &bl);
|
||||||
|
|
|
@ -176,9 +176,17 @@ void Group::GenerateShellAndMesh(void) {
|
||||||
stepm.MakeFromCopyOf(&(src->thisMesh));
|
stepm.MakeFromCopyOf(&(src->thisMesh));
|
||||||
src->thisShell.TriangulateInto(&stepm);
|
src->thisShell.TriangulateInto(&stepm);
|
||||||
|
|
||||||
|
SMesh outm;
|
||||||
|
ZERO(&outm);
|
||||||
GenerateForStepAndRepeat<SMesh>
|
GenerateForStepAndRepeat<SMesh>
|
||||||
(&prevm, &stepm, &runningMesh, src->meshCombine);
|
(&prevm, &stepm, &outm, src->meshCombine);
|
||||||
|
|
||||||
|
// And make sure that the output mesh is vertex-to-vertex.
|
||||||
|
SKdNode *root = SKdNode::From(&outm);
|
||||||
|
root->SnapToMesh(&outm);
|
||||||
|
root->MakeMeshInto(&runningMesh);
|
||||||
|
|
||||||
|
outm.Clear();
|
||||||
stepm.Clear();
|
stepm.Clear();
|
||||||
prevm.Clear();
|
prevm.Clear();
|
||||||
}
|
}
|
||||||
|
@ -295,8 +303,16 @@ void Group::GenerateShellAndMesh(void) {
|
||||||
thism.MakeFromCopyOf(&thisMesh);
|
thism.MakeFromCopyOf(&thisMesh);
|
||||||
thisShell.TriangulateInto(&thism);
|
thisShell.TriangulateInto(&thism);
|
||||||
|
|
||||||
GenerateForBoolean<SMesh>(&prevm, &thism, &runningMesh);
|
SMesh outm;
|
||||||
|
ZERO(&outm);
|
||||||
|
GenerateForBoolean<SMesh>(&prevm, &thism, &outm);
|
||||||
|
|
||||||
|
// And make sure that the output mesh is vertex-to-vertex.
|
||||||
|
SKdNode *root = SKdNode::From(&outm);
|
||||||
|
root->SnapToMesh(&outm);
|
||||||
|
root->MakeMeshInto(&runningMesh);
|
||||||
|
|
||||||
|
outm.Clear();
|
||||||
thism.Clear();
|
thism.Clear();
|
||||||
prevm.Clear();
|
prevm.Clear();
|
||||||
}
|
}
|
||||||
|
|
299
mesh.cpp
299
mesh.cpp
|
@ -50,6 +50,38 @@ void SMesh::GetBounding(Vector *vmax, Vector *vmin) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//----------------------------------------------------------------------------
|
||||||
|
// Report the edges of the boundary of the region(s) of our mesh that lie
|
||||||
|
// within the plane n dot p = d.
|
||||||
|
//----------------------------------------------------------------------------
|
||||||
|
void SMesh::MakeEdgesInPlaneInto(SEdgeList *sel, Vector n, double d) {
|
||||||
|
SMesh m;
|
||||||
|
ZERO(&m);
|
||||||
|
m.MakeFromCopyOf(this);
|
||||||
|
|
||||||
|
// Delete all triangles in the mesh that do not lie in our export plane.
|
||||||
|
m.l.ClearTags();
|
||||||
|
int i;
|
||||||
|
for(i = 0; i < m.l.n; i++) {
|
||||||
|
STriangle *tr = &(m.l.elem[i]);
|
||||||
|
|
||||||
|
if((fabs(n.Dot(tr->a) - d) >= LENGTH_EPS) ||
|
||||||
|
(fabs(n.Dot(tr->b) - d) >= LENGTH_EPS) ||
|
||||||
|
(fabs(n.Dot(tr->c) - d) >= LENGTH_EPS))
|
||||||
|
{
|
||||||
|
tr->tag = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m.l.RemoveTagged();
|
||||||
|
|
||||||
|
// Select the naked edges in our resulting open mesh.
|
||||||
|
SKdNode *root = SKdNode::From(&m);
|
||||||
|
root->SnapToMesh(&m);
|
||||||
|
root->MakeNakedEdgesInto(sel, false, NULL, NULL);
|
||||||
|
|
||||||
|
m.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
void SMesh::Simplify(int start) {
|
void SMesh::Simplify(int start) {
|
||||||
int maxTriangles = (l.n - start) + 10;
|
int maxTriangles = (l.n - start) + 10;
|
||||||
|
|
||||||
|
@ -469,98 +501,111 @@ void SKdNode::MakeMeshInto(SMesh *m) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void SKdNode::FindEdgeOn(Vector a, Vector b, int *n, int cnt,
|
//-----------------------------------------------------------------------------
|
||||||
bool coplanarIsInter, bool *inter, bool *fwd)
|
// If any triangles in the mesh have an edge that goes through v (but not
|
||||||
{
|
// a vertex at v), then split those triangles so that they now have a vertex
|
||||||
|
// there. The existing triangle is modified, and the new triangle appears
|
||||||
|
// in extras.
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
void SKdNode::SnapToVertex(Vector v, SMesh *extras) {
|
||||||
if(gt && lt) {
|
if(gt && lt) {
|
||||||
double ac = a.Element(which),
|
double vc = v.Element(which);
|
||||||
bc = b.Element(which);
|
if(vc < c + KDTREE_EPS) {
|
||||||
if(ac < c + KDTREE_EPS ||
|
lt->SnapToVertex(v, extras);
|
||||||
bc < c + KDTREE_EPS)
|
|
||||||
{
|
|
||||||
lt->FindEdgeOn(a, b, n, cnt, coplanarIsInter, inter, fwd);
|
|
||||||
}
|
}
|
||||||
if(ac > c - KDTREE_EPS ||
|
if(vc > c - KDTREE_EPS) {
|
||||||
bc > c - KDTREE_EPS)
|
gt->SnapToVertex(v, extras);
|
||||||
{
|
|
||||||
gt->FindEdgeOn(a, b, n, cnt, coplanarIsInter, inter, fwd);
|
|
||||||
}
|
}
|
||||||
return;
|
// Nothing bad happens if the triangle to be split appears in both
|
||||||
}
|
// branches; the first call will split the triangle, so that the
|
||||||
|
// second call will do nothing, because the modified triangle will
|
||||||
// We are a leaf node; so we iterate over all the triangles in our
|
// already contain v
|
||||||
// linked list.
|
} else {
|
||||||
STriangleLl *ll;
|
STriangleLl *ll;
|
||||||
for(ll = tris; ll; ll = ll->next) {
|
for(ll = tris; ll; ll = ll->next) {
|
||||||
STriangle *tr = ll->tri;
|
STriangle *tr = ll->tri;
|
||||||
|
|
||||||
if(tr->tag == cnt) continue;
|
// Do a cheap bbox test first
|
||||||
|
int k;
|
||||||
|
bool mightHit = true;
|
||||||
|
|
||||||
// Test if this triangle matches up with the given edge
|
for(k = 0; k < 3; k++) {
|
||||||
if((a.Equals(tr->b) && b.Equals(tr->a)) ||
|
if((tr->a).Element(k) < v.Element(k) - KDTREE_EPS &&
|
||||||
(a.Equals(tr->c) && b.Equals(tr->b)) ||
|
(tr->b).Element(k) < v.Element(k) - KDTREE_EPS &&
|
||||||
(a.Equals(tr->a) && b.Equals(tr->c)))
|
(tr->c).Element(k) < v.Element(k) - KDTREE_EPS)
|
||||||
{
|
{
|
||||||
(*n)++;
|
mightHit = false;
|
||||||
// Record whether this triangle is front- or back-facing.
|
break;
|
||||||
if(tr->Normal().z > LENGTH_EPS) {
|
|
||||||
*fwd = true;
|
|
||||||
} else {
|
|
||||||
*fwd = false;
|
|
||||||
}
|
}
|
||||||
} else if(((a.Equals(tr->a) && b.Equals(tr->b)) ||
|
if((tr->a).Element(k) > v.Element(k) + KDTREE_EPS &&
|
||||||
(a.Equals(tr->b) && b.Equals(tr->c)) ||
|
(tr->b).Element(k) > v.Element(k) + KDTREE_EPS &&
|
||||||
(a.Equals(tr->c) && b.Equals(tr->a))))
|
(tr->c).Element(k) > v.Element(k) + KDTREE_EPS)
|
||||||
{
|
{
|
||||||
// It's an edge of this triangle, okay.
|
mightHit = false;
|
||||||
} else {
|
break;
|
||||||
// Check for self-intersection
|
|
||||||
Vector n = (tr->Normal()).WithMagnitude(1);
|
|
||||||
double d = (tr->a).Dot(n);
|
|
||||||
double pa = a.Dot(n) - d, pb = b.Dot(n) - d;
|
|
||||||
// It's an intersection if neither point lies in-plane,
|
|
||||||
// and the edge crosses the plane (should handle in-plane
|
|
||||||
// intersections separately but don't yet).
|
|
||||||
if((pa < -LENGTH_EPS || pa > LENGTH_EPS) &&
|
|
||||||
(pb < -LENGTH_EPS || pb > LENGTH_EPS) &&
|
|
||||||
(pa*pb < 0))
|
|
||||||
{
|
|
||||||
// The edge crosses the plane of the triangle; now see if
|
|
||||||
// it crosses inside the triangle.
|
|
||||||
if(tr->ContainsPointProjd(b.Minus(a), a)) {
|
|
||||||
if(coplanarIsInter) {
|
|
||||||
*inter = true;
|
|
||||||
} else {
|
|
||||||
Vector p = Vector::AtIntersectionOfPlaneAndLine(
|
|
||||||
n, d, a, b, NULL);
|
|
||||||
Vector ta = tr->a,
|
|
||||||
tb = tr->b,
|
|
||||||
tc = tr->c;
|
|
||||||
if((p.DistanceToLine(ta, tb.Minus(ta)) < LENGTH_EPS) ||
|
|
||||||
(p.DistanceToLine(tb, tc.Minus(tb)) < LENGTH_EPS) ||
|
|
||||||
(p.DistanceToLine(tc, ta.Minus(tc)) < LENGTH_EPS))
|
|
||||||
{
|
|
||||||
// Intersection lies on edge. This happens when
|
|
||||||
// our edge is from a triangle coplanar with
|
|
||||||
// another triangle in the mesh. We don't test
|
|
||||||
// the edge against triangles whose plane contains
|
|
||||||
// that edge, but we do end up testing against
|
|
||||||
// the coplanar triangle's neighbours, which we
|
|
||||||
// will intersect on their edges.
|
|
||||||
} else {
|
|
||||||
*inter = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if(!mightHit) continue;
|
||||||
|
|
||||||
|
if(tr->a.Equals(v)) { tr->a = v; continue; }
|
||||||
|
if(tr->b.Equals(v)) { tr->b = v; continue; }
|
||||||
|
if(tr->c.Equals(v)) { tr->c = v; continue; }
|
||||||
|
|
||||||
|
if(v.OnLineSegment(tr->a, tr->b)) {
|
||||||
|
STriangle nt = STriangle::From(tr->meta, tr->a, v, tr->c);
|
||||||
|
extras->AddTriangle(&nt);
|
||||||
|
tr->a = v;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if(v.OnLineSegment(tr->b, tr->c)) {
|
||||||
|
STriangle nt = STriangle::From(tr->meta, tr->b, v, tr->a);
|
||||||
|
extras->AddTriangle(&nt);
|
||||||
|
tr->b = v;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if(v.OnLineSegment(tr->c, tr->a)) {
|
||||||
|
STriangle nt = STriangle::From(tr->meta, tr->c, v, tr->b);
|
||||||
|
extras->AddTriangle(&nt);
|
||||||
|
tr->c = v;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure that we don't count this triangle twice if it appears
|
//-----------------------------------------------------------------------------
|
||||||
// in two buckets of the kd tree.
|
// Snap to each vertex of each triangle of the given mesh. If the given mesh
|
||||||
tr->tag = cnt;
|
// is identical to the mesh used to make this kd tree, then the result should
|
||||||
|
// be a vertex-to-vertex mesh.
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
void SKdNode::SnapToMesh(SMesh *m) {
|
||||||
|
int i, j, k;
|
||||||
|
for(i = 0; i < m->l.n; i++) {
|
||||||
|
STriangle *tr = &(m->l.elem[i]);
|
||||||
|
for(j = 0; j < 3; j++) {
|
||||||
|
Vector v = ((j == 0) ? tr->a :
|
||||||
|
((j == 1) ? tr->b :
|
||||||
|
tr->c));
|
||||||
|
|
||||||
|
SMesh extra;
|
||||||
|
ZERO(&extra);
|
||||||
|
SnapToVertex(v, &extra);
|
||||||
|
|
||||||
|
for(k = 0; k < extra.l.n; k++) {
|
||||||
|
STriangle *tra = (STriangle *)AllocTemporary(sizeof(*tra));
|
||||||
|
*tra = extra.l.elem[k];
|
||||||
|
AddTriangle(tra);
|
||||||
|
}
|
||||||
|
extra.Clear();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
// For all the edges in sel, split them against the given triangle, and test
|
||||||
|
// them for occlusion. Keep only the visible segments. sel is both our input
|
||||||
|
// and our output.
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
void SKdNode::SplitLinesAgainstTriangle(SEdgeList *sel, STriangle *tr) {
|
void SKdNode::SplitLinesAgainstTriangle(SEdgeList *sel, STriangle *tr) {
|
||||||
SEdgeList seln;
|
SEdgeList seln;
|
||||||
ZERO(&seln);
|
ZERO(&seln);
|
||||||
|
@ -666,6 +711,10 @@ void SKdNode::SplitLinesAgainstTriangle(SEdgeList *sel, STriangle *tr) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
// Given an edge orig, occlusion test it against our mesh. We output an edge
|
||||||
|
// list in sel, containing the visible portions of that edge.
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
void SKdNode::OcclusionTestLine(SEdge orig, SEdgeList *sel, int cnt) {
|
void SKdNode::OcclusionTestLine(SEdge orig, SEdgeList *sel, int cnt) {
|
||||||
if(gt && lt) {
|
if(gt && lt) {
|
||||||
double ac = (orig.a).Element(which),
|
double ac = (orig.a).Element(which),
|
||||||
|
@ -697,6 +746,107 @@ void SKdNode::OcclusionTestLine(SEdge orig, SEdgeList *sel, int cnt) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
// Search the mesh for a triangle with an edge from b to a (i.e., the mate
|
||||||
|
// for the edge from a to b), and increment *n each time that we find one.
|
||||||
|
// If a triangle is found, then report whether it is front- or back-facing
|
||||||
|
// using *fwd. And regardless of whether a mate is found, report whether
|
||||||
|
// the edge intersects the mesh with *inter; if coplanarIsInter then we
|
||||||
|
// count the edge as intersecting if it's coplanar with a triangle in the
|
||||||
|
// mesh, otherwise not.
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
void SKdNode::FindEdgeOn(Vector a, Vector b, int *n, int cnt,
|
||||||
|
bool coplanarIsInter, bool *inter, bool *fwd)
|
||||||
|
{
|
||||||
|
if(gt && lt) {
|
||||||
|
double ac = a.Element(which),
|
||||||
|
bc = b.Element(which);
|
||||||
|
if(ac < c + KDTREE_EPS ||
|
||||||
|
bc < c + KDTREE_EPS)
|
||||||
|
{
|
||||||
|
lt->FindEdgeOn(a, b, n, cnt, coplanarIsInter, inter, fwd);
|
||||||
|
}
|
||||||
|
if(ac > c - KDTREE_EPS ||
|
||||||
|
bc > c - KDTREE_EPS)
|
||||||
|
{
|
||||||
|
gt->FindEdgeOn(a, b, n, cnt, coplanarIsInter, inter, fwd);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We are a leaf node; so we iterate over all the triangles in our
|
||||||
|
// linked list.
|
||||||
|
STriangleLl *ll;
|
||||||
|
for(ll = tris; ll; ll = ll->next) {
|
||||||
|
STriangle *tr = ll->tri;
|
||||||
|
|
||||||
|
if(tr->tag == cnt) continue;
|
||||||
|
|
||||||
|
// Test if this triangle matches up with the given edge
|
||||||
|
if((a.Equals(tr->b) && b.Equals(tr->a)) ||
|
||||||
|
(a.Equals(tr->c) && b.Equals(tr->b)) ||
|
||||||
|
(a.Equals(tr->a) && b.Equals(tr->c)))
|
||||||
|
{
|
||||||
|
(*n)++;
|
||||||
|
// Record whether this triangle is front- or back-facing.
|
||||||
|
if(tr->Normal().z > LENGTH_EPS) {
|
||||||
|
*fwd = true;
|
||||||
|
} else {
|
||||||
|
*fwd = false;
|
||||||
|
}
|
||||||
|
} else if(((a.Equals(tr->a) && b.Equals(tr->b)) ||
|
||||||
|
(a.Equals(tr->b) && b.Equals(tr->c)) ||
|
||||||
|
(a.Equals(tr->c) && b.Equals(tr->a))))
|
||||||
|
{
|
||||||
|
// It's an edge of this triangle, okay.
|
||||||
|
} else {
|
||||||
|
// Check for self-intersection
|
||||||
|
Vector n = (tr->Normal()).WithMagnitude(1);
|
||||||
|
double d = (tr->a).Dot(n);
|
||||||
|
double pa = a.Dot(n) - d, pb = b.Dot(n) - d;
|
||||||
|
// It's an intersection if neither point lies in-plane,
|
||||||
|
// and the edge crosses the plane (should handle in-plane
|
||||||
|
// intersections separately but don't yet).
|
||||||
|
if((pa < -LENGTH_EPS || pa > LENGTH_EPS) &&
|
||||||
|
(pb < -LENGTH_EPS || pb > LENGTH_EPS) &&
|
||||||
|
(pa*pb < 0))
|
||||||
|
{
|
||||||
|
// The edge crosses the plane of the triangle; now see if
|
||||||
|
// it crosses inside the triangle.
|
||||||
|
if(tr->ContainsPointProjd(b.Minus(a), a)) {
|
||||||
|
if(coplanarIsInter) {
|
||||||
|
*inter = true;
|
||||||
|
} else {
|
||||||
|
Vector p = Vector::AtIntersectionOfPlaneAndLine(
|
||||||
|
n, d, a, b, NULL);
|
||||||
|
Vector ta = tr->a,
|
||||||
|
tb = tr->b,
|
||||||
|
tc = tr->c;
|
||||||
|
if((p.DistanceToLine(ta, tb.Minus(ta)) < LENGTH_EPS) ||
|
||||||
|
(p.DistanceToLine(tb, tc.Minus(tb)) < LENGTH_EPS) ||
|
||||||
|
(p.DistanceToLine(tc, ta.Minus(tc)) < LENGTH_EPS))
|
||||||
|
{
|
||||||
|
// Intersection lies on edge. This happens when
|
||||||
|
// our edge is from a triangle coplanar with
|
||||||
|
// another triangle in the mesh. We don't test
|
||||||
|
// the edge against triangles whose plane contains
|
||||||
|
// that edge, but we do end up testing against
|
||||||
|
// the coplanar triangle's neighbours, which we
|
||||||
|
// will intersect on their edges.
|
||||||
|
} else {
|
||||||
|
*inter = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure that we don't count this triangle twice if it appears
|
||||||
|
// in two buckets of the kd tree.
|
||||||
|
tr->tag = cnt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
// Report all naked edges of the mesh (i.e., edges that don't join up to
|
// Report all naked edges of the mesh (i.e., edges that don't join up to
|
||||||
// a single anti-parallel edge of another triangle), and all edges that
|
// a single anti-parallel edge of another triangle), and all edges that
|
||||||
|
@ -744,6 +894,11 @@ void SKdNode::MakeNakedEdgesInto(SEdgeList *sel, bool coplanarIsInter,
|
||||||
m.Clear();
|
m.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
|
// Report all the edges of the mesh where a front- and back-facing triangle
|
||||||
|
// join. These edges should be drawn when we generate a wireframe drawing
|
||||||
|
// of the part.
|
||||||
|
//-----------------------------------------------------------------------------
|
||||||
void SKdNode::MakeTurningEdgesInto(SEdgeList *sel) {
|
void SKdNode::MakeTurningEdgesInto(SEdgeList *sel) {
|
||||||
SMesh m;
|
SMesh m;
|
||||||
ZERO(&m);
|
ZERO(&m);
|
||||||
|
|
|
@ -191,6 +191,8 @@ public:
|
||||||
void MakeFromTransformationOf(SMesh *a, Vector trans, Quaternion q);
|
void MakeFromTransformationOf(SMesh *a, Vector trans, Quaternion q);
|
||||||
void MakeFromAssemblyOf(SMesh *a, SMesh *b);
|
void MakeFromAssemblyOf(SMesh *a, SMesh *b);
|
||||||
|
|
||||||
|
void MakeEdgesInPlaneInto(SEdgeList *sel, Vector n, double d);
|
||||||
|
|
||||||
bool IsEmpty(void);
|
bool IsEmpty(void);
|
||||||
void RemapFaces(Group *g, int remap);
|
void RemapFaces(Group *g, int remap);
|
||||||
|
|
||||||
|
@ -237,6 +239,9 @@ public:
|
||||||
|
|
||||||
void OcclusionTestLine(SEdge orig, SEdgeList *sel, int cnt);
|
void OcclusionTestLine(SEdge orig, SEdgeList *sel, int cnt);
|
||||||
void SplitLinesAgainstTriangle(SEdgeList *sel, STriangle *tr);
|
void SplitLinesAgainstTriangle(SEdgeList *sel, STriangle *tr);
|
||||||
|
|
||||||
|
void SnapToMesh(SMesh *m);
|
||||||
|
void SnapToVertex(Vector v, SMesh *extras);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
Loading…
Reference in New Issue
Block a user