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.
23 // ----------------- Labeled circle --------------------
25 void label_path(picture pic=currentpicture, Label L, path g, real margin=0,
29 real m = labelmargin(L.p);
30 real scale = (m + margin)/m;
32 L.align.dir3 *= scale;
36 if (L.embed == Rotate) {
37 L.embed = Rotate(rdir);
39 label(pic=pic, L=L, g=g);
44 void label_path(picture pic=currentpicture, Label L, path3 g, real margin=0,
48 real m = labelmargin(L.p);
49 real scale = (m + margin)/m;
51 L.align.dir3 *= scale;
55 if (L.embed == Rotate) {
56 L.embed = Rotate(rdir);
58 label(pic=pic, L=L, g=g);
63 struct LabeledCircle {
70 void operator init(pair center=(0,0), real radius=2mm,
71 pen outline=currentpen, pen fill=grey, Label L="") {
74 this.outline = outline;
79 void draw_label(picture pic=currentpicture, Label L=null) {
85 if (L.align != NoAlign && L.align != Align) {
86 real m = labelmargin(L.p);
87 real scale = (m + this.radius)/m;
89 L.align.dir3 *= scale;
94 label(pic=pic, L=L, position=this.center);
98 void draw(picture pic=currentpicture) {
99 path p = shift(this.center)*scale(this.radius)*unitcircle;
100 filldraw(pic, p, this.fill, this.outline);
101 this.draw_label(pic=pic);
105 // ---------------------- Mass -------------------------
111 void operator init(pair center=(0,0), real m=1, real radius=2mm,
112 pen outline=currentpen, pen fill=grey, Label L="") {
113 this.lc.operator init(center=center, radius=radius, outline=outline,
118 pair center() { return this.lc.center; }
119 void set_center(pair center) { this.lc.center = center; }
120 void draw(picture pic=currentpicture) = this.lc.draw;
133 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="") {
134 this.center = center;
140 this.height = height;
141 this.direction = direction;
142 this.outline = outline;
147 void draw(picture pic=currentpicture) {
149 path c = rotate(direction)*scale(width,height)*shift(-.5,-.5)*unitsquare;
150 filldraw(picF, c, fill, outline);
151 label(pic=picF, L=L, position=(0,0));
152 add(pic, picF, center);
156 // ---------------------- Vectors -------------------------
161 real dir; // angle in the plane of the drawing.
162 real phi; // angle with the plane of the drawing, 90 is out of the page.
165 real out_of_plane_radius;
166 real out_of_plane_tolerance;
168 void operator init(pair center=(0,0), real mag=5mm, real dir=0, real phi=0, pen outline=currentpen, Label L="") {
169 this.center = center;
173 this.outline = outline;
175 this.out_of_plane_radius = 1mm;
176 this.out_of_plane_tolerance = 0.01;
180 Vector v = Vector(center=this.center, mag=this.mag, dir=this.dir,
181 phi=this.phi, outline=this.outline, L=this.label);
182 v.out_of_plane_radius = this.out_of_plane_radius;
183 v.out_of_plane_tolerance = this.out_of_plane_tolerance;
187 pair dTip() { // offset from center to tip
189 real phi_e = this.phi % 360; // effective phi
190 if (Tan(phi_e) == 0 || abs(1.0/Tan(phi_e)) > this.out_of_plane_tolerance) {
191 return this.mag*Cos(this.phi)*dir(this.dir);
197 return this.dTip() + this.center;
200 void draw(picture pic=currentpicture) {
201 pair p = this.dTip();
203 real phi_e = this.phi % 360; // effective phi
204 if (this.mag < 0) (phi_e + 180) % 360;
205 if (Tan(phi_e) == 0 || abs(1.0/Tan(phi_e)) > this.out_of_plane_tolerance) {
206 // draw arrow in the plane of the drawing
207 // TODO: thickening for phi?
208 P = shift(this.center)*((0,0)--p);
209 draw(pic, P, this.outline, Arrow);
210 } else if (phi_e > 0 && phi_e < 180) {
211 // draw a circled dot for out-of-the-page
212 P = shift(this.center)*scale(this.out_of_plane_radius)*unitcircle;
213 draw(pic, P, outline);
214 dot(pic, this.center, this.outline);
216 // draw a circled cross for into-the-page
217 real a = 0.8*sqrt(2.0)/2.0;
218 P = shift(this.center)*scale(this.out_of_plane_radius)*unitcircle;
219 draw(pic, P, this.outline);
220 draw(pic, shift(this.center)*scale(this.out_of_plane_radius
221 )*((-a,-a)--(a,a)), this.outline);
222 draw(pic, shift(this.center)*scale(this.out_of_plane_radius
223 )*((-a,a)--(a,-a)), this.outline);
225 label_path(pic=pic, L=this.label, g=P);
229 Vector operator +(Vector a, Vector b) {
231 pair p = a.mag*dir(a.dir) + b.mag*dir(b.dir);
237 Vector operator -(Vector a, Vector b) {
239 pair p = a.mag*dir(a.dir) - b.mag*dir(b.dir);
245 void vector_field(pair center=(0,0), real width=2cm, real height=2cm,
246 real dv=0.5cm, real buf=2pt, Vector v=null,
247 pen outline=invisible) {
248 /* There will be a buffer of at least buf on each side */
250 v = Vector(); // unlikely to be what they want, but it will draw something
253 pair ovcenter = v.center;
255 path bufsq = shift(center)*xscale(width-2*buf)*yscale(height-2*buf)
256 *shift((-.5,-.5))*unitsquare; // buffered bounding box
257 pair uv = dir(v.dir); // unit vector in the direction of v
258 pair d = dv * dir(v.dir+90);
261 int nx = 1; // x steps
262 int ny = 1; // y steps
265 if (abs(fmod(v.phi, 180)) == 90) { // pure in/out, make a 2D grid
266 dx = dy = dv; // v.dir was meaninless, reset dx and dy
267 nx = abs((int)((width-2*buf) / dx)) + 1;
268 ny = abs((int)((height-2*buf) / dy)) + 1;
269 } else if (abs(fmod(v.dir, 180)) == 0) { // pure left/right, vert. border
270 ny = abs((int)((height-2*buf) / dy)) + 1;
272 } else if (abs(fmod(v.dir, 180)) == 90) { // pure up/down, horiz. border
273 nx = abs((int)((width-2*buf) / dx)) + 1;
275 } else { // diagonal, draw along a vertical an horizontal border
277 // this requires enough special handling that we break it out below
280 if (!diag) { // square grid
281 real xx=buf, xy=buf; // buffer distace per side
283 xx = (width-(nx-1)*fabs(dx))/2.0; // "extra" left over after division
285 xy = (height-(ny-1)*fabs(dy))/2.0;
287 real xstart = center.x - width/2 + xx;
288 real ystart = center.y - height/2 + xy;
289 if (dx < 0 || (dx == 0 && dot(uv, dir(0)) < 0))
290 xstart += width - 2*xx;
291 if (dy < 0 || (dy == 0 && dot(uv, dir(90)) < 0))
292 ystart += height - 2*xy;
294 for (int i=0; i<nx; i+=1) {
295 for (int j=0; j<ny; j+=1) {
296 v.center = (xstart+i*dx, ystart+j*dy);
297 if (abs(fmod(v.phi, 180)) != 90) { // pure left/right/up/down
298 // set length so that v just touches far bufsq face
299 path vlong = (v.center+uv*min(width, height)/2)
300 --(v.center+uv*max(width, height));
301 //dot(v.center); draw(vlong); continue; // intersection debugging
302 pair xs[] = intersectionpoints(vlong, bufsq);
303 assert(xs.length == 1, format("%d", xs.length));
304 v.mag = length(xs[0] - v.center);
309 } else { // v is diagonal
310 pair cc = (sgn(d.x)*width, sgn(d.y)*height); // catty-corner vector
311 real ccbuf = buf*max(fabs(1/cos(angle(cc))), // buf away in y
312 fabs(1/sin(angle(cc)))); // buf away in x
313 int n = abs((int)((dot(cc, unit(d))-2*ccbuf)/length(d))) + 1; // lines
314 pair pstart = center - (n-1)*d/2.0;
316 for (int i=0; i<n; i+=1) {
317 // project along +/- v until you hit the edge of bufsq
318 pair p = (pstart + i*d);
319 path vlong = (p-uv*length(cc))--(p+uv*length(cc)); // extends out of box
320 //dot(p); draw(vlong); continue; // intersection debugging
321 pair xs[] = intersectionpoints(vlong, bufsq);
322 assert(xs.length == 2, format("%d", xs.length));
324 if (dot((start - xs[1]), uv) > 0)
327 v.mag = length(xs[1]-xs[0]);
332 v.center = ovcenter; // restore original center
333 v.mag = ovmag; // restore original magnitude
336 draw(shift(center)*xscale(width)*yscale(height)*shift((-.5,-.5))*
337 unitsquare, outline);
340 Vector Velocity(pair center=(0,0), real mag=5mm, real dir=0, real phi=0, Label L="")
342 Vector v = Vector(center=center, mag=mag, dir=dir, phi=phi, L=L, outline=rgb(1,0.1,0.2)); // red
346 // ---------------------- Forces -------------------------
348 Vector Force(pair center=(0,0), real mag=5mm, real dir=0, real phi=0, Label L="")
350 Vector v = Vector(center=center, mag=mag, dir=dir, phi=phi, L=L, outline=rgb(0.1,0.2,1)); // blue
354 // ---------------------- Measures -------------------------
356 // Distance derived from CAD.MeasuredLine
365 void operator init(pair pFrom=(0,0), pair pTo=(5mm,0), real offset=0, real scale=5mm, pen outline=currentpen, Label L="") {
368 this.offset = offset;
370 this.outline = outline;
374 void draw(picture pic=currentpicture) {
375 pair o = this.offset*unit(rotate(-90)*(this.pTo - this.pFrom));
376 path p = (this.pFrom+o) -- (this.pTo+o);
377 draw(pic, p, outline, Arrows);
378 label_path(pic=pic, L=this.label, g=p, rdir=this.pTo - this.pFrom);
384 pair A; // center of angle
386 real radius; // radius < 0 for exterior angles.
391 void operator init(pair B, pair A, pair C, real radius=5mm, pen outline=currentpen, pen fill=invisible, Label L="") {
395 this.radius = radius;
396 this.outline = outline;
401 void draw(picture pic=currentpicture) {
404 real ccw_angle = degrees(C-A)-degrees(B-A);
405 bool direction = CCW;
406 if (ccw_angle < 0) ccw_angle += 360.0;
410 direction = !direction;
411 path p = arc(this.A, fabs(radius), degrees(B-A), degrees(C-A), direction);
412 if (this.fill != invisible) {
413 path pcycle = this.A -- p -- cycle;
414 fill(pic, pcycle, this.fill);
416 draw(pic, p, this.outline);
417 if (direction == CW) {
420 label_path(pic=pic, L=this.label, g=p);
424 Vector hatVect (string name, pair center=(0,0), real dir=0, real phi=0) {
425 string s = replace("$\mathbf{\hat{X}}$", "X", name);
426 Label L = Label(s, position=EndPoint, align=RightSide);
428 center=center, mag=5mm, dir=dir, phi=phi, L=L, outline=rgb(0,0,0));
432 Vector ihat (pair center=(0,0), real dir=0, real phi=0) {
433 Vector v = hatVect(name="i", center=center, dir=dir, phi=phi);
437 Vector jhat (pair center=(0,0), real dir=90, real phi=0) {
438 Vector v = hatVect(name="j", center=center, dir=dir, phi=phi);
442 void draw_ijhat(pair center=(0,0), real idir=0) {
443 Vector ihat = ihat(center, idir);
444 Vector jhat = jhat(center, idir+90);
449 // ---------------------- Shapes -------------------------
457 void operator init(pair pFrom=(0,0), pair pTo=(5mm,0), pen outline=currentpen, Label L="") {
460 this.outline = outline;
464 void draw(picture pic=currentpicture) {
465 path p = this.pFrom--this.pTo;
466 draw(pic, p, outline);
467 label_path(pic=pic, L=this.label, g=p, rdir=this.pTo - this.pFrom);
480 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="") {
483 this.thickness = thickness;
484 this.outline = outline;
490 void draw(picture pic=currentpicture) {
491 pair pDiff = pTo - pFrom;
492 pair pDepth = rotate(-90)*unit(pDiff)*thickness;
493 path p = (0, 0) -- pDiff -- (pDiff + pDepth) -- pDepth -- cycle;
494 p = shift(this.pFrom) * p;
496 pic=pic, g=p, pena=filla, a=this.pFrom,
497 penb=fillb, b=this.pFrom+pDepth);
498 draw(pic, p, outline);
499 label_path(pic=pic, L=this.label,
500 g=shift(pDepth/2)*(this.pFrom -- this.pTo), rdir=this.pTo - this.pFrom);
509 real dLength; // length of a single loop (when unstretched)
510 real deadLength; // length before loops start on each end
511 real unstretchedLength;
516 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="") {
521 this.dLength = dLength;
522 this.unstretchedLength = unstretchedLength;
523 this.outline = outline;
526 this.nLoops = floor((unstretchedLength-2*deadLength)/dLength);
527 if (this.nLoops == 0)
529 this.deadLength = (unstretchedLength-this.nLoops*dLength)/2;
532 void draw(picture pic=currentpicture) {
533 pair pDiff = pTo - pFrom;
535 real working_dLength = (length(pDiff) - 2*deadLength) / nLoops;
536 path p = (0,0)--(deadLength,0);
538 --(working_dLength/4, width/2)
539 --(working_dLength*3/4, -width/2)
540 --(working_dLength, 0);
542 for (int i=0; i<nLoops; ++i) {
543 loopStart = point(p, length(p));
544 p = p--(shift(loopStart)*loop);
546 loopStart = point(p, length(p));
547 p = p--(loopStart+(deadLength,0));
548 p = shift(this.pFrom)*rotate(degrees(pDiff)) * p;
549 draw(pic, p, outline);
550 label_path(pic=pic, L=this.label, g=this.pFrom--this.pTo,
551 margin=this.width/2, rdir=this.pTo - this.pFrom);
561 void operator init(pair pivot=(0,0), Mass mass=Mass()) {
564 this.angle = Angle(mass.center(), pivot, pivot-(0,1));
565 this.str = Wire(pivot, mass.center());
568 void draw(picture pic=currentpicture, bool drawVertical=false) {
570 if (drawVertical == true) {
571 pair final = pivot + realmult((0,0.5),(mass.center()-pivot));
572 draw(pic=pic, pivot--final, p=currentpen+dashed);
574 draw(pic=pic, pivot);
580 // The angle argument is deflection from straight down (i.e. 0 degrees = plumb)
581 Pendulum makePendulum(pair pivot=(0,0), Mass mass=Mass(), real length=15mm, real angleDeg=0, Label angleL="", Label stringL="") {
582 mass.set_center(pivot + length*dir(angleDeg-90));
583 Pendulum p = Pendulum(pivot=pivot, mass=mass);
584 p.angle.label = angleL;
585 p.str.label = stringL;
602 void operator init(triple center=(0,0,0), triple normal=(0,0,1),
603 real radius=5mm, real width=1mm, real axis_pre=0,
604 real axis_post=0, pen outline=currentpen,
605 pen fill=invisible, pen axis=invisible, Label L="",
606 Label axis_label="") {
607 this.center = center;
608 this.normal = normal;
609 this.radius = radius;
611 this.axis_pre = axis_pre;
612 this.axis_post = axis_post;
613 this.outline = outline;
617 this.axis_label = axis_label;
620 void draw(picture pic=currentpicture) {
621 path3 a = shift(this.center)*(
622 (-this.axis_pre*this.normal)--(this.axis_post*this.normal));
623 draw(pic, a, this.axis, Arrow3);
624 path3 c = circle(c=this.center, r=this.radius, normal=this.normal);
625 //tube t = tube(c, width=this.width); // slow and ugly!
626 //draw(t.s, this.fill);
627 draw(pic, c, this.outline);
628 label_path(pic=pic, L=this.label, g=c);
629 label_path(pic=pic, L=this.axis_label, g=a);
633 // TODO: plate, cylinder, table