void Constraint::DoEqualLenTicks(Vector a, Vector b, Vector gn) { Vector m = (a.ScaledBy(1.0/3)).Plus(b.ScaledBy(2.0/3)); Vector ab = a.Minus(b); Vector n = (gn.Cross(ab)).WithMagnitude(10/SS.GW.scale); LineDrawOrGetDistance(m.Minus(n), m.Plus(n)); }
void ssglFatLine(Vector a, Vector b, double width) { // The half-width of the line we're drawing. double hw = width / 2; Vector ab = b.Minus(a); Vector gn = (SS.GW.projRight).Cross(SS.GW.projUp); Vector abn = (ab.Cross(gn)).WithMagnitude(1); abn = abn.Minus(gn.ScaledBy(gn.Dot(abn))); // So now abn is normal to the projection of ab into the screen, so the // line will always have constant thickness as the view is rotated. abn = abn.WithMagnitude(hw); ab = gn.Cross(abn); ab = ab. WithMagnitude(hw); // The body of a line is a quad glBegin(GL_QUADS); ssglVertex3v(a.Minus(abn)); ssglVertex3v(b.Minus(abn)); ssglVertex3v(b.Plus (abn)); ssglVertex3v(a.Plus (abn)); glEnd(); // And the line has two semi-circular end caps. FatLineEndcap(a, ab, abn); FatLineEndcap(b, ab.ScaledBy(-1), abn); }
void SSurface::TangentsAt(double u, double v, Vector *tu, Vector *tv) { Vector num = Vector::From(0, 0, 0), num_u = Vector::From(0, 0, 0), num_v = Vector::From(0, 0, 0); double den = 0, den_u = 0, den_v = 0; int i, j; for(i = 0; i <= degm; i++) { for(j = 0; j <= degn; j++) { double Bi = Bernstein(i, degm, u), Bj = Bernstein(j, degn, v), Bip = BernsteinDerivative(i, degm, u), Bjp = BernsteinDerivative(j, degn, v); num = num.Plus(ctrl[i][j].ScaledBy(Bi*Bj*weight[i][j])); den += weight[i][j]*Bi*Bj; num_u = num_u.Plus(ctrl[i][j].ScaledBy(Bip*Bj*weight[i][j])); den_u += weight[i][j]*Bip*Bj; num_v = num_v.Plus(ctrl[i][j].ScaledBy(Bi*Bjp*weight[i][j])); den_v += weight[i][j]*Bi*Bjp; } } // quotient rule; f(t) = n(t)/d(t), so f' = (n'*d - n*d')/(d^2) *tu = ((num_u.ScaledBy(den)).Minus(num.ScaledBy(den_u))); *tu = tu->ScaledBy(1.0/(den*den)); *tv = ((num_v.ScaledBy(den)).Minus(num.ScaledBy(den_v))); *tv = tv->ScaledBy(1.0/(den*den)); }
bool Vector::BoundingBoxIntersectsLine(Vector amax, Vector amin, Vector p0, Vector p1, bool segment) { Vector dp = p1.Minus(p0); double lp = dp.Magnitude(); dp = dp.ScaledBy(1.0/lp); int i, a; for(i = 0; i < 3; i++) { int j = WRAP(i+1, 3), k = WRAP(i+2, 3); if(lp*fabs(dp.Element(i)) < LENGTH_EPS) continue; // parallel to plane for(a = 0; a < 2; a++) { double d = (a == 0) ? amax.Element(i) : amin.Element(i); // n dot (p0 + t*dp) = d // (n dot p0) + t * (n dot dp) = d double t = (d - p0.Element(i)) / dp.Element(i); Vector p = p0.Plus(dp.ScaledBy(t)); if(segment && (t < -LENGTH_EPS || t > (lp+LENGTH_EPS))) continue; if(p.Element(j) > amax.Element(j) + LENGTH_EPS) continue; if(p.Element(k) > amax.Element(k) + LENGTH_EPS) continue; if(p.Element(j) < amin.Element(j) - LENGTH_EPS) continue; if(p.Element(k) < amin.Element(k) - LENGTH_EPS) continue; return true; } } return false; }
Vector SBsp2::IntersectionWith(Vector a, Vector b) { double da = a.Dot(no) - d; double db = b.Dot(no) - d; if(da*db > 0) oops(); double dab = (db - da); return (a.ScaledBy(db/dab)).Plus(b.ScaledBy(-da/dab)); }
Vector Vector::ProjectVectorInto(hEntity wrkpl) { EntityBase *w = SK.GetEntity(wrkpl); Vector u = w->Normal()->NormalU(); Vector v = w->Normal()->NormalV(); double up = this->Dot(u); double vp = this->Dot(v); return (u.ScaledBy(up)).Plus(v.ScaledBy(vp)); }
Vector Vector::AtIntersectionOfPlanes(Vector n1, double d1, Vector n2, double d2) { double det = (n1.Dot(n1))*(n2.Dot(n2)) - (n1.Dot(n2))*(n1.Dot(n2)); double c1 = (d1*n2.Dot(n2) - d2*n1.Dot(n2))/det; double c2 = (d2*n1.Dot(n1) - d1*n1.Dot(n2))/det; return (n1.ScaledBy(c1)).Plus(n2.ScaledBy(c2)); }
bool TextWindow::EditControlDoneForView(const char *s) { switch(edit.meaning) { case Edit::VIEW_SCALE: { Expr *e = Expr::From(s, /*popUpError=*/true); if(e) { double v = e->Eval() / SS.MmPerUnit(); if(v > LENGTH_EPS) { SS.GW.scale = v; } else { Error(_("Scale cannot be zero or negative.")); } } break; } case Edit::VIEW_ORIGIN: { Vector pt; if(sscanf(s, "%lf, %lf, %lf", &pt.x, &pt.y, &pt.z) == 3) { pt = pt.ScaledBy(SS.MmPerUnit()); SS.GW.offset = pt.ScaledBy(-1); } else { Error(_("Bad format: specify x, y, z")); } break; } case Edit::VIEW_PROJ_RIGHT: case Edit::VIEW_PROJ_UP: { Vector pt; if(sscanf(s, "%lf, %lf, %lf", &pt.x, &pt.y, &pt.z) != 3) { Error(_("Bad format: specify x, y, z")); break; } if(edit.meaning == Edit::VIEW_PROJ_RIGHT) { SS.GW.projRight = pt; SS.GW.NormalizeProjectionVectors(); edit.meaning = Edit::VIEW_PROJ_UP; HideEditControl(); ShowEditControl(10, ssprintf("%.3f, %.3f, %.3f", CO(SS.GW.projUp)), editControl.halfRow + 2); edit.showAgain = true; } else { SS.GW.projUp = pt; SS.GW.NormalizeProjectionVectors(); } break; } default: return false; } return true; }
Quaternion Quaternion::Times(Quaternion b) { double sa = w, sb = b.w; Vector va = { vx, vy, vz }; Vector vb = { b.vx, b.vy, b.vz }; Quaternion r; r.w = sa*sb - va.Dot(vb); Vector vr = vb.ScaledBy(sa).Plus( va.ScaledBy(sb).Plus( va.Cross(vb))); r.vx = vr.x; r.vy = vr.y; r.vz = vr.z; return r; }
Vector EntityBase::PointGetNum(void) { Vector p; switch(type) { case POINT_IN_3D: p = Vector::From(param[0], param[1], param[2]); break; case POINT_IN_2D: { EntityBase *c = SK.GetEntity(workplane); Vector u = c->Normal()->NormalU(); Vector v = c->Normal()->NormalV(); p = u.ScaledBy(SK.GetParam(param[0])->val); p = p.Plus(v.ScaledBy(SK.GetParam(param[1])->val)); p = p.Plus(c->WorkplaneGetOffset()); break; } case POINT_N_TRANS: { Vector trans = Vector::From(param[0], param[1], param[2]); p = numPoint.Plus(trans.ScaledBy(timesApplied)); break; } case POINT_N_ROT_TRANS: { Vector offset = Vector::From(param[0], param[1], param[2]); Quaternion q = PointGetQuaternion(); p = q.Rotate(numPoint); p = p.Plus(offset); break; } case POINT_N_ROT_AA: { Vector offset = Vector::From(param[0], param[1], param[2]); Quaternion q = PointGetQuaternion(); p = numPoint.Minus(offset); p = q.Rotate(p); p = p.Plus(offset); break; } case POINT_N_COPY: p = numPoint; break; default: oops(); } return p; }
Vector Vector::AtIntersectionOfLines(Vector a0, Vector a1, Vector b0, Vector b1, bool *skew, double *parama, double *paramb) { Vector da = a1.Minus(a0), db = b1.Minus(b0); double pa, pb; Vector::ClosestPointBetweenLines(a0, da, b0, db, &pa, &pb); if(parama) *parama = pa; if(paramb) *paramb = pb; // And from either of those, we get the intersection point. Vector pi = a0.Plus(da.ScaledBy(pa)); if(skew) { // Check if the intersection points on each line are actually // coincident... if(pi.Equals(b0.Plus(db.ScaledBy(pb)))) { *skew = false; } else { *skew = true; } } return pi; }
void ConstraintBase::ModifyToSatisfy() { if(type == Type::ANGLE) { Vector a = SK.GetEntity(entityA)->VectorGetNum(); Vector b = SK.GetEntity(entityB)->VectorGetNum(); if(other) a = a.ScaledBy(-1); if(workplane.v != EntityBase::FREE_IN_3D.v) { a = a.ProjectVectorInto(workplane); b = b.ProjectVectorInto(workplane); } double c = (a.Dot(b))/(a.Magnitude() * b.Magnitude()); valA = acos(c)*180/PI; } else { // We'll fix these ones up by looking at their symbolic equation; // that means no extra work. IdList<Equation,hEquation> l = {}; // Generate the equations even if this is a reference dimension GenerateReal(&l); ssassert(l.n == 1, "Expected constraint to generate a single equation"); // These equations are written in the form f(...) - d = 0, where // d is the value of the valA. valA += (l.elem[0].e)->Eval(); l.Clear(); } }
void ConstraintBase::ModifyToSatisfy(void) { if(type == ANGLE) { Vector a = SK.GetEntity(entityA)->VectorGetNum(); Vector b = SK.GetEntity(entityB)->VectorGetNum(); if(other) a = a.ScaledBy(-1); if(workplane.v != EntityBase::FREE_IN_3D.v) { a = a.ProjectVectorInto(workplane); b = b.ProjectVectorInto(workplane); } double c = (a.Dot(b))/(a.Magnitude() * b.Magnitude()); valA = acos(c)*180/PI; } else { // We'll fix these ones up by looking at their symbolic equation; // that means no extra work. IdList<Equation,hEquation> l; // An uninit IdList could lead us to free some random address, bad. ZERO(&l); // Generate the equations even if this is a reference dimension GenerateReal(&l); if(l.n != 1) oops(); // These equations are written in the form f(...) - d = 0, where // d is the value of the valA. valA += (l.elem[0].e)->Eval(); l.Clear(); } }
Quaternion Quaternion::Mirror(void) { Vector u = RotationU(), v = RotationV(); u = u.ScaledBy(-1); v = v.ScaledBy(-1); return Quaternion::From(u, v); }
Vector GraphicsWindow::VectorFromProjs(Vector rightUpForward) { Vector n = projRight.Cross(projUp); Vector r = (projRight.ScaledBy(rightUpForward.x)); r = r.Plus(projUp.ScaledBy(rightUpForward.y)); r = r.Plus(n.ScaledBy(rightUpForward.z)); return r; }
//----------------------------------------------------------------------------- // Draw a line with arrows on both ends, and possibly a gap in the middle for // the dimension. We will use these for most length dimensions. The length // being dimensioned is from A to B; but those points get extended perpendicular // to the line AB, until the line between the extensions crosses ref (the // center of the label). //----------------------------------------------------------------------------- void Constraint::DoLineWithArrows(Vector ref, Vector a, Vector b, bool onlyOneExt) { Vector gn = (SS.GW.projRight.Cross(SS.GW.projUp)).WithMagnitude(1); double pixels = 1.0 / SS.GW.scale; Vector ab = a.Minus(b); Vector ar = a.Minus(ref); // Normal to a plane containing the line and the label origin. Vector n = ab.Cross(ar); // Within that plane, and normal to the line AB; so that's our extension // line. Vector out = ab.Cross(n).WithMagnitude(1); out = out.ScaledBy(-out.Dot(ar)); Vector ae = a.Plus(out), be = b.Plus(out); // Extension lines extend 10 pixels beyond where the arrows get // drawn (which is at the same offset perpendicular from AB as the // label). LineDrawOrGetDistance(a, ae.Plus(out.WithMagnitude(10*pixels))); if(!onlyOneExt) { LineDrawOrGetDistance(b, be.Plus(out.WithMagnitude(10*pixels))); } int within = DoLineTrimmedAgainstBox(ref, ae, be); // Arrow heads are 13 pixels long, with an 18 degree half-angle. double theta = 18*PI/180; Vector arrow = (be.Minus(ae)).WithMagnitude(13*pixels); if(within != 0) { arrow = arrow.ScaledBy(-1); Vector seg = (be.Minus(ae)).WithMagnitude(18*pixels); if(within < 0) LineDrawOrGetDistance(ae, ae.Minus(seg)); if(within > 0) LineDrawOrGetDistance(be, be.Plus(seg)); } LineDrawOrGetDistance(ae, ae.Plus(arrow.RotatedAbout(n, theta))); LineDrawOrGetDistance(ae, ae.Plus(arrow.RotatedAbout(n, -theta))); arrow = arrow.ScaledBy(-1); LineDrawOrGetDistance(be, be.Plus(arrow.RotatedAbout(n, theta))); LineDrawOrGetDistance(be, be.Plus(arrow.RotatedAbout(n, -theta))); }
void ssglWriteTextRefCenter(const char *str, double h, Vector t, Vector u, Vector v, ssglLineFn *fn, void *fndata) { u = u.WithMagnitude(1); v = v.WithMagnitude(1); double scale = FONT_SCALE(h)/SS.GW.scale; double fh = ssglStrHeight(h); double fw = ssglStrWidth(str, h); t = t.Plus(u.ScaledBy(-fw/2)); t = t.Plus(v.ScaledBy(-fh/2)); // Undo the (+5, +5) offset that ssglWriteText applies. t = t.Plus(u.ScaledBy(-5*scale)); t = t.Plus(v.ScaledBy(-5*scale)); ssglWriteText(str, h, t, u, v, fn, fndata); }
Vector GraphicsWindow::ParametricCurve::TangentAt(double t) { if(isLine) { return p1.Minus(p0); } else { double theta = theta0 + dtheta*t; Vector t = u.ScaledBy(-r*sin(theta)).Plus(v.ScaledBy(r*cos(theta))); t = t.ScaledBy(dtheta); return t; } }
void SSurface::BlendRowOrCol(bool row, int this_ij, SSurface *a, int a_ij, SSurface *b, int b_ij) { if(row) { int j; for(j = 0; j <= degn; j++) { Vector c = (a->ctrl [a_ij][j]).Plus(b->ctrl [b_ij][j]); double w = (a->weight[a_ij][j] + b->weight[b_ij][j]); ctrl [this_ij][j] = c.ScaledBy(0.5); weight[this_ij][j] = w / 2; } } else { int i; for(i = 0; i <= degm; i++) { Vector c = (a->ctrl [i][a_ij]).Plus(b->ctrl [i][b_ij]); double w = (a->weight[i][a_ij] + b->weight[i][b_ij]); ctrl [i][this_ij] = c.ScaledBy(0.5); weight[i][this_ij] = w / 2; } } }
Vector Vector::InPerspective(Vector u, Vector v, Vector n, Vector origin, double cameraTan) { Vector r = this->Minus(origin); r = r.DotInToCsys(u, v, n); // yes, minus; we are assuming a csys where u cross v equals n, backwards // from the display stuff double w = (1 - r.z*cameraTan); r = r.ScaledBy(1/w); return r; }
void Constraint::DoLabel(Vector ref, Vector *labelPos, Vector gr, Vector gu) { double th; if(type == COMMENT) { th = Style::TextHeight(disp.style); } else { th = DEFAULT_TEXT_HEIGHT; } char *s = Label(); double swidth = ssglStrWidth(s, th), sheight = ssglStrHeight(th); // By default, the reference is from the center; but the style could // specify otherwise if one is present, and it could also specify a // rotation. if(type == COMMENT && disp.style.v) { Style *st = Style::Get(disp.style); // rotation first double rads = st->textAngle*PI/180; double c = cos(rads), s = sin(rads); Vector pr = gr, pu = gu; gr = pr.ScaledBy( c).Plus(pu.ScaledBy(s)); gu = pr.ScaledBy(-s).Plus(pu.ScaledBy(c)); // then origin int o = st->textOrigin; if(o & Style::ORIGIN_LEFT) ref = ref.Plus(gr.WithMagnitude(swidth/2)); if(o & Style::ORIGIN_RIGHT) ref = ref.Minus(gr.WithMagnitude(swidth/2)); if(o & Style::ORIGIN_BOT) ref = ref.Plus(gu.WithMagnitude(sheight/2)); if(o & Style::ORIGIN_TOP) ref = ref.Minus(gu.WithMagnitude(sheight/2)); } if(labelPos) { // labelPos is from the top left corner (for the text box used to // edit things), but ref is from the center. *labelPos = ref.Minus(gr.WithMagnitude(swidth/2)).Minus( gu.WithMagnitude(sheight/2)); } if(dogd.drawing) { ssglWriteTextRefCenter(s, th, ref, gr, gu, LineCallback, this); } else { double l = swidth/2 - sheight/2; l = max(l, 5/SS.GW.scale); Point2d a = SS.GW.ProjectPoint(ref.Minus(gr.WithMagnitude(l))); Point2d b = SS.GW.ProjectPoint(ref.Plus (gr.WithMagnitude(l))); double d = dogd.mp.DistanceToLine(a, b.Minus(a), true); dogd.dmin = min(dogd.dmin, d - (th / 2)); dogd.refp = ref; } }
Vector SBezier::PointAt(double t) { Vector pt = Vector::From(0, 0, 0); double d = 0; int i; for(i = 0; i <= deg; i++) { double B = Bernstein(i, deg, t); pt = pt.Plus(ctrl[i].ScaledBy(B*weight[i])); d += weight[i]*B; } pt = pt.ScaledBy(1.0/d); return pt; }
void ssglWriteText(const char *str, double h, Vector t, Vector u, Vector v, ssglLineFn *fn, void *fndata) { if(!fn) fn = LineDrawCallback; u = u.WithMagnitude(1); v = v.WithMagnitude(1); double scale = FONT_SCALE(h)/SS.GW.scale; int xo = 5; int yo = 5; for(; *str; str++) { int c = *str; if(c < 32 || c > 126) c = 32; c -= 32; int j; Vector prevp = Vector::From(VERY_POSITIVE, 0, 0); for(j = 0; j < Font[c].points; j++) { int x = Font[c].coord[j*2]; int y = Font[c].coord[j*2+1]; if(x == PEN_UP && y == PEN_UP) { prevp.x = VERY_POSITIVE; } else { Vector p = t; p = p.Plus(u.ScaledBy((xo + x)*scale)); p = p.Plus(v.ScaledBy((yo + y)*scale)); if(EXACT(prevp.x != VERY_POSITIVE)) { fn(fndata, prevp, p); } prevp = p; } } xo += Font[c].width; } }
Vector SBezier::TangentAt(double t) { Vector pt = Vector::From(0, 0, 0), pt_p = Vector::From(0, 0, 0); double d = 0, d_p = 0; int i; for(i = 0; i <= deg; i++) { double B = Bernstein(i, deg, t), Bp = BernsteinDerivative(i, deg, t); pt = pt.Plus(ctrl[i].ScaledBy(B*weight[i])); d += weight[i]*B; pt_p = pt_p.Plus(ctrl[i].ScaledBy(Bp*weight[i])); d_p += weight[i]*Bp; } // quotient rule; f(t) = n(t)/d(t), so f' = (n'*d - n*d')/(d^2) Vector ret; ret = (pt_p.ScaledBy(d)).Minus(pt.ScaledBy(d_p)); ret = ret.ScaledBy(1.0/(d*d)); return ret; }
//----------------------------------------------------------------------------- // Compute the exact tangent to the intersection curve between two surfaces, // by taking the cross product of the surface normals. We choose the direction // of this tangent so that its dot product with dir is positive. //----------------------------------------------------------------------------- Vector SSurface::ExactSurfaceTangentAt(Vector p, SSurface *srfA, SSurface *srfB, Vector dir) { Point2d puva, puvb; srfA->ClosestPointTo(p, &puva); srfB->ClosestPointTo(p, &puvb); Vector ts = (srfA->NormalAt(puva)).Cross( (srfB->NormalAt(puvb))); ts = ts.WithMagnitude(1); if(ts.Dot(dir) < 0) { ts = ts.ScaledBy(-1); } return ts; }
void Constraint::DoEqualRadiusTicks(hEntity he) { Entity *circ = SK.GetEntity(he); Vector center = SK.GetEntity(circ->point[0])->PointGetNum(); double r = circ->CircleGetRadiusNum(); Quaternion q = circ->Normal()->NormalGetNum(); Vector u = q.RotationU(), v = q.RotationV(); double theta; if(circ->type == Entity::CIRCLE) { theta = PI/2; } else if(circ->type == Entity::ARC_OF_CIRCLE) { double thetaa, thetab, dtheta; circ->ArcGetAngles(&thetaa, &thetab, &dtheta); theta = thetaa + dtheta/2; } else oops(); Vector d = u.ScaledBy(cos(theta)).Plus(v.ScaledBy(sin(theta))); d = d.ScaledBy(r); Vector p = center.Plus(d); Vector tick = d.WithMagnitude(10/SS.GW.scale); LineDrawOrGetDistance(p.Plus(tick), p.Minus(tick)); }
static void FatLineEndcap(Vector p, Vector u, Vector v) { // A table of cos and sin of (pi*i/10 + pi/2), as i goes from 0 to 10 static const double Circle[11][2] = { { 0.0000, 1.0000 }, { -0.3090, 0.9511 }, { -0.5878, 0.8090 }, { -0.8090, 0.5878 }, { -0.9511, 0.3090 }, { -1.0000, 0.0000 }, { -0.9511, -0.3090 }, { -0.8090, -0.5878 }, { -0.5878, -0.8090 }, { -0.3090, -0.9511 }, { 0.0000, -1.0000 }, }; glBegin(GL_TRIANGLE_FAN); for(int i = 0; i <= 10; i++) { double c = Circle[i][0], s = Circle[i][1]; ssglVertex3v(p.Plus(u.ScaledBy(c)).Plus(v.ScaledBy(s))); } glEnd(); }
void Group::GenerateForStepAndRepeat(T *steps, T *outs) { T workA, workB; workA = {}; workB = {}; T *soFar = &workA, *scratch = &workB; int n = (int)valA, a0 = 0; if(subtype == ONE_SIDED && skipFirst) { a0++; n++; } int a; for(a = a0; a < n; a++) { int ap = a*2 - (subtype == ONE_SIDED ? 0 : (n-1)); int remap = (a == (n - 1)) ? REMAP_LAST : a; T transd = {}; if(type == TRANSLATE) { Vector trans = Vector::From(h.param(0), h.param(1), h.param(2)); trans = trans.ScaledBy(ap); transd.MakeFromTransformationOf(steps, trans, Quaternion::IDENTITY, 1.0); } else { Vector trans = Vector::From(h.param(0), h.param(1), h.param(2)); double theta = ap * SK.GetParam(h.param(3))->val; double c = cos(theta), s = sin(theta); Vector axis = Vector::From(h.param(4), h.param(5), h.param(6)); Quaternion q = Quaternion::From(c, s*axis.x, s*axis.y, s*axis.z); // Rotation is centered at t; so A(x - t) + t = Ax + (t - At) transd.MakeFromTransformationOf(steps, trans.Minus(q.Rotate(trans)), q, 1.0); } // We need to rewrite any plane face entities to the transformed ones. transd.RemapFaces(this, remap); // And tack this transformed copy on to the return. if(soFar->IsEmpty()) { scratch->MakeFromCopyOf(&transd); } else { scratch->MakeFromUnionOf(soFar, &transd); } swap(scratch, soFar); scratch->Clear(); transd.Clear(); } outs->Clear(); *outs = *soFar; }
Vector SSurface::PointAt(double u, double v) { Vector num = Vector::From(0, 0, 0); double den = 0; int i, j; for(i = 0; i <= degm; i++) { for(j = 0; j <= degn; j++) { double Bi = Bernstein(i, degm, u), Bj = Bernstein(j, degn, v); num = num.Plus(ctrl[i][j].ScaledBy(Bi*Bj*weight[i][j])); den += weight[i][j]*Bi*Bj; } } num = num.ScaledBy(1.0/den); return num; }
void GraphicsWindow::HandlePointForZoomToFit(Vector p, Point2d *pmax, Point2d *pmin, double *wmin, bool usePerspective) { double w; Vector pp = ProjectPoint4(p, &w); // If usePerspective is true, then we calculate a perspective projection of the point. // If not, then we do a parallel projection regardless of the current // scale factor. if(usePerspective) { pp = pp.ScaledBy(1.0/w); } pmax->x = max(pmax->x, pp.x); pmax->y = max(pmax->y, pp.y); pmin->x = min(pmin->x, pp.x); pmin->y = min(pmin->y, pp.y); *wmin = min(*wmin, w); }