Next: , Previous: A technical drawing, Up: Building a drawing



4.3 A hierarchical model

While sketch was never meant to be a geometric modeling language, it comes fairly close. The following example puts all we have seen to work in a very simple model of the human hand. Start by sweeping a line to make a truncated cone, which will be copied over and over again to make the segments of fingers.

  def O (0,0,0) % origin
  def I [1,0,0] def J [0,1,0] def K [0,0,1] % canonical unit vectors
  def segment {
    def n_faces 8
    sweep { n_faces<>, rotate(360 / n_faces, [J]) } 
      line(proximal_rad, 0)(distal_rad, distal_len)
  }
In hand anatomy, distal is “at the tip” and proximal is “in the area of the palm.” We have omitted all the scalar constants. You can find them in hand.sk, which is provided in the sketch distribution.

We also need a prototypical sphere to use for the joints themselves.

  def joint_sphere {
    def n_joint_faces 8
    sweep [fillcolor=red] { n_joint_faces, rotate(360 / n_joint_faces, [J]) }
      sweep { n_joint_faces, rotate(180 / n_joint_faces) } 
        (0, -joint_rad)
  }

We'll now design the index finger (number 1 in our notational convention; finger 0 is the thumb). The distal rotation for the finger applies only to the tip, so we define the following.

  def distal_1 {
    put { translate(joint_gap * joint_rad * [J]) 
          then rotate(distal_1_rot, [I]) 
          then translate((distal_len + joint_gap * joint_rad) * [J]) }
      {segment}
    put { rotate(distal_1_rot / 2, [I])
          then translate((distal_len + joint_gap * joint_rad) * [J]) } 
      {joint_sphere}
    put { scale( [J] + proximal_distal_ratio * ([I]+[K]) ) }
      {segment}
  }  
The identifiers here are for size and location constants. The exception is distal_rot_1. This rotation parameter models the flexing of the finger tip. The first put makes a copy of the finger segment that is translated upward just far enough to make room for the spherical joint. Then it applies the distal rotation. Finally it translates the whole assembly upward again to make room for the middle phlanges (the next bone toward the palm). The second put positions the sphere. There is a rotation to place the grid on the sphere surface at an nice angle, then a translation to the base of the distal phlanges, which is also center of its rotation. Finally, the last put positions the middle segment itself.

The middle joint is the next one down, with rotation angle middle_rot_1. When this angle changes, we need all the objects in distal_1 to rotate as a unit. This is the reasoning behind the next definition.

  def finger_1 {
    put { translate(joint_gap * joint_rad * [J])
          then rotate(middle_1_rot, [I])
          then translate((middle_ratio * distal_len + 
                          joint_gap * joint_rad) * [J]) }
      {distal_1}
    put { scale(proximal_distal_ratio)
          then rotate(middle_1_rot / 2, [I])
          then translate((middle_ratio * distal_len + 
                          joint_gap * joint_rad) * [J]) } 
      {joint_sphere}
    put { scale( middle_ratio * [J] + 
                 proximal_distal_ratio^2 * ([I]+[K]) ) }
      {segment}
  }
This looks very similar to the previous definition, and it is. The important difference is that rather than positioning and rotating a single segment, we position and rotate the entire “assembly” defined as distal_1. The rest is just arithmetic to compute sizes and positions that look nice. The last put places an appropriately shaped segment that is the proximal phlanges, the bone that joins the palm of the hand. This completes the finger itself.

All the other fingers are described identically to this one. We account for the fact that real fingers are different sizes in the next step, which is to build the entire hand.

The hand definition that follows includes a section for each finger. We'll continue with finger 1 and omit all the others. (Of note is that the thumb needs slightly special treatment—an extra rotation to account for its opposing angle. This is clear in the full source code.) Not surprisingly, the hand definition looks very much like the previous two. It should be no surprise that when the rotation parameter meta_1_rot changes, the entire finger rotates! There is an additional rotation that allows the fingers to spread laterally. We say these joints of the proximal phlanges have two degrees of freedom. The joints higher on the finger have only one. Finally, each finger is scaled by a factor to lend it proportion.

  def hand {
    % finger 1 [all other fingers omitted]
    def scale_1 .85
    put { scale(scale_1) 
          then translate((joint_gap * joint_rad) * [J])
	  then rotate(meta_1_rot, [I])
          then rotate(-spread_rot, [K])
          then translate((proximal_1_loc) - (O)) } 
      {finger_1}
    put { scale(scale_1 * proximal_distal_ratio^2)
          then rotate(meta_1_rot / 2, [I])
          then rotate(-spread_rot, [K])
          then translate((proximal_1_loc) - (O)) } 
      {joint_sphere}

    % palm
    sweep { 1, rotate(6, (0,15,0), [I]) }
      put { rotate(-3, (0,15,0), [I]) } {
        polygon(proximal_1_loc)(proximal_2_loc)
               (proximal_3_loc)(proximal_4_loc)
               (h5)(h6)(h6a)(h9)(h10)
        polygon(h6a)(h7)(h8)(h9)
   }  }
The last section of the definition creates the polytope for the palm of the hand by sweeping a 10-sided polygon through a very short arc (9 degrees). This provides a wedge-shaped profile when viewed from the side. The thick end of the wedge is the wrist. Because the polygon is concave, it is split into into two convex shapes with nine and four vertices.

We can now have fun positioning the hand by adjusting the various rotation angles. The complete source includes definitions with alternatives that include the following views and more.

ex210.pngex220.pngex230.pngex240.png