Metafont/Metapost — Find a path tangent through the external point

Assuming that I have the path P given, and let the T be a point outside the path P. Is there an easy way to find the point X on the path P such that the tangent on point X going through the point T?

Example in Metapost:

outputformat := "svg";
outputtemplate := "%j-%c.svg";
beginfig(1)
pair T,X; path P;
T:=(800,900); P:=(0,0)..(300,100)..(200,600);
draw P withpen pencircle scaled 10;
draw T withpen pencircle scaled 20 withcolor red;
endfig; end

Basically, what I need is: X = directionpoint (T-X) of P (this is, of course, an error).

The only solution I can think of is to check an every point on the path until I found it, something like:

numeric v; v:=0;
forever: X:=point v of P;
exitif abs(angle(T-X) - angle(direction v of P)) < 0.001; % for example
v:=v+epsilon; endfor;

Of course, this specific code will not work in the every case. Plus, it's very slow.

Is there a better solution? Of course, in a general case it's possible that there is no the solution, or more than just one solution.

• we can probably do it with the solve macro Nov 5 '21 at 19:02
• The arc is a general path rather than the arc of a circle? Nov 5 '21 at 19:05

Try this:

outputformat := "svg";
outputtemplate := "%j-%c.svg";
beginfig(1);
pair T,X; path P;
T:=(800,900); P:=(0,0)..(300,100)..(200,600);
draw P withpen pencircle scaled 10;
draw T withpen pencircle scaled 20 withcolor red;

vardef f(expr t) = angle direction t of P - angle (T - point t of P) < eps enddef;
X = point solve f (0, 2) of P;
draw X -- T withpen pencircle scaled 5 withcolor red;

endfig; end

which gets me this (although I was using eps as the output format and converting it to png to get this actual pic) The solve macro is explained in The Metafont Book pp.176-177. Paraphrasing, the macro uses a binary search to find a numeric (brute force) solution to a non-linear equation. You have to define a macro with a single parameter that returns either true or false. You then pass the name of your macro to solve as a suffix, followed by a pair (a, b) such that f(a) is true and f(b) is false. The solve macro returns a value between a and b that is "at the cutting edge between truth and falsity".

You can find the macro in your local copy of plain.mp:

vardef solve@#(expr true_x,false_x)=
tx_:=true_x; fx_:=false_x;
forever: x_:=.5[tx_,fx_]; exitif abs(tx_-fx_)<=tolerance;
if @#(x_): tx_ else: fx_ fi :=x_; endfor
x_ enddef; % now x_ is near where @# changes from true to false
newinternal tolerance, tx_,fx_,x_; tolerance:=.01;

Notice how the passed macro is called with @#(x_)...

• Great solution! Of course, it's not an universal solution, but for the my purposes it's absolutely perfect!
– Урош
Nov 7 '21 at 14:32