Add selectable faces, by associating an hEntity with the triangle's

metadata. And add point-on-face constraints to go with that. Still
needs some cleanup for the user interface.

[git-p4: depot-paths = "//depot/solvespace/": change = 1766]
This commit is contained in:
Jonathan Westhues 2008-06-01 19:31:37 -08:00
parent 57db9bea34
commit 6748160026
15 changed files with 323 additions and 29 deletions

View File

@ -126,6 +126,10 @@ void Constraint::MenuConstrain(int id) {
c.type = PT_ON_CIRCLE;
c.ptA = gs.point[0];
c.entityA = gs.entity[0];
} else if(gs.points == 1 && gs.faces == 1 && gs.n == 2) {
c.type = PT_ON_FACE;
c.ptA = gs.point[0];
c.entityA = gs.face[0];
} else {
Error("Bad selection for on point / curve / plane constraint.");
return;
@ -547,6 +551,16 @@ void Constraint::Generate(IdList<Equation,hEquation> *l) {
SS.GetEntity(ptA)->PointGetExprs(), entityA), 0);
break;
case PT_ON_FACE: {
// a plane, n dot (p - p0) = 0
ExprVector p = SS.GetEntity(ptA)->PointGetExprs();
Entity *f = SS.GetEntity(entityA);
ExprVector p0 = f->FaceGetPointExprs();
ExprVector n = f->FaceGetNormalExprs();
AddEq(l, (p.Minus(p0)).Dot(n), 0);
break;
}
case PT_ON_LINE:
if(workplane.v == Entity::FREE_IN_3D.v) {
Entity *ln = SS.GetEntity(entityA);

View File

@ -224,11 +224,25 @@ void Constraint::DrawOrGetDistance(Vector *labelPos) {
case PT_ON_CIRCLE:
case PT_ON_LINE:
case PT_ON_FACE:
case PT_IN_PLANE: {
double s = 7/SS.GW.scale;
double s = 8/SS.GW.scale;
Vector p = SS.GetEntity(ptA)->PointGetNum();
Vector r = gr.WithMagnitude(s);
Vector d = gu.WithMagnitude(s);
Vector r, d;
if(type == PT_ON_FACE) {
Vector n = SS.GetEntity(entityA)->FaceGetNormalNum();
r = n.Normal(0);
d = n.Normal(1);
} else if(type == PT_IN_PLANE) {
Entity *n = SS.GetEntity(entityA)->Normal();
r = n->NormalU();
d = n->NormalV();
} else {
r = gr;
d = gu;
s *= (6.0/8); // draw these a little smaller
}
r = r.WithMagnitude(s); d = d.WithMagnitude(s);
LineDrawOrGetDistance(p.Plus (r).Plus (d), p.Plus (r).Minus(d));
LineDrawOrGetDistance(p.Plus (r).Minus(d), p.Minus(r).Minus(d));
LineDrawOrGetDistance(p.Minus(r).Minus(d), p.Minus(r).Plus (d));

1
dsc.h
View File

@ -16,6 +16,7 @@ public:
double w, vx, vy, vz;
static Quaternion From(double w, double vx, double vy, double vz);
static Quaternion From(hParam w, hParam vx, hParam vy, hParam vz);
static Quaternion From(Vector u, Vector v);
Quaternion Plus(Quaternion b);

View File

@ -574,6 +574,69 @@ Quaternion Entity::PointGetQuaternion(void) {
return q;
}
bool Entity::IsFace(void) {
switch(type) {
case FACE_NORMAL_PT:
case FACE_XPROD:
case FACE_N_ROT_TRANS:
return true;
default:
return false;
}
}
ExprVector Entity::FaceGetNormalExprs(void) {
ExprVector r;
if(type == FACE_NORMAL_PT) {
r = ExprVector::From(numNormal.vx, numNormal.vy, numNormal.vz);
} else if(type == FACE_XPROD) {
ExprVector vc = ExprVector::From(param[0], param[1], param[2]);
ExprVector vn = ExprVector::From(numVector);
r = vc.Cross(vn);
} else if(type == FACE_N_ROT_TRANS) {
// The numerical normal vector gets the rotation
r = ExprVector::From(numNormal.vx, numNormal.vy, numNormal.vz);
ExprQuaternion q =
ExprQuaternion::From(param[3], param[4], param[5], param[6]);
r = q.Rotate(r);
} else oops();
return r;
}
Vector Entity::FaceGetNormalNum(void) {
Vector r;
if(type == FACE_NORMAL_PT) {
r = Vector::From(numNormal.vx, numNormal.vy, numNormal.vz);
} else if(type == FACE_XPROD) {
Vector vc = Vector::From(param[0], param[1], param[2]);
r = vc.Cross(numVector);
} else if(type == FACE_N_ROT_TRANS) {
// The numerical normal vector gets the rotation
r = Vector::From(numNormal.vx, numNormal.vy, numNormal.vz);
Quaternion q = Quaternion::From(param[3], param[4], param[5], param[6]);
r = q.Rotate(r);
} else oops();
return r;
}
ExprVector Entity::FaceGetPointExprs(void) {
ExprVector r;
if(type == FACE_NORMAL_PT) {
r = SS.GetEntity(point[0])->PointGetExprs();
} else if(type == FACE_XPROD) {
r = ExprVector::From(numPoint);
} else if(type == FACE_N_ROT_TRANS) {
// The numerical point gets the rotation and translation.
ExprVector trans = ExprVector::From(param[0], param[1], param[2]);
ExprQuaternion q =
ExprQuaternion::From(param[3], param[4], param[5], param[6]);
r = ExprVector::From(numPoint);
r = q.Rotate(r);
r = r.Plus(trans);
} else oops();
return r;
}
void Entity::LineDrawOrGetDistance(Vector a, Vector b) {
if(dogd.drawing) {
// glPolygonOffset works only on polys, not lines, so do it myself
@ -890,6 +953,12 @@ void Entity::DrawOrGetDistance(int order) {
break;
}
case FACE_NORMAL_PT:
case FACE_XPROD:
case FACE_N_ROT_TRANS:
// Do nothing; these are drawn with the triangle mesh
break;
default:
oops();
}
@ -933,5 +1002,11 @@ void Entity::CalculateNumerical(void) {
if(type == DISTANCE || type == DISTANCE_N_COPY) {
actDistance = DistanceGetNum();
}
if(IsFace()) {
ExprVector p = FaceGetPointExprs();
ExprVector n = FaceGetNormalExprs();
numPoint = Vector::From( p.x->Eval(), p.y->Eval(), p.z->Eval());
numNormal = Quaternion::From(0, n.x->Eval(), n.y->Eval(), n.z->Eval());
}
}

View File

@ -21,6 +21,14 @@ ExprVector ExprVector::From(hParam x, hParam y, hParam z) {
return ve;
}
ExprVector ExprVector::From(double x, double y, double z) {
ExprVector ve;
ve.x = Expr::From(x);
ve.y = Expr::From(y);
ve.z = Expr::From(z);
return ve;
}
ExprVector ExprVector::Minus(ExprVector b) {
ExprVector r;
r.x = x->Minus(b.x);
@ -236,6 +244,15 @@ int Expr::Children(void) {
}
}
int Expr::Nodes(void) {
switch(Children()) {
case 0: return 1;
case 1: return 1 + a->Nodes();
case 2: return 1 + a->Nodes() + b->Nodes();
default: oops();
}
}
Expr *Expr::DeepCopy(void) {
Expr *n = AllocExpr();
*n = *this;

3
expr.h
View File

@ -92,6 +92,8 @@ public:
Expr *DeepCopy(void);
// number of child nodes: 0 (e.g. constant), 1 (sqrt), or 2 (+)
int Children(void);
// total number of nodes in the tree
int Nodes(void);
// Make a copy, with the parameters (usually referenced by hParam)
// resolved to pointers to the actual value. This speeds things up
@ -125,6 +127,7 @@ public:
static ExprVector From(Expr *x, Expr *y, Expr *z);
static ExprVector From(Vector vn);
static ExprVector From(hParam x, hParam y, hParam z);
static ExprVector From(double x, double y, double z);
ExprVector Plus(ExprVector b);
ExprVector Minus(ExprVector b);

View File

@ -111,7 +111,7 @@ void glxColor4d(double r, double g, double b, double a)
if(!ColorLocked) glColor4d(r, g, b, a);
}
void glxFillMesh(bool useModelColor, SMesh *m)
void glxFillMesh(int specColor, SMesh *m, DWORD h, DWORD s1, DWORD s2)
{
glEnable(GL_NORMALIZE);
int prevColor = -1;
@ -121,8 +121,19 @@ void glxFillMesh(bool useModelColor, SMesh *m)
Vector n = tr->Normal();
glNormal3d(n.x, n.y, n.z);
int color = tr->meta.color;
if(useModelColor && color != prevColor) {
int color;
if((s1 != 0 && tr->meta.face == s1) ||
(s2 != 0 && tr->meta.face == s2))
{
color = RGB(200, 0, 0);
} else if(h != 0 && tr->meta.face == h) {
color = RGB(200, 200, 0);
} else if(specColor < 0) {
color = tr->meta.color;
} else {
color = specColor;
}
if(color != prevColor) {
GLfloat mpf[] = { ((color >> 0) & 0xff) / 255.0f,
((color >> 8) & 0xff) / 255.0f,
((color >> 16) & 0xff) / 255.0f, 1.0 };

View File

@ -745,6 +745,15 @@ void GraphicsWindow::HitTestMakeSelection(Point2d mp) {
}
}
// Faces, from the triangle mesh; these are lowest priority
if(s.constraint.v == 0 && s.entity.v == 0 && (showMesh || showShaded)) {
SMesh *m = &((SS.GetGroup(activeGroup))->mesh);
DWORD v = m->FirstIntersectionWith(mp);
if(v) {
s.entity.v = v;
}
}
if(!s.Equals(&hover)) {
hover = s;
InvalidateGraphics();
@ -804,6 +813,11 @@ void GraphicsWindow::GroupSelection(void) {
gs.vector[(gs.vectors)++] = s->entity;
}
// Faces (which are special, associated/drawn with triangles)
if(e->IsFace()) {
gs.face[(gs.faces)++] = s->entity;
}
// And some aux counts too
switch(e->type) {
case Entity::WORKPLANE: (gs.workplanes)++; break;

View File

@ -235,6 +235,35 @@ void SMesh::MakeFromDifference(SMesh *a, SMesh *b) {
dbp("tris = %d", l.n);
}
DWORD SMesh::FirstIntersectionWith(Point2d mp) {
Vector gu = SS.GW.projRight, gv = SS.GW.projUp;
Vector gn = (gu.Cross(gv)).WithMagnitude(1);
Vector p0 = gu.ScaledBy(mp.x/SS.GW.scale).Plus(
gv.ScaledBy(mp.y/SS.GW.scale));
p0 = p0.Minus(SS.GW.offset);
double maxT = -1e12;
DWORD face = 0;
int i;
for(i = 0; i < l.n; i++) {
STriangle *tr = &(l.elem[i]);
Vector n = tr->Normal();
if(n.Dot(gn) < LENGTH_EPS) continue; // back-facing or on edge
if(tr->ContainsPointProjd(gn, p0)) {
// Let our line have the form r(t) = p0 + gn*t
double t = (n.Dot((tr->a).Minus(p0)))/(n.Dot(gn));
if(t > maxT) {
maxT = t;
face = tr->meta.face;
}
}
}
return face;
}
SBsp2 *SBsp2::Alloc(void) { return (SBsp2 *)AllocTemporary(sizeof(SBsp2)); }
SBsp3 *SBsp3::Alloc(void) { return (SBsp3 *)AllocTemporary(sizeof(SBsp3)); }

View File

@ -6,9 +6,12 @@ Vector STriangle::Normal(void) {
}
bool STriangle::ContainsPoint(Vector p) {
Vector n = Normal();
return ContainsPointProjd(n.WithMagnitude(1), p);
}
bool STriangle::ContainsPointProjd(Vector n, Vector p) {
Vector ab = b.Minus(a), bc = c.Minus(b), ca = a.Minus(c);
Vector n = ab.Cross(bc);
n = n.WithMagnitude(1);
Vector no_ab = n.Cross(ab);
if(no_ab.Dot(p) < no_ab.Dot(a) - LENGTH_EPS) return false;

View File

@ -114,6 +114,7 @@ public:
static STriangle From(STriMeta meta, Vector a, Vector b, Vector c);
Vector Normal(void);
bool ContainsPoint(Vector p);
bool ContainsPointProjd(Vector n, Vector p);
};
class SBsp2 {
@ -190,6 +191,8 @@ public:
void AddAgainstBsp(SMesh *srcm, SBsp3 *bsp3);
void MakeFromUnion(SMesh *a, SMesh *b);
void MakeFromDifference(SMesh *a, SMesh *b);
DWORD FirstIntersectionWith(Point2d mp);
};
// A linked list of triangles

View File

@ -10,6 +10,8 @@ const hRequest Request::HREQUEST_REFERENCE_XY = { 1 };
const hRequest Request::HREQUEST_REFERENCE_YZ = { 2 };
const hRequest Request::HREQUEST_REFERENCE_ZX = { 3 };
#define gs (SS.GW.gs)
void Group::AddParam(IdList<Param,hParam> *param, hParam hp, double v) {
Param pa;
memset(&pa, 0, sizeof(pa));
@ -30,7 +32,6 @@ void Group::MenuGroup(int id) {
}
SS.GW.GroupSelection();
#define gs (SS.GW.gs)
switch(id) {
case GraphicsWindow::MNU_GROUP_3D:
@ -214,7 +215,7 @@ void Group::Generate(IdList<Entity,hEntity> *entity,
break;
}
case EXTRUDE:
case EXTRUDE: {
AddParam(param, h.param(0), gn.x);
AddParam(param, h.param(1), gn.y);
AddParam(param, h.param(2), gn.z);
@ -224,10 +225,16 @@ void Group::Generate(IdList<Entity,hEntity> *entity,
} else if(subtype == TWO_SIDED) {
ai = -1; af = 1;
} else oops();
// Get some arbitrary point in the sketch, that will be used
// as a reference when defining top and bottom faces.
hEntity pt = { 0 };
for(i = 0; i < entity->n; i++) {
Entity *e = &(entity->elem[i]);
if(e->group.v != opA.v) continue;
if(e->IsPoint()) pt = e->h;
e->CalculateNumerical();
hEntity he = e->h; e = NULL;
// As soon as I call CopyEntity, e may become invalid! That
@ -242,7 +249,11 @@ void Group::Generate(IdList<Entity,hEntity> *entity,
true, false);
MakeExtrusionLines(he);
}
// Remapped versions of that arbitrary point will be used to
// provide points on the plane faces.
MakeExtrusionTopBottomFaces(pt);
break;
}
case TRANSLATE: {
// The translation vector
@ -386,16 +397,54 @@ hEntity Group::Remap(hEntity in, int copyNumber) {
void Group::MakeExtrusionLines(hEntity in) {
Entity *ep = SS.GetEntity(in);
if(!(ep->IsPoint())) return;
Entity en;
memset(&en, 0, sizeof(en));
en.point[0] = Remap(ep->h, REMAP_TOP);
en.point[1] = Remap(ep->h, REMAP_BOTTOM);
ZERO(&en);
if(ep->IsPoint()) {
// A point gets extruded to form a line segment
en.point[0] = Remap(ep->h, REMAP_TOP);
en.point[1] = Remap(ep->h, REMAP_BOTTOM);
en.group = h;
en.h = Remap(ep->h, REMAP_PT_TO_LINE);
en.type = Entity::LINE_SEGMENT;
SS.entity.Add(&en);
} else if(ep->type == Entity::LINE_SEGMENT) {
// A line gets extruded to form a plane face; an endpoint of the
// original line is a point in the plane, and the line is in the plane.
Vector a = SS.GetEntity(ep->point[0])->PointGetNum();
Vector b = SS.GetEntity(ep->point[1])->PointGetNum();
Vector ab = b.Minus(a);
en.param[0] = h.param(0);
en.param[1] = h.param(1);
en.param[2] = h.param(2);
en.numPoint = a;
en.numVector = ab;
en.group = h;
en.h = Remap(ep->h, REMAP_LINE_TO_FACE);
en.type = Entity::FACE_XPROD;
SS.entity.Add(&en);
}
}
void Group::MakeExtrusionTopBottomFaces(hEntity pt) {
if(pt.v == 0) return;
Group *src = SS.GetGroup(opA);
Vector n = src->poly.normal;
Entity en;
ZERO(&en);
en.type = Entity::FACE_NORMAL_PT;
en.group = h;
en.h = Remap(ep->h, 10);
en.type = Entity::LINE_SEGMENT;
// And then this line segment gets added
en.numNormal = Quaternion::From(0, n.x, n.y, n.z);
en.point[0] = Remap(pt, REMAP_TOP);
en.h = Remap(Entity::NO_ENTITY, REMAP_TOP);
SS.entity.Add(&en);
en.point[0] = Remap(pt, REMAP_BOTTOM);
en.h = Remap(Entity::NO_ENTITY, REMAP_BOTTOM);
SS.entity.Add(&en);
}
@ -498,6 +547,23 @@ void Group::CopyEntity(Entity *ep, int timesApplied, int remap,
en.numDistance = ep->actDistance;
break;
case Entity::FACE_NORMAL_PT:
case Entity::FACE_XPROD:
case Entity::FACE_N_ROT_TRANS:
if(asTrans || asAxisAngle) return;
en.type = Entity::FACE_N_ROT_TRANS;
en.param[0] = dx;
en.param[1] = dy;
en.param[2] = dz;
en.param[3] = qw;
en.param[4] = qvx;
en.param[5] = qvy;
en.param[6] = qvz;
en.numPoint = ep->numPoint;
en.numNormal = ep->numNormal;
break;
default:
oops();
}
@ -526,8 +592,8 @@ void Group::TagEdgesFromLineSegments(SEdgeList *el) {
for(j = 0; j < el->l.n; j++) {
SEdge *se = &(el->l.elem[j]);
if((p0.Equals(se->a) && p1.Equals(se->b))) se->tag = 1;
if((p0.Equals(se->b) && p1.Equals(se->a))) se->tag = 1;
if((p0.Equals(se->a) && p1.Equals(se->b))) se->tag = e->h.v;
if((p0.Equals(se->b) && p1.Equals(se->a))) se->tag = e->h.v;
}
}
}
@ -583,6 +649,7 @@ void Group::MakePolygons(void) {
STriMeta meta = { 0, color };
// Do the bottom; that has normal pointing opposite from translate
meta.face = Remap(Entity::NO_ENTITY, REMAP_BOTTOM).v;
for(i = 0; i < srcm.l.n; i++) {
STriangle *st = &(srcm.l.elem[i]);
Vector at = (st->a).Plus(tbot),
@ -595,6 +662,7 @@ void Group::MakePolygons(void) {
}
}
// And the top; that has the normal pointing the same dir as translate
meta.face = Remap(Entity::NO_ENTITY, REMAP_TOP).v;
for(i = 0; i < srcm.l.n; i++) {
STriangle *st = &(srcm.l.elem[i]);
Vector at = (st->a).Plus(ttop),
@ -618,6 +686,15 @@ void Group::MakePolygons(void) {
SEdge *edge = &(edges.l.elem[i]);
Vector abot = (edge->a).Plus(tbot), bbot = (edge->b).Plus(tbot);
Vector atop = (edge->a).Plus(ttop), btop = (edge->b).Plus(ttop);
// We tagged the edges that came from line segments; their
// triangles should be associated with that plane face.
if(edge->tag) {
hEntity hl = { edge->tag };
hEntity hf = Remap(hl, REMAP_LINE_TO_FACE);
meta.face = hf.v;
} else {
meta.face = 0;
}
if(flipBottom) {
outm.AddTriangle(meta, bbot, abot, atop);
outm.AddTriangle(meta, bbot, atop, btop);
@ -641,6 +718,11 @@ void Group::MakePolygons(void) {
for(int i = 0; i < impMesh.l.n; i++) {
STriangle st = impMesh.l.elem[i];
if(st.meta.face != 0) {
hEntity he = { st.meta.face };
st.meta.face = Remap(he, 0).v;
}
st.a = q.Rotate(st.a).Plus(offset);
st.b = q.Rotate(st.b).Plus(offset);
st.c = q.Rotate(st.c).Plus(offset);
@ -665,21 +747,30 @@ void Group::Draw(void) {
// Show this even if the group is not visible. It's already possible
// to show or hide just this with the "show solids" flag.
bool useModelColor;
int specColor;
if(type != EXTRUDE && type != IMPORTED) {
GLfloat mpf[] = { 0.1f, 0.1f, 0.1f, 1.0 };
glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, mpf);
useModelColor = false;
specColor = RGB(25, 25, 25); // force the color to something dim
} else {
useModelColor = true;
specColor = -1; // use the model color
}
// The back faces are drawn in red; should never seem them, since we
// draw closed shells, so that's a debugging aid.
GLfloat mpb[] = { 1.0f, 0.1f, 0.1f, 1.0 };
glMaterialfv(GL_BACK, GL_AMBIENT_AND_DIFFUSE, mpb);
// When we fill the mesh, we need to know which triangles are selected
// or hovered, in order to draw them differently.
DWORD mh = 0, ms1 = 0, ms2 = 0;
hEntity he = SS.GW.hover.entity;
if(he.v != 0 && SS.GetEntity(he)->IsFace()) {
mh = he.v;
}
SS.GW.GroupSelection();
if(gs.faces > 0) ms1 = gs.face[0].v;
if(gs.faces > 1) ms2 = gs.face[1].v;
glEnable(GL_LIGHTING);
if(SS.GW.showShaded) glxFillMesh(useModelColor, &mesh);
if(SS.GW.showShaded) glxFillMesh(specColor, &mesh, mh, ms1, ms2);
glDisable(GL_LIGHTING);
if(SS.GW.showMesh) glxDebugMesh(&mesh);

View File

@ -148,8 +148,11 @@ public:
static const int REMAP_LAST = 1000;
static const int REMAP_TOP = 1001;
static const int REMAP_BOTTOM = 1002;
static const int REMAP_PT_TO_LINE = 1003;
static const int REMAP_LINE_TO_FACE = 1004;
hEntity Remap(hEntity in, int copyNumber);
void MakeExtrusionLines(hEntity in);
void MakeExtrusionTopBottomFaces(hEntity pt);
void TagEdgesFromLineSegments(SEdgeList *sle);
void CopyEntity(Entity *ep, int timesApplied, int remap,
hParam dx, hParam dy, hParam dz,
@ -232,9 +235,9 @@ public:
static const int DISTANCE = 4000;
static const int DISTANCE_N_COPY = 4001;
static const int FACE_N_COPY = 5000;
static const int FACE_N_TRANS = 5001;
static const int FACE_N_XPROD = 5002;
static const int FACE_NORMAL_PT = 5000;
static const int FACE_XPROD = 5001;
static const int FACE_N_ROT_TRANS = 5002;
static const int WORKPLANE = 10000;
@ -258,6 +261,8 @@ public:
Vector numPoint;
Quaternion numNormal;
double numDistance;
// and a bit more state that the faces need
Vector numVector;
// All points/normals/distances have their numerical value; this is
// a convenience, to simplify the import/assembly code, so that the
@ -297,6 +302,11 @@ public:
Vector WorkplaneGetOffset(void);
Entity *Normal(void);
bool IsFace(void);
ExprVector FaceGetNormalExprs(void);
Vector FaceGetNormalNum(void);
ExprVector FaceGetPointExprs(void);
bool IsPoint(void);
// Applies for any of the point types
Vector PointGetNum(void);

View File

@ -104,7 +104,7 @@ void glxVertex3v(Vector u);
typedef void GLX_CALLBACK glxCallbackFptr(void);
void glxTesselatePolygon(GLUtesselator *gt, SPolygon *p);
void glxFillPolygon(SPolygon *p);
void glxFillMesh(bool useModelColor, SMesh *m);
void glxFillMesh(int color, SMesh *m, DWORD h, DWORD s1, DWORD s2);
void glxDebugPolygon(SPolygon *p);
void glxDebugEdgeList(SEdgeList *l);
void glxDebugMesh(SMesh *m);

View File

@ -32,6 +32,15 @@ Quaternion Quaternion::From(double w, double vx, double vy, double vz) {
return q;
}
Quaternion Quaternion::From(hParam w, hParam vx, hParam vy, hParam vz) {
Quaternion q;
q.w = SS.GetParam(w )->val;
q.vx = SS.GetParam(vx)->val;
q.vy = SS.GetParam(vy)->val;
q.vz = SS.GetParam(vz)->val;
return q;
}
Quaternion Quaternion::From(Vector u, Vector v)
{
Vector n = u.Cross(v);