Simplify Angle labeling to respect Label attributes in Mechanics.asy.
[course.git] / asymptote / Mechanics.asy
index f3f3e01f53d25fd0fbbc657c220f7faeaa4d30bc..96e1a9bf72588f8dd2b95d8946f6ad20fe032dd9 100644 (file)
@@ -1,33 +1,86 @@
+/* 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;
@@ -66,49 +119,196 @@ struct Block {
 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;
 }
 
@@ -118,35 +318,30 @@ Vector Force(pair center=(0,0), real mag=5mm, real dir=0, Label L="")
 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;
   }
 }
 
@@ -156,24 +351,23 @@ struct Angle {
   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;
@@ -182,40 +376,42 @@ struct Angle {
     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 {
@@ -243,9 +439,9 @@ 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);
   }
 }
@@ -281,11 +477,11 @@ struct Surface {
     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);
   }
 }
@@ -345,9 +541,9 @@ struct Spring {
       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);
   }
 }
@@ -361,14 +557,14 @@ struct Pendulum {
   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);
@@ -379,12 +575,12 @@ struct Pendulum {
 
 // 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