solvespace/entity.cpp
Jonathan Westhues 853c6cb59c A big change, to add a concept of normals. These are "oriented
vectors", represented by unit quaternions. This permits me to add
circles, where the normal defines the plane of the circle.

Still many things painful. The interface for editing normals is not
so intuitive, and it's not yet clear how I would e.g. export a
circle entity and recreate it properly, since that entity has a
param not associated with a normal or point.

And the transformed points/normals do not yet support rotations.
That will be necessary soon.

[git-p4: depot-paths = "//depot/solvespace/": change = 1705]
2008-05-04 22:18:01 -08:00

508 lines
15 KiB
C++

#include "solvespace.h"
char *Entity::DescriptionString(void) {
Request *r = SS.GetRequest(h.request());
return r->DescriptionString();
}
void Entity::WorkplaneGetBasisVectors(Vector *u, Vector *v) {
Quaternion quat = SS.GetEntity(normal)->NormalGetNum();
*u = quat.RotationU();
*v = quat.RotationV();
}
Vector Entity::WorkplaneGetNormalVector(void) {
Vector u, v;
WorkplaneGetBasisVectors(&u, &v);
return u.Cross(v);
}
void Entity::WorkplaneGetBasisExprs(ExprVector *u, ExprVector *v) {
ExprQuaternion q = SS.GetEntity(normal)->NormalGetExprs();
*u = q.RotationU();
*v = q.RotationV();
}
ExprVector Entity::WorkplaneGetOffsetExprs(void) {
return SS.GetEntity(point[0])->PointGetExprs();
}
bool Entity::HasPlane(void) {
switch(type) {
case WORKPLANE:
return true;
default:
return false;
}
}
void Entity::PlaneGetExprs(ExprVector *n, Expr **dn) {
if(type == WORKPLANE) {
Expr *a = Expr::FromParam(param[0]);
Expr *b = Expr::FromParam(param[1]);
Expr *c = Expr::FromParam(param[2]);
Expr *d = Expr::FromParam(param[3]);
Expr *two = Expr::FromConstant(2);
// Convert the quaternion to our plane's normal vector.
n->x = two->Times(a->Times(c));
n->x = (n->x)->Plus (two->Times(b->Times(d)));
n->y = two->Times(c->Times(d));
n->y = (n->y)->Minus(two->Times(a->Times(b)));
n->z = a->Square();
n->z = (n->z)->Minus(b->Square());
n->z = (n->z)->Minus(c->Square());
n->z = (n->z)->Plus (d->Square());
ExprVector p0 = SS.GetEntity(point[0])->PointGetExprs();
// The plane is n dot (p - p0) = 0, or
// n dot p - n dot p0 = 0
// so dn = n dot p0
*dn = p0.Dot(*n);
} else {
oops();
}
}
bool Entity::IsPoint(void) {
switch(type) {
case POINT_IN_3D:
case POINT_IN_2D:
case POINT_XFRMD: return true;
default: return false;
}
}
bool Entity::IsNormal(void) {
switch(type) {
case NORMAL_IN_3D:
case NORMAL_IN_2D:
case NORMAL_XFRMD: return true;
default: return false;
}
}
Quaternion Entity::NormalGetNum(void) {
Quaternion q;
switch(type) {
case NORMAL_IN_3D:
q.w = SS.GetParam(param[0])->val;
q.vx = SS.GetParam(param[1])->val;
q.vy = SS.GetParam(param[2])->val;
q.vz = SS.GetParam(param[3])->val;
break;
case NORMAL_IN_2D: {
Entity *wrkpl = SS.GetEntity(workplane);
Entity *norm = SS.GetEntity(wrkpl->normal);
q = norm->NormalGetNum();
break;
}
case NORMAL_XFRMD:
q = numNormal;
break;
default: oops();
}
return q;
}
void Entity::NormalForceTo(Quaternion q) {
switch(type) {
case NORMAL_IN_3D:
SS.GetParam(param[0])->val = q.w;
SS.GetParam(param[1])->val = q.vx;
SS.GetParam(param[2])->val = q.vy;
SS.GetParam(param[3])->val = q.vz;
break;
case NORMAL_IN_2D:
case NORMAL_XFRMD:
// There's absolutely nothing to do; these are locked.
break;
default: oops();
}
}
ExprQuaternion Entity::NormalGetExprs(void) {
ExprQuaternion q;
switch(type) {
case NORMAL_IN_3D:
q.w = Expr::FromParam(param[0]);
q.vx = Expr::FromParam(param[1]);
q.vy = Expr::FromParam(param[2]);
q.vz = Expr::FromParam(param[3]);
break;
case NORMAL_IN_2D: {
Entity *wrkpl = SS.GetEntity(workplane);
Entity *norm = SS.GetEntity(wrkpl->normal);
q = norm->NormalGetExprs();
break;
}
case NORMAL_XFRMD:
q.w = Expr::FromConstant(numNormal.w);
q.vx = Expr::FromConstant(numNormal.vx);
q.vy = Expr::FromConstant(numNormal.vy);
q.vz = Expr::FromConstant(numNormal.vz);
break;
default: oops();
}
return q;
}
bool Entity::PointIsFromReferences(void) {
return h.request().IsFromReferences();
}
void Entity::PointForceTo(Vector p) {
switch(type) {
case POINT_IN_3D:
SS.GetParam(param[0])->val = p.x;
SS.GetParam(param[1])->val = p.y;
SS.GetParam(param[2])->val = p.z;
break;
case POINT_IN_2D: {
Entity *c = SS.GetEntity(workplane);
Vector u, v;
c->WorkplaneGetBasisVectors(&u, &v);
SS.GetParam(param[0])->val = p.Dot(u);
SS.GetParam(param[1])->val = p.Dot(v);
break;
}
case POINT_XFRMD: {
Vector trans = p.Minus(numPoint);
SS.GetParam(param[0])->val = trans.x;
SS.GetParam(param[1])->val = trans.y;
SS.GetParam(param[2])->val = trans.z;
break;
}
default: oops();
}
}
Vector Entity::PointGetNum(void) {
Vector p;
switch(type) {
case POINT_IN_3D:
p.x = SS.GetParam(param[0])->val;
p.y = SS.GetParam(param[1])->val;
p.z = SS.GetParam(param[2])->val;
break;
case POINT_IN_2D: {
Entity *c = SS.GetEntity(workplane);
Vector u, v;
c->WorkplaneGetBasisVectors(&u, &v);
p = u.ScaledBy(SS.GetParam(param[0])->val);
p = p.Plus(v.ScaledBy(SS.GetParam(param[1])->val));
break;
}
case POINT_XFRMD: {
p = numPoint;
p.x += SS.GetParam(param[0])->val;
p.y += SS.GetParam(param[1])->val;
p.z += SS.GetParam(param[2])->val;
break;
}
default: oops();
}
return p;
}
ExprVector Entity::PointGetExprs(void) {
ExprVector r;
switch(type) {
case POINT_IN_3D:
r.x = Expr::FromParam(param[0]);
r.y = Expr::FromParam(param[1]);
r.z = Expr::FromParam(param[2]);
break;
case POINT_IN_2D: {
Entity *c = SS.GetEntity(workplane);
ExprVector u, v;
c->WorkplaneGetBasisExprs(&u, &v);
r = u.ScaledBy(Expr::FromParam(param[0]));
r = r.Plus(v.ScaledBy(Expr::FromParam(param[1])));
break;
}
case POINT_XFRMD: {
ExprVector orig = {
Expr::FromConstant(numPoint.x),
Expr::FromConstant(numPoint.y),
Expr::FromConstant(numPoint.z) };
ExprVector trans;
trans.x = Expr::FromParam(param[0]);
trans.y = Expr::FromParam(param[1]);
trans.z = Expr::FromParam(param[2]);
r = orig.Plus(trans);
break;
}
default: oops();
}
return r;
}
void Entity::PointGetExprsInWorkplane(hEntity wrkpl, Expr **u, Expr **v) {
if(type == POINT_IN_2D && workplane.v == wrkpl.v) {
// They want our coordinates in the form that we've written them,
// very nice.
*u = Expr::FromParam(param[0]);
*v = Expr::FromParam(param[1]);
} else {
// Get the offset and basis vectors for this weird exotic csys.
Entity *w = SS.GetEntity(wrkpl);
ExprVector wp = w->WorkplaneGetOffsetExprs();
ExprVector wu, wv;
w->WorkplaneGetBasisExprs(&wu, &wv);
// Get our coordinates in three-space, and project them into that
// coordinate system.
ExprVector ev = PointGetExprs();
ev = ev.Minus(wp);
*u = ev.Dot(wu);
*v = ev.Dot(wv);
}
}
void Entity::LineDrawOrGetDistance(Vector a, Vector b) {
if(dogd.drawing) {
// This fudge guarantees that the line will get drawn in front of
// anything else at the "same" depth in the z-buffer, so that it
// goes in front of the shaded stuff.
Vector n = SS.GW.projRight.Cross(SS.GW.projUp);
n = n.WithMagnitude(3/SS.GW.scale);
glBegin(GL_LINE_STRIP);
glxVertex3v(a.Plus(n));
glxVertex3v(b.Plus(n));
glEnd();
} else {
Point2d ap = SS.GW.ProjectPoint(a);
Point2d bp = SS.GW.ProjectPoint(b);
double d = dogd.mp.DistanceToLine(ap, bp.Minus(ap), true);
dogd.dmin = min(dogd.dmin, d);
}
}
void Entity::LineDrawOrGetDistanceOrEdge(Vector a, Vector b) {
LineDrawOrGetDistance(a, b);
if(dogd.edges) {
SEdge edge;
edge.a = a; edge.b = b;
dogd.edges->l.Add(&edge);
}
}
void Entity::Draw(int order) {
dogd.drawing = true;
dogd.edges = NULL;
DrawOrGetDistance(order);
}
void Entity::GenerateEdges(SEdgeList *el) {
dogd.drawing = false;
dogd.edges = el;
DrawOrGetDistance(-1);
dogd.edges = NULL;
}
double Entity::GetDistance(Point2d mp) {
dogd.drawing = false;
dogd.edges = NULL;
dogd.mp = mp;
dogd.dmin = 1e12;
DrawOrGetDistance(-1);
return dogd.dmin;
}
void Entity::DrawOrGetDistance(int order) {
Group *g = SS.GetGroup(group);
// If an entity is invisible, then it doesn't get shown, and it doesn't
// contribute a distance for the selection, but it still generates edges.
if(!(g->visible) && !dogd.edges) return;
glxColor3d(1, 1, 1);
switch(type) {
case POINT_XFRMD:
case POINT_IN_3D:
case POINT_IN_2D: {
if(order >= 0 && order != 2) break;
if(!SS.GW.showPoints) break;
if(h.isFromRequest()) {
Entity *isfor = SS.GetEntity(h.request().entity(0));
if(!SS.GW.showWorkplanes && isfor->type == Entity::WORKPLANE) {
break;
}
}
Vector v = PointGetNum();
if(dogd.drawing) {
double s = 3;
Vector r = SS.GW.projRight.ScaledBy(s/SS.GW.scale);
Vector d = SS.GW.projUp.ScaledBy(s/SS.GW.scale);
// The usual fudge, to make this appear in front.
Vector gn = SS.GW.projRight.Cross(SS.GW.projUp);
v = v.Plus(gn.ScaledBy(4/SS.GW.scale));
glxColor3d(0, 0.8, 0);
glBegin(GL_QUADS);
glxVertex3v(v.Plus (r).Plus (d));
glxVertex3v(v.Plus (r).Minus(d));
glxVertex3v(v.Minus(r).Minus(d));
glxVertex3v(v.Minus(r).Plus (d));
glEnd();
} else {
Point2d pp = SS.GW.ProjectPoint(v);
// Make a free point slightly easier to select, so that with
// coincident points, we select the free one.
dogd.dmin = pp.DistanceTo(dogd.mp) - 4;
}
break;
}
case NORMAL_IN_3D:
case NORMAL_IN_2D:
case NORMAL_XFRMD: {
if(order >= 0 && order != 2) break;
if(!SS.GW.showNormals) break;
hRequest hr = h.request();
double f = 0.5;
if(hr.v == Request::HREQUEST_REFERENCE_XY.v) {
glxColor3d(0, 0, f);
} else if(hr.v == Request::HREQUEST_REFERENCE_YZ.v) {
glxColor3d(f, 0, 0);
} else if(hr.v == Request::HREQUEST_REFERENCE_ZX.v) {
glxColor3d(0, f, 0);
} else {
glxColor3d(0, 0.4, 0.4);
}
Quaternion q = NormalGetNum();
Vector tail = SS.GetEntity(point[0])->PointGetNum();
Vector v = (q.RotationN()).WithMagnitude(50/SS.GW.scale);
Vector tip = tail.Plus(v);
LineDrawOrGetDistance(tail, tip);
v = v.WithMagnitude(12);
Vector axis = q.RotationV();
LineDrawOrGetDistance(tip, tip.Minus(v.RotatedAbout(axis, 0.6)));
LineDrawOrGetDistance(tip, tip.Minus(v.RotatedAbout(axis, -0.6)));
break;
}
case WORKPLANE: {
if(order >= 0 && order != 0) break;
if(!SS.GW.showWorkplanes) break;
Vector p;
p = SS.GetEntity(point[0])->PointGetNum();
Vector u, v;
WorkplaneGetBasisVectors(&u, &v);
double s = (min(SS.GW.width, SS.GW.height))*0.4/SS.GW.scale;
Vector us = u.ScaledBy(s);
Vector vs = v.ScaledBy(s);
Vector pp = p.Plus (us).Plus (vs);
Vector pm = p.Plus (us).Minus(vs);
Vector mm = p.Minus(us).Minus(vs);
Vector mp = p.Minus(us).Plus (vs);
glxColor3d(0, 0.3, 0.3);
LineDrawOrGetDistance(pp, pm);
LineDrawOrGetDistance(pm, mm);
LineDrawOrGetDistance(mm, mp);
LineDrawOrGetDistance(mp, pp);
if(dogd.drawing) {
glPushMatrix();
glxTranslatev(mm);
glxOntoWorkplane(u, v);
glxWriteText(DescriptionString());
glPopMatrix();
} else {
// If a line lies in a plane, then select the line, not
// the plane.
dogd.dmin += 3;
}
break;
}
case LINE_SEGMENT: {
if(order >= 0 && order != 1) break;
Vector a = SS.GetEntity(point[0])->PointGetNum();
Vector b = SS.GetEntity(point[1])->PointGetNum();
LineDrawOrGetDistanceOrEdge(a, b);
break;
}
case CUBIC: {
if(order >= 0 && order != 1) break;
Vector p0 = SS.GetEntity(point[0])->PointGetNum();
Vector p1 = SS.GetEntity(point[1])->PointGetNum();
Vector p2 = SS.GetEntity(point[2])->PointGetNum();
Vector p3 = SS.GetEntity(point[3])->PointGetNum();
int i, n = 20;
Vector prev = p0;
for(i = 1; i <= n; i++) {
double t = ((double)i)/n;
Vector p =
(p0.ScaledBy((1 - t)*(1 - t)*(1 - t))).Plus(
(p1.ScaledBy(3*t*(1 - t)*(1 - t))).Plus(
(p2.ScaledBy(3*t*t*(1 - t))).Plus(
(p3.ScaledBy(t*t*t)))));
LineDrawOrGetDistanceOrEdge(prev, p);
prev = p;
}
break;
}
case CIRCLE: {
if(order >= 0 && order != 1) break;
Quaternion q = SS.GetEntity(normal)->NormalGetNum();
double r = SS.GetParam(param[0])->val;
Vector center = SS.GetEntity(point[0])->PointGetNum();
Vector u = q.RotationU(), v = q.RotationV();
int i, c = 20;
Vector prev = u.ScaledBy(r).Plus(center);
for(i = 0; i <= c; i++) {
double phi = (2*PI*i)/c;
Vector p = (u.ScaledBy(r*cos(phi))).Plus(
v.ScaledBy(r*sin(phi)));
p = p.Plus(center);
LineDrawOrGetDistanceOrEdge(prev, p);
prev = p;
}
break;
}
default:
oops();
}
}