16
$\begingroup$

I'm trying to write a procedure or function to find the direction in which a 2D triangle points. The triangle is assumed to be isosceles. While I can see the basic outline of what I want to do, making this into a procedure/function is proving harder. This is very basic Mathematica programming, I think... :(

Suppose that points p, q, and r are three arbitrary 2D points assumed to form an isosceles triangle:

a triangle

triangles = 
{
  {{30.07, 11.04}, {20.07, 35.905}, {40.905, 19.095}}, 
  {{82.918, 26.9417}, {77.5077, 43.5925}, {88.3281, 43.5925}}}

aTriangle =  First[triangles];  
{p, q, r} = aTriangle

(* 
{{30.07, 11.04}, {20.07, 35.905}, {40.905, 19.095}}
*)

The side lengths are found easily enough:

{EuclideanDistance[p, q],
 EuclideanDistance[p, r],
 EuclideanDistance[q, r]}

(* 
{26.8005, 13.5011, 26.7708}
*)

and clearly in this case pr is the different side that helps define the direction:

midPoint = {(p[[1]] + r[[1]])/2 , (p[[2]] + r[[2]])/2}

(* 
{35.4875, 15.0675}
*)

Then all that's left is to find the angle of the line Line[{midPoint, q}], which is probably something like this:

ArcTan[q[[1]] - midPoint[[1]], q[[2]] - midPoint[[2]]] / Degree

(* 
126.497
*)

But I want this to be automated, and I can't see how to do this.

$\endgroup$

3 Answers 3

19
$\begingroup$

Given that the triangle might not exactly be isosceles, let's characterize this direction as being from the triangle's center towards the most distant vertex:

ClearAll[direction];
direction[t_List] := 
 With[{center = Mean[t]}, 
  t[[First[Ordering[N[Norm /@ (# - center & /@ t)], -1]]]] - center]

(Edit As @R.M. notes, N needs to be applied for Ordering to work consistently and correctly.)

One advantage of this approach is that it works for any polygon. Another is that it does not require any additional specification such as a tolerance for testing approximate equality of sides.

As an example,

t = {{30.07, 11.04}, {20.07, 35.905}, {40.905, 19.095}};
center = Mean[t];
Graphics[{White, EdgeForm[Black], Polygon[t], Red, Arrow[{center, center + direction[t]}]}]

Figure

Note that the direction is given as a vector (which carries the additional information about the distance from the triangle's center towards this extreme vertex). For other forms of output, this is readily converted into a unit vector (by normalizing it) or an angle.


Edit

Notice that for squat, flat isosceles triangles (where the apex angle exceeds 60 degrees), this solution determines that the "pointing direction" is from the center towards one of the base vertices, not towards the apex. If that is not the intention, then additional analysis is needed. One way is to determine that among all the possible directions from the center, the one most different from the other two is the one to pick:

direction[t_List] := Block[{center = Mean[t], s, l, a},
  s = # - center & /@ t;
  l = Norm /@ s;
  a = Abs[RotateLeft[l, 1] - RotateLeft[l, 2]];
  s[[First[Ordering[N[a], 1]]]]
  ]

In this implementation (which is written for clarity rather than brevity), s lists the three possible directions, l lists their lengths, and for each entry in l, a lists the differences in lengths between the other two. The last line returns the direction where that difference is the smallest (as a vector from the center to the vertex, exactly as before).

$\endgroup$
9
  • 2
    $\begingroup$ The "center towards most distant vertex" approach won't work for flatish isoceles triangles... for example a triangle with 1°, 1° and 178° as angles. $\endgroup$
    – rm -rf
    Commented Apr 16, 2012 at 16:55
  • $\begingroup$ @R.M Or even 62°-59°-59°. (To see this set up a Manipulate using t = {{e, e}, {1, 0}, {0, 1}} and vary e between -1 and 1, say.) $\endgroup$ Commented Apr 16, 2012 at 17:27
  • $\begingroup$ @whuber Thanks for the answer - neat idea. Luckily the edge cases aren't an issue for my application. :) $\endgroup$
    – cormullion
    Commented Apr 16, 2012 at 17:50
  • $\begingroup$ @R.M. You are correct--thank you--but not for the reason you might think! The problem is that Ordering is different for exact values than it is for numerical values. (Although this is well documented behavior, it's a subtle and nasty one IMHO.) One solution is to convert the norms to machine numbers. $\endgroup$
    – whuber
    Commented Apr 16, 2012 at 18:39
  • $\begingroup$ @whuber I wasn't referring to Ordering's behaviour... rather that the most distant vertex (or vertices) in such cases would be the two vertices whose subtended angles are equal, rather than the third vertex which is what the OP wants. $\endgroup$
    – rm -rf
    Commented Apr 16, 2012 at 18:44
5
$\begingroup$

If the triangle is isosceles, the corresponding median line is orthogonal to the side. Therefore I think the following should be the most robust solution (note that it also works for triangles where the arrow tip angle is larger than 60 degrees, however it assumes the triangle is not degenerate; also for equilateral triangles it will return just one of the three possible directions):

direction[t:{{_,_},{_,_},{_,_}}] := ArcTan@@
  (Sort[Table[Module[{tipcand  = t[[i]],
                      basecand = t[[Range[3]~Complement~{i}]]},
    ({Abs[#1.#2]/Sqrt[(#1.#1)(#2.#2)],#1}&)[tipcand-Plus@@basecand/2,
                                            Subtract@@basecand]],
              {i, 3}]][[1,2]])/Degree
$\endgroup$
4
$\begingroup$

I am taking here the simplest approach and simply putting the reasoning you followed above into a single module:

dir[t_, prec_] := Module[{d1, d2, d3},
  d3 = EuclideanDistance[t[[1]], t[[2]]];
  d2 = EuclideanDistance[t[[1]], t[[3]]];
  d1 = EuclideanDistance[t[[2]], t[[3]]];
  v = If[Abs[d1 - d2] < prec*(d1 + d2), Normalize[t[[3]] - (t[[1]] + t[[2]])/2],
       If[Abs[d1 - d3] < prec*(d1 + d3), Normalize[t[[2]] - (t[[1]] + t[[3]])/2],
        If[Abs[d3 - d2] < prec*(d3 + d2),  Normalize[t[[1]] - (t[[3]] + t[[2]])/2],
         Null]]];
  ArcTan[v[[1]], v[[2]]]
]

The function takes a triangle as first argument, and compares its length with some precision (for your input data, 0.1% is a good choice of precision, so 0.001). Then calculates the direction as a vector (v) and return its argument with ArcTan's two-argument variant.

In[21]:= Map[dir[#, 0.001]/Degree &, triangles]
Out[21]= {126.497, -89.9997}
$\endgroup$
1
  • $\begingroup$ Thanks - and your descriptive explanation is appreciated, too! $\endgroup$
    – cormullion
    Commented Apr 16, 2012 at 17:57

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.