[metapost] new is_clockwise routine

Werner LEMBERG wl at gnu.org
Thu Nov 24 17:12:27 CET 2005


Dear hobby drawers,


a longer time ago we discussed how to circumvent the turning number
bug in metapost to (re)implement the functions `clockwise' and
`counterclockwise' in mf2pt1.  We finally came up with the following
solution:


----------------------------------------------------------------------


vardef Angle primary d =
  if d <> (0, 0):
    angle d
  else:
    0
  fi
enddef;

vardef postdir expr t of p =    % t is expected to be an integer.
  save a, b, c, d, s;
  pair a, b, c, d;
  path s;

  s := subpath (t, t + 1) of p;

  a = point 0 of s;
  b = postcontrol 0 of s;
  c = precontrol 1 of s;
  d = point 1 of s;

  if a <> b:
    b - a
  elseif a <> c:
    c - a
  elseif a <> d:
    d - a
  else:
    (0, 0)
  fi
enddef;

vardef is_clockwise primary p =
  save res;

  res := 0;

  for t = 0 upto length p - 1:
    res := res
	   + (-Angle (postdir t of p)
	      + Angle (postdir t + 1 of p) + 180) mod 360
	   - 180;
  endfor;

  res <= 0
enddef;

vardef counterclockwise primary c =
  (if is_clockwise c: (reverse c) else: c fi)
enddef;

vardef clockwise primary c =
  (if is_clockwise c: c else: (reverse c) fi)
enddef;


----------------------------------------------------------------------


Unfortunately, this doesn't work, as I've found out meanwhile.  As an
example, it fails for this curve:

  (0,0)
  .. controls (10,-30) and (10,40).. (0,10)
  -- cycle;

Reason is that the above algorithm can't recognize properly that a
Bézier curve element in the path turns more than 180 degrees.  I tried
hard, but I couldn't find a simple solution to overcome this flaw
reliably.

Below I present a completely different solution which should work for
all curves which don't have a self-intersection.  It simply searches
the outermost intersection point of the path with an infinitely long
horizontal line and determines its direction.

Please comment.  It seems to run reasonably fast (at least I couldn't
find a greate difference to the old algorithm), but maybe it can be
streamlined.


    Werner


======================================================================


vardef crossproduct (expr u, v) =
  save u_, v_;
  pair u_, v_;

  u_ := unitvector u;
  v_ := unitvector v;

  abs (xpart u_ * ypart v_ - ypart u_ * xpart v_)
enddef;

vardef makeline primary p =
  save start, loop, d, n;
  pair start, d;

  loop := 0;
  for i = 0.5 step 1 until length p - 0.5:
    % add some randomness to get different lines for each function call
    n = uniformdeviate 0.9 - 0.45 + i;
    start := point n of p;
    d := direction n of p;
    if d <> (0, 0):
      loop := 1;
    fi;
    exitif loop = 1;
  endfor;

  if loop = 0:
    errmessage ("Cannot find a starting point on path for orientation test");
  fi;

  d := unitvector (d rotated 90);

  % construct line orthogonal to tangent which intersects path at least once
  start - eps * d
  -- infinity * d
enddef;

vardef is_clockwise primary p =
  save line, cut, cut_new, res, line_dir, tangent_dir;
  path line;
  pair cut, cut_new, line_dir, tangent_dir;

  res := 1;
  line := makeline p;
  line_dir := direction 0 of line;

  % find outermost intersection
  cut := (0, 0);
  forever:
    cut_new := line intersectiontimes p;
    exitif cut_new = (-1, -1);

    % compute new line if we have a strange intersection
    tangent_dir := direction (ypart cut_new) of p;

    if abs tangent_dir < eps:
      % vector zero or too small
      line := makeline p;
      line_dir := direction 0 of line;
    elseif crossproduct (tangent_dir, line_dir) < 0.02:
      % grazing intersection
      line := makeline p;
      line_dir := direction 0 of line;
    else:
      cut := cut_new;
      line := subpath (xpart cut + eps, infinity) of line;
    fi;
  endfor;

  tangent_dir := direction (ypart cut) of p;
  res := (angle tangent_dir - angle line_dir + 180) mod 360 - 180;

  res < 0
enddef;

vardef counterclockwise primary c =
  (if is_clockwise c: (reverse c) else: c fi)
enddef;

vardef clockwise primary c =
  (if is_clockwise c: c else: (reverse c) fi)
enddef;



More information about the metapost mailing list