+/* Useful functions for drawing Physics 101 figures.
+ *
+ * Copyright (C) 2008-2011 W. Trevor King <wking@drexel.edu>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
import geometry;
-// ---------------------- Mass -------------------------
+// ----------------- Labeled circle --------------------
-struct Mass {
+struct LabeledCircle {
pair center;
- real m;
real radius;
pen outline;
pen fill;
- Label L;
-
- void operator init(pair center=(0,0), real m=1, real radius=2mm, pen outline=currentpen, pen fill=grey, Label L="") {
+ Label label;
+
+ void operator init(pair center=(0,0), real radius=2mm,
+ pen outline=currentpen, pen fill=grey, Label L="") {
this.center = center;
- this.m = m;
this.radius = radius;
this.outline = outline;
this.fill = fill;
- this.L = L;
+ this.label = L;
}
-
+
+ void draw_label(picture pic=currentpicture, Label L=null) {
+ align a;
+ if (L == null) {
+ L = this.label;
+ }
+ a = L.align;
+ if (L.align != NoAlign && L.align != Align) {
+ real m = labelmargin(L.p);
+ real scale = (m + this.radius)/m;
+ if (L.align.is3D) {
+ L.align.dir3 *= scale;
+ } else {
+ L.align.dir *= scale;
+ }
+ }
+ label(pic=pic, L=L, position=this.center);
+ L.align = a;
+ }
+
void draw(picture pic=currentpicture) {
- picture picF;
- path c = scale(radius)*unitcircle;
- filldraw(picF, c, fill, outline);
- label(pic=picF, L=L, position=(0,0));
- add(pic, picF, center);
+ path p = shift(this.center)*scale(this.radius)*unitcircle;
+ filldraw(pic, p, this.fill, this.outline);
+ this.draw_label(pic=pic);
}
}
+// ---------------------- Mass -------------------------
+
+struct Mass {
+ LabeledCircle lc;
+ real m;
+
+ void operator init(pair center=(0,0), real m=1, real radius=2mm,
+ pen outline=currentpen, pen fill=grey, Label L="") {
+ this.lc.operator init(center=center, radius=radius, outline=outline,
+ fill=fill, L=L);
+ this.m = m;
+ }
+
+ pair center() { return this.lc.center; }
+ void set_center(pair center) { this.lc.center = center; }
+ void draw(picture pic=currentpicture) = this.lc.draw;
+}
+
struct Block {
pair center;
real m;
struct Vector {
pair center;
real mag;
- real dir;
+ real dir; // angle in the plane of the drawing.
+ real phi; // angle with the plane of the drawing, 90 is out of the page.
pen outline;
- Label L;
-
- void operator init(pair center=(0,0), real mag=5mm, real dir=0, pen outline=currentpen, Label L="") {
+ Label label;
+ real out_of_plane_radius;
+ real out_of_plane_tolerance;
+
+ void operator init(pair center=(0,0), real mag=5mm, real dir=0, real phi=0, pen outline=currentpen, Label L="") {
this.center = center;
this.mag = mag;
this.dir = dir;
+ this.phi = phi;
this.outline = outline;
- this.L = L;
+ this.label = L;
+ this.out_of_plane_radius = 1mm;
+ this.out_of_plane_tolerance = 0.01;
+ }
+
+ Vector copy() {
+ Vector v = Vector(center=this.center, mag=this.mag, dir=this.dir,
+ phi=this.phi, outline=this.outline, L=this.label);
+ v.out_of_plane_radius = this.out_of_plane_radius;
+ v.out_of_plane_tolerance = this.out_of_plane_tolerance;
+ return v;
+ }
+
+ pair dTip() { // offset from center to tip
+ pair p = (0,0);
+ real phi_e = this.phi % 360; // effective phi
+ if (Tan(phi_e) == 0 || abs(1.0/Tan(phi_e)) > this.out_of_plane_tolerance) {
+ return this.mag*Cos(this.phi)*dir(this.dir);
+ }
+ return (0, 0);
}
pair pTip() {
- return this.mag*dir(this.dir)+this.center;
+ return this.dTip() + this.center;
}
-
- void draw(picture pic=currentpicture, bool rotateLabel=false, pair labelOffset=(0,0)) {
+
+ void draw(picture pic=currentpicture) {
picture picF;
- pair p = this.mag*dir(this.dir);
- path P = (0,0)--p;
- pair label_rotate = p;
- draw(picF, P, outline, Arrow);
- if (rotateLabel == false)
- label_rotate = (1,0);
- label(pic = picF,
- L = rotate(degrees(label_rotate)) * L,
- position = p+labelOffset,
- align = unit(rotate(90)*p));
+ pair p = this.dTip();
+ path P;
+ real phi_e = this.phi % 360; // effective phi
+ if (this.mag < 0) (phi_e + 180) % 360;
+ if (Tan(phi_e) == 0 || abs(1.0/Tan(phi_e)) > this.out_of_plane_tolerance) {
+ // draw arrow in the plane of the drawing
+ // TODO: thickening for phi?
+ P = (0,0)--p;
+ draw(picF, P, outline, Arrow);
+ } else if (phi_e > 0 && phi_e < 180) {
+ // draw a circled dot for out-of-the-page
+ P = scale(this.out_of_plane_radius)*unitcircle;
+ draw(picF, P, outline);
+ dot(picF, (0,0), outline);
+ } else {
+ // draw a circled cross for into-the-page
+ real a = 0.8*sqrt(2.0)/2.0;
+ P = scale(this.out_of_plane_radius)*unitcircle;
+ draw(picF, P, outline);
+ draw(picF, scale(this.out_of_plane_radius)*((-a,-a)--(a,a)), outline);
+ draw(picF, scale(this.out_of_plane_radius)*((-a,a)--(a,-a)), outline);
+ }
+ label(pic=picF, L=this.label, g=P);
add(pic, picF, center);
}
}
-Vector Velocity(pair center=(0,0), real mag=5mm, real dir=0, Label L="")
+Vector operator +(Vector a, Vector b) {
+ Vector c = a.copy();
+ pair p = a.mag*dir(a.dir) + b.mag*dir(b.dir);
+ c.mag = length(p);
+ c.dir = degrees(p);
+ return c;
+}
+
+Vector operator -(Vector a, Vector b) {
+ Vector c = a.copy();
+ pair p = a.mag*dir(a.dir) - b.mag*dir(b.dir);
+ c.mag = length(p);
+ c.dir = degrees(p);
+ return c;
+}
+
+void vector_field(pair center=(0,0), real width=2cm, real height=2cm,
+ real dv=0.5cm, real buf=2pt, Vector v=null,
+ pen outline=invisible) {
+ /* There will be a buffer of at least buf on each side */
+ if (v == null) {
+ v = Vector(); // unlikely to be what they want, but it will draw something
+ }
+
+ pair ovcenter = v.center;
+ real ovmag = v.mag;
+ path bufsq = shift(center)*xscale(width-2*buf)*yscale(height-2*buf)
+ *shift((-.5,-.5))*unitsquare; // buffered bounding box
+ pair uv = dir(v.dir); // unit vector in the direction of v
+ pair d = dv * dir(v.dir+90);
+ real dx = d.x;
+ real dy = d.y;
+ int nx = 1; // x steps
+ int ny = 1; // y steps
+ bool diag = false;
+
+ if (abs(fmod(v.phi, 180)) == 90) { // pure in/out, make a 2D grid
+ dx = dy = dv; // v.dir was meaninless, reset dx and dy
+ nx = abs((int)((width-2*buf) / dx)) + 1;
+ ny = abs((int)((height-2*buf) / dy)) + 1;
+ } else if (abs(fmod(v.dir, 180)) == 0) { // pure left/right, vert. border
+ ny = abs((int)((height-2*buf) / dy)) + 1;
+ dx = 0;
+ } else if (abs(fmod(v.dir, 180)) == 90) { // pure up/down, horiz. border
+ nx = abs((int)((width-2*buf) / dx)) + 1;
+ dy = 0;
+ } else { // diagonal, draw along a vertical an horizontal border
+ diag = true;
+ // this requires enough special handling that we break it out below
+ }
+
+ if (!diag) { // square grid
+ real xx=buf, xy=buf; // buffer distace per side
+ if (dx != 0)
+ xx = (width-(nx-1)*fabs(dx))/2.0; // "extra" left over after division
+ if (dy != 0)
+ xy = (height-(ny-1)*fabs(dy))/2.0;
+
+ real xstart = center.x - width/2 + xx;
+ real ystart = center.y - height/2 + xy;
+ if (dx < 0 || (dx == 0 && dot(uv, dir(0)) < 0))
+ xstart += width - 2*xx;
+ if (dy < 0 || (dy == 0 && dot(uv, dir(90)) < 0))
+ ystart += height - 2*xy;
+
+ for (int i=0; i<nx; i+=1) {
+ for (int j=0; j<ny; j+=1) {
+ v.center = (xstart+i*dx, ystart+j*dy);
+ if (abs(fmod(v.phi, 180)) != 90) { // pure left/right/up/down
+ // set length so that v just touches far bufsq face
+ path vlong = (v.center+uv*min(width, height)/2)
+ --(v.center+uv*max(width, height));
+ //dot(v.center); draw(vlong); continue; // intersection debugging
+ pair xs[] = intersectionpoints(vlong, bufsq);
+ assert(xs.length == 1, format("%d", xs.length));
+ v.mag = length(xs[0] - v.center);
+ }
+ v.draw();
+ }
+ }
+ } else { // v is diagonal
+ pair cc = (sgn(d.x)*width, sgn(d.y)*height); // catty-corner vector
+ real ccbuf = buf*max(fabs(1/cos(angle(cc))), // buf away in y
+ fabs(1/sin(angle(cc)))); // buf away in x
+ int n = abs((int)((dot(cc, unit(d))-2*ccbuf)/length(d))) + 1; // lines
+ pair pstart = center - (n-1)*d/2.0;
+
+ for (int i=0; i<n; i+=1) {
+ // project along +/- v until you hit the edge of bufsq
+ pair p = (pstart + i*d);
+ path vlong = (p-uv*length(cc))--(p+uv*length(cc)); // extends out of box
+ //dot(p); draw(vlong); continue; // intersection debugging
+ pair xs[] = intersectionpoints(vlong, bufsq);
+ assert(xs.length == 2, format("%d", xs.length));
+ pair start = xs[0];
+ if (dot((start - xs[1]), uv) > 0)
+ start = xs[1];
+ v.center = start;
+ v.mag = length(xs[1]-xs[0]);
+ v.draw();
+ }
+ }
+
+ v.center = ovcenter; // restore original center
+ v.mag = ovmag; // restore original magnitude
+
+ // draw bounding box
+ draw(shift(center)*xscale(width)*yscale(height)*shift((-.5,-.5))*
+ unitsquare, outline);
+}
+
+Vector Velocity(pair center=(0,0), real mag=5mm, real dir=0, real phi=0, Label L="")
{
- Vector v = Vector(center=center, mag=mag, dir=dir, L=L, outline=rgb(1,0.1,0.2)); // red
+ Vector v = Vector(center=center, mag=mag, dir=dir, phi=phi, L=L, outline=rgb(1,0.1,0.2)); // red
return v;
}
// ---------------------- Forces -------------------------
-Vector Force(pair center=(0,0), real mag=5mm, real dir=0, Label L="")
+Vector Force(pair center=(0,0), real mag=5mm, real dir=0, real phi=0, Label L="")
{
- Vector v = Vector(center=center, mag=mag, dir=dir, L=L, outline=rgb(0.1,0.2,1)); // blue
+ Vector v = Vector(center=center, mag=mag, dir=dir, phi=phi, L=L, outline=rgb(0.1,0.2,1)); // blue
return v;
}
struct Distance {
pair pFrom;
pair pTo;
+ real offset;
real scale;
pen outline;
- Label L;
+ Label label;
- void operator init(pair pFrom=(0,0), pair pTo=(5mm,0), real scale=5mm, pen outline=currentpen, Label L="") {
+ void operator init(pair pFrom=(0,0), pair pTo=(5mm,0), real offset=0, real scale=5mm, pen outline=currentpen, Label L="") {
this.pFrom = pFrom;
this.pTo = pTo;
+ this.offset = offset;
this.scale = scale;
this.outline = outline;
- this.L = L;
+ this.label = L;
}
-
- void draw(picture pic=currentpicture, bool rotateLabel=true) {
- picture picF;
- picture picL;
- label(picL, L);
- pair pLabelSize = 1.2 * (max(picL)-min(picL));
- pair pDiff = pTo - pFrom;
- path p = (0,0)--pDiff;
- pair label_rotate=pDiff;
- if (rotateLabel == false)
- label_rotate=(1,0);
- draw(picF, p, outline, Arrows);
- label(pic = picF,
- L = rotate(degrees(label_rotate)) * L,
- position = pDiff/2
- + unit(rotate(90)*pDiff) * pLabelSize.y / 2);
- //label(pic=picF, L = rotate(degrees(label_rotate)) format("%g", pDiff/scale), position = TODO);
- add(pic, picF, pFrom);
+
+ void draw(picture pic=currentpicture) {
+ pair o = this.offset*unit(rotate(-90)*(this.pTo - this.pFrom));
+ path p = (this.pFrom+o) -- (this.pTo+o);
+ draw(pic, p, outline, Arrows);
+ embed e = this.label.embed;
+ if (this.label.embed == Rotate) {
+ this.label.embed = Rotate(this.pFrom - this.pTo);
+ }
+ label(pic=pic, L=this.label, g=p);
+ this.label.embed = e;
}
}
pair C;
real radius; // radius < 0 for exterior angles.
pen outline;
- Label L;
+ pen fill;
+ Label label;
- void operator init(pair B, pair A, pair C, real radius=5mm, pen outline=currentpen, Label L="") {
+ void operator init(pair B, pair A, pair C, real radius=5mm, pen outline=currentpen, pen fill=invisible, Label L="") {
this.B = B;
this.A = A;
this.C = C;
this.radius = radius;
this.outline = outline;
- this.L = L;
+ this.fill = fill;
+ this.label = L;
}
- void draw(picture pic=currentpicture, bool rotateLabel=false) {
+ void draw(picture pic=currentpicture) {
picture picF;
- picture picL;
bool direction;
- label(picL, L);
- pair pLabelSize = 1.2 * (max(picL)-min(picL));
real ccw_angle = degrees(C-A)-degrees(B-A);
bool direction = CCW;
if (ccw_angle < 0) ccw_angle += 360.0;
if (radius < 0)
direction = !direction;
path p = arc((0,0), fabs(radius), degrees(B-A), degrees(C-A), direction);
- real t = reltime(p, 0.5);
- pair P = midpoint(p);
- pair tangent = dir(p, t);
- if (direction == CW) tangent *= -1.0;
-
- pair label_rotate = tangent;
- if (rotateLabel == false)
- label_rotate = (1,0);
-
- draw(picF, p, outline);
- label(pic = picF,
- L = rotate(degrees(label_rotate)) * L,
- position = P + unit(P) * pLabelSize.y / 2);
+ if (this.fill != invisible) {
+ path pcycle = (0,0) -- p -- cycle;
+ fill(picF, pcycle, this.fill);
+ }
+ draw(picF, p, this.outline);
+ if (direction == CW) {
+ p = reverse(p);
+ }
+ label(pic=picF, L=this.label, g=p);
add(pic, picF, A);
}
}
-// TODO: ihat, ijhat
-Vector hatVect (string name, pair center=(0,0), real dir=0) {
+Vector hatVect (string name, pair center=(0,0), real dir=0, real phi=0) {
string L = replace("$\mathbf{\hat{X}}$", "X", name);
- Vector v = Vector(center=center, mag=5mm, dir=dir, L=L, outline=rgb(0,0,0));
+ Vector v = Vector(center=center, mag=5mm, dir=dir, phi=phi, L=L, outline=rgb(0,0,0));
return v;
}
-Vector ihat (pair center=(0,0), real dir=0) {
- Vector v = hatVect(name="i", center=center, dir=dir);
+Vector ihat (pair center=(0,0), real dir=0, real phi=0) {
+ Vector v = hatVect(name="i", center=center, dir=dir, phi=phi);
return v;
}
-Vector jhat (pair center=(0,0), real dir=90) {
- Vector v = hatVect(name="j", center=center, dir=dir);
+Vector jhat (pair center=(0,0), real dir=90, real phi=0) {
+ Vector v = hatVect(name="j", center=center, dir=dir, phi=phi);
return v;
}
+void draw_ijhat(pair center=(0,0), real idir=0) {
+ Vector ihat = ihat(center, idir);
+ Vector jhat = jhat(center, idir+90);
+ ihat.draw();
+ jhat.draw();
+}
+
// ---------------------- Shapes -------------------------
struct Wire {
label_rotate=(1,0);
draw(picF, p, outline);
label(pic = picF,
- L = rotate(degrees(label_rotate)) * L,
- position = pDiff/2
- + unit(rotate(90)*pDiff) * pLabelSize.y / 2);
+ L = rotate(degrees(label_rotate)) * L,
+ position = pDiff/2
+ + unit(rotate(90)*pDiff) * pLabelSize.y / 2);
add(pic, picF, pFrom);
}
}
if (rotateLabel == false)
label_rotate=(1,0);
axialshade(pic=picF, g=p, pena=filla, a=(0,0), penb=fillb,
- b=pDepth);
+ b=pDepth);
draw(picF, p, outline);
label(pic = picF,
- L = rotate(degrees(label_rotate)) * L,
- position = (pDiff+pDepth)/2);
+ L = rotate(degrees(label_rotate)) * L,
+ position = (pDiff+pDepth)/2);
add(pic, picF, pFrom);
}
}
label_rotate=(1,0);
draw(picF, p, outline);
label(pic = picF,
- L = rotate(degrees(label_rotate)) * L,
- position = pDiff/2
- + unit(rotate(90)*pDiff) * (width + pLabelSize.y) / 2);
+ L = rotate(degrees(label_rotate)) * L,
+ position = pDiff/2
+ + unit(rotate(90)*pDiff) * (width + pLabelSize.y) / 2);
add(pic, picF, pFrom);
}
}
void operator init(pair pivot=(0,0), Mass mass=Mass()) {
this.pivot = pivot;
this.mass = mass;
- this.angle = Angle(mass.center, pivot, pivot-(0,1));
- this.str = Wire(pivot, mass.center);
+ this.angle = Angle(mass.center(), pivot, pivot-(0,1));
+ this.str = Wire(pivot, mass.center());
}
void draw(picture pic=currentpicture, bool drawVertical=false) {
str.draw(pic=pic, rotateLabel=false);
if (drawVertical == true) {
- pair final = pivot + realmult((0,0.5),(mass.center-pivot));
+ pair final = pivot + realmult((0,0.5),(mass.center()-pivot));
draw(pic=pic, pivot--final, p=currentpen+dashed);
}
draw(pic=pic, pivot);
// The angle argument is deflection from straight down (i.e. 0 degrees = plumb)
Pendulum makePendulum(pair pivot=(0,0), Mass mass=Mass(), real length=15mm, real angleDeg=0, Label angleL="", Label stringL="") {
- mass.center = pivot + length*dir(angleDeg-90);
+ mass.set_center(pivot + length*dir(angleDeg-90));
Pendulum p = Pendulum(pivot=pivot, mass=mass);
- p.angle.L = angleL;
+ p.angle.label = angleL;
p.str.L = stringL;
return p;
}
-// TODO: ring, plate, block, cylinder, table
+// TODO: ring, plate, cylinder, table