1 /* Useful functions for drawing Physics 101 figures.
3 * Copyright (C) 2008-2011 W. Trevor King <wking@drexel.edu>
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
22 // ----------------- Labeled circle --------------------
24 struct LabeledCircle {
31 void operator init(pair center=(0,0), real radius=2mm,
32 pen outline=currentpen, pen fill=grey, Label L="") {
35 this.outline = outline;
40 void draw_label(picture pic=currentpicture, Label L=null) {
46 if (L.align != NoAlign && L.align != Align) {
47 real m = labelmargin(L.p);
48 real scale = (m + this.radius)/m;
50 L.align.dir3 *= scale;
55 label(pic=pic, L=L, position=this.center);
59 void draw(picture pic=currentpicture) {
60 path p = shift(this.center)*scale(this.radius)*unitcircle;
61 filldraw(pic, p, this.fill, this.outline);
62 this.draw_label(pic=pic);
66 // ---------------------- Mass -------------------------
72 void operator init(pair center=(0,0), real m=1, real radius=2mm,
73 pen outline=currentpen, pen fill=grey, Label L="") {
74 this.lc.operator init(center=center, radius=radius, outline=outline,
79 pair center() { return this.lc.center; }
80 void set_center(pair center) { this.lc.center = center; }
81 void draw(picture pic=currentpicture) = this.lc.draw;
94 void operator init(pair center=(0,0), real m=1, real width=5mm, real height=-1, real direction=0, pen outline=currentpen, pen fill=grey, Label L="") {
101 this.height = height;
102 this.direction = direction;
103 this.outline = outline;
108 void draw(picture pic=currentpicture) {
110 path c = rotate(direction)*scale(width,height)*shift(-.5,-.5)*unitsquare;
111 filldraw(picF, c, fill, outline);
112 label(pic=picF, L=L, position=(0,0));
113 add(pic, picF, center);
117 // ---------------------- Vectors -------------------------
122 real dir; // angle in the plane of the drawing.
123 real phi; // angle with the plane of the drawing, 90 is out of the page.
126 real out_of_plane_radius;
127 real out_of_plane_tolerance;
129 void operator init(pair center=(0,0), real mag=5mm, real dir=0, real phi=0, pen outline=currentpen, Label L="") {
130 this.center = center;
134 this.outline = outline;
136 this.out_of_plane_radius = 1mm;
137 this.out_of_plane_tolerance = 0.01;
141 Vector v = Vector(center=this.center, mag=this.mag, dir=this.dir,
142 phi=this.phi, outline=this.outline, L=this.label);
143 v.out_of_plane_radius = this.out_of_plane_radius;
144 v.out_of_plane_tolerance = this.out_of_plane_tolerance;
148 pair dTip() { // offset from center to tip
150 real phi_e = this.phi % 360; // effective phi
151 if (Tan(phi_e) == 0 || abs(1.0/Tan(phi_e)) > this.out_of_plane_tolerance) {
152 return this.mag*Cos(this.phi)*dir(this.dir);
158 return this.dTip() + this.center;
161 void draw(picture pic=currentpicture) {
163 pair p = this.dTip();
165 real phi_e = this.phi % 360; // effective phi
166 if (this.mag < 0) (phi_e + 180) % 360;
167 if (Tan(phi_e) == 0 || abs(1.0/Tan(phi_e)) > this.out_of_plane_tolerance) {
168 // draw arrow in the plane of the drawing
169 // TODO: thickening for phi?
171 draw(picF, P, outline, Arrow);
172 } else if (phi_e > 0 && phi_e < 180) {
173 // draw a circled dot for out-of-the-page
174 P = scale(this.out_of_plane_radius)*unitcircle;
175 draw(picF, P, outline);
176 dot(picF, (0,0), outline);
178 // draw a circled cross for into-the-page
179 real a = 0.8*sqrt(2.0)/2.0;
180 P = scale(this.out_of_plane_radius)*unitcircle;
181 draw(picF, P, outline);
182 draw(picF, scale(this.out_of_plane_radius)*((-a,-a)--(a,a)), outline);
183 draw(picF, scale(this.out_of_plane_radius)*((-a,a)--(a,-a)), outline);
185 label(pic=picF, L=this.label, g=P);
186 add(pic, picF, center);
190 Vector operator +(Vector a, Vector b) {
192 pair p = a.mag*dir(a.dir) + b.mag*dir(b.dir);
198 Vector operator -(Vector a, Vector b) {
200 pair p = a.mag*dir(a.dir) - b.mag*dir(b.dir);
206 void vector_field(pair center=(0,0), real width=2cm, real height=2cm,
207 real dv=0.5cm, real buf=2pt, Vector v=null,
208 pen outline=invisible) {
209 /* There will be a buffer of at least buf on each side */
211 v = Vector(); // unlikely to be what they want, but it will draw something
214 pair ovcenter = v.center;
216 path bufsq = shift(center)*xscale(width-2*buf)*yscale(height-2*buf)
217 *shift((-.5,-.5))*unitsquare; // buffered bounding box
218 pair uv = dir(v.dir); // unit vector in the direction of v
219 pair d = dv * dir(v.dir+90);
222 int nx = 1; // x steps
223 int ny = 1; // y steps
226 if (abs(fmod(v.phi, 180)) == 90) { // pure in/out, make a 2D grid
227 dx = dy = dv; // v.dir was meaninless, reset dx and dy
228 nx = abs((int)((width-2*buf) / dx)) + 1;
229 ny = abs((int)((height-2*buf) / dy)) + 1;
230 } else if (abs(fmod(v.dir, 180)) == 0) { // pure left/right, vert. border
231 ny = abs((int)((height-2*buf) / dy)) + 1;
233 } else if (abs(fmod(v.dir, 180)) == 90) { // pure up/down, horiz. border
234 nx = abs((int)((width-2*buf) / dx)) + 1;
236 } else { // diagonal, draw along a vertical an horizontal border
238 // this requires enough special handling that we break it out below
241 if (!diag) { // square grid
242 real xx=buf, xy=buf; // buffer distace per side
244 xx = (width-(nx-1)*fabs(dx))/2.0; // "extra" left over after division
246 xy = (height-(ny-1)*fabs(dy))/2.0;
248 real xstart = center.x - width/2 + xx;
249 real ystart = center.y - height/2 + xy;
250 if (dx < 0 || (dx == 0 && dot(uv, dir(0)) < 0))
251 xstart += width - 2*xx;
252 if (dy < 0 || (dy == 0 && dot(uv, dir(90)) < 0))
253 ystart += height - 2*xy;
255 for (int i=0; i<nx; i+=1) {
256 for (int j=0; j<ny; j+=1) {
257 v.center = (xstart+i*dx, ystart+j*dy);
258 if (abs(fmod(v.phi, 180)) != 90) { // pure left/right/up/down
259 // set length so that v just touches far bufsq face
260 path vlong = (v.center+uv*min(width, height)/2)
261 --(v.center+uv*max(width, height));
262 //dot(v.center); draw(vlong); continue; // intersection debugging
263 pair xs[] = intersectionpoints(vlong, bufsq);
264 assert(xs.length == 1, format("%d", xs.length));
265 v.mag = length(xs[0] - v.center);
270 } else { // v is diagonal
271 pair cc = (sgn(d.x)*width, sgn(d.y)*height); // catty-corner vector
272 real ccbuf = buf*max(fabs(1/cos(angle(cc))), // buf away in y
273 fabs(1/sin(angle(cc)))); // buf away in x
274 int n = abs((int)((dot(cc, unit(d))-2*ccbuf)/length(d))) + 1; // lines
275 pair pstart = center - (n-1)*d/2.0;
277 for (int i=0; i<n; i+=1) {
278 // project along +/- v until you hit the edge of bufsq
279 pair p = (pstart + i*d);
280 path vlong = (p-uv*length(cc))--(p+uv*length(cc)); // extends out of box
281 //dot(p); draw(vlong); continue; // intersection debugging
282 pair xs[] = intersectionpoints(vlong, bufsq);
283 assert(xs.length == 2, format("%d", xs.length));
285 if (dot((start - xs[1]), uv) > 0)
288 v.mag = length(xs[1]-xs[0]);
293 v.center = ovcenter; // restore original center
294 v.mag = ovmag; // restore original magnitude
297 draw(shift(center)*xscale(width)*yscale(height)*shift((-.5,-.5))*
298 unitsquare, outline);
301 Vector Velocity(pair center=(0,0), real mag=5mm, real dir=0, real phi=0, Label L="")
303 Vector v = Vector(center=center, mag=mag, dir=dir, phi=phi, L=L, outline=rgb(1,0.1,0.2)); // red
307 // ---------------------- Forces -------------------------
309 Vector Force(pair center=(0,0), real mag=5mm, real dir=0, real phi=0, Label L="")
311 Vector v = Vector(center=center, mag=mag, dir=dir, phi=phi, L=L, outline=rgb(0.1,0.2,1)); // blue
315 // ---------------------- Measures -------------------------
317 // Distance derived from CAD.MeasuredLine
326 void operator init(pair pFrom=(0,0), pair pTo=(5mm,0), real offset=0, real scale=5mm, pen outline=currentpen, Label L="") {
329 this.offset = offset;
331 this.outline = outline;
335 void draw(picture pic=currentpicture) {
336 pair o = this.offset*unit(rotate(-90)*(this.pTo - this.pFrom));
337 path p = (this.pFrom+o) -- (this.pTo+o);
338 draw(pic, p, outline, Arrows);
339 embed e = this.label.embed;
340 if (this.label.embed == Rotate) {
341 this.label.embed = Rotate(this.pTo - this.pFrom);
343 label(pic=pic, L=this.label, g=p);
344 this.label.embed = e;
350 pair A; // center of angle
352 real radius; // radius < 0 for exterior angles.
357 void operator init(pair B, pair A, pair C, real radius=5mm, pen outline=currentpen, pen fill=invisible, Label L="") {
361 this.radius = radius;
362 this.outline = outline;
367 void draw(picture pic=currentpicture) {
371 real ccw_angle = degrees(C-A)-degrees(B-A);
372 bool direction = CCW;
373 if (ccw_angle < 0) ccw_angle += 360.0;
377 direction = !direction;
378 path p = arc((0,0), fabs(radius), degrees(B-A), degrees(C-A), direction);
379 if (this.fill != invisible) {
380 path pcycle = (0,0) -- p -- cycle;
381 fill(picF, pcycle, this.fill);
383 draw(picF, p, this.outline);
384 if (direction == CW) {
387 label(pic=picF, L=this.label, g=p);
392 Vector hatVect (string name, pair center=(0,0), real dir=0, real phi=0) {
393 string s = replace("$\mathbf{\hat{X}}$", "X", name);
394 Label L = Label(s, position=EndPoint, align=RightSide);
396 center=center, mag=5mm, dir=dir, phi=phi, L=L, outline=rgb(0,0,0));
400 Vector ihat (pair center=(0,0), real dir=0, real phi=0) {
401 Vector v = hatVect(name="i", center=center, dir=dir, phi=phi);
405 Vector jhat (pair center=(0,0), real dir=90, real phi=0) {
406 Vector v = hatVect(name="j", center=center, dir=dir, phi=phi);
410 void draw_ijhat(pair center=(0,0), real idir=0) {
411 Vector ihat = ihat(center, idir);
412 Vector jhat = jhat(center, idir+90);
417 // ---------------------- Shapes -------------------------
425 void operator init(pair pFrom=(0,0), pair pTo=(5mm,0), pen outline=currentpen, Label L="") {
428 this.outline = outline;
432 void draw(picture pic=currentpicture) {
433 path p = this.pFrom--this.pTo;
434 draw(pic, p, outline);
435 embed e = this.label.embed;
436 if (this.label.embed == Rotate) {
437 this.label.embed = Rotate(this.pTo - this.pFrom);
439 label(pic=pic, L=this.label, g=p);
440 this.label.embed = e;
453 void operator init(pair pFrom=(0,0), pair pTo=(5mm,0), real thickness=5mm, pen outline=currentpen, pen filla=rgb(.5,.5,.5), pen fillb=rgb(.7,.7,.7), Label L="") {
456 this.thickness = thickness;
457 this.outline = outline;
463 void draw(picture pic=currentpicture) {
464 pair pDiff = pTo - pFrom;
465 pair pDepth = rotate(-90)*unit(pDiff)*thickness;
466 path p = (0, 0) -- pDiff -- (pDiff + pDepth) -- pDepth -- cycle;
467 p = shift(this.pFrom) * p;
469 pic=pic, g=p, pena=filla, a=this.pFrom,
470 penb=fillb, b=this.pFrom+pDepth);
471 draw(pic, p, outline);
472 embed e = this.label.embed;
473 if (this.label.embed == Rotate) {
474 this.label.embed = Rotate(this.pTo - this.pFrom);
476 label(pic=pic, L=this.label,
477 g=(this.pFrom+pDepth/2) -- (this.pTo+pDepth/2));
478 this.label.embed = e;
487 real dLength; // length of a single loop (when unstretched)
488 real deadLength; // length before loops start on each end
489 real unstretchedLength;
494 void operator init(pair pFrom=(0,0), pair pTo=(5mm,0), real k=1, real width=3mm, real dLength=1mm, real deadLength=3mm, real unstretchedLength=12mm, pen outline=currentpen, Label L="") {
499 this.dLength = dLength;
500 this.unstretchedLength = unstretchedLength;
501 this.outline = outline;
504 this.nLoops = floor((unstretchedLength-2*deadLength)/dLength);
505 if (this.nLoops == 0)
507 this.deadLength = (unstretchedLength-this.nLoops*dLength)/2;
510 void draw(picture pic=currentpicture) {
511 pair pDiff = pTo - pFrom;
513 real working_dLength = (length(pDiff) - 2*deadLength) / nLoops;
514 path p = (0,0)--(deadLength,0);
516 --(working_dLength/4, width/2)
517 --(working_dLength*3/4, -width/2)
518 --(working_dLength, 0);
520 for (int i=0; i<nLoops; ++i) {
521 loopStart = point(p, length(p));
522 p = p--(shift(loopStart)*loop);
524 loopStart = point(p, length(p));
525 p = p--(loopStart+(deadLength,0));
526 p = shift(this.pFrom)*rotate(degrees(pDiff)) * p;
527 draw(pic, p, outline);
528 align a = this.label.align;
529 embed e = this.label.embed;
530 real m = labelmargin(this.label.p);
531 real scale = (m + this.width/2)/m;
532 if (this.label.align.is3D) {
533 this.label.align.dir3 *= scale;
535 this.label.align.dir *= scale;
537 if (this.label.embed == Rotate) {
538 this.label.embed = Rotate(this.pTo - this.pFrom);
540 label(pic=pic, L=this.label, g=this.pFrom -- this.pTo);
541 this.label.align = a;
542 this.label.embed = e;
552 void operator init(pair pivot=(0,0), Mass mass=Mass()) {
555 this.angle = Angle(mass.center(), pivot, pivot-(0,1));
556 this.str = Wire(pivot, mass.center());
559 void draw(picture pic=currentpicture, bool drawVertical=false) {
561 if (drawVertical == true) {
562 pair final = pivot + realmult((0,0.5),(mass.center()-pivot));
563 draw(pic=pic, pivot--final, p=currentpen+dashed);
565 draw(pic=pic, pivot);
571 // The angle argument is deflection from straight down (i.e. 0 degrees = plumb)
572 Pendulum makePendulum(pair pivot=(0,0), Mass mass=Mass(), real length=15mm, real angleDeg=0, Label angleL="", Label stringL="") {
573 mass.set_center(pivot + length*dir(angleDeg-90));
574 Pendulum p = Pendulum(pivot=pivot, mass=mass);
575 p.angle.label = angleL;
576 p.str.label = stringL;
581 // TODO: ring, plate, cylinder, table