5

I am new to Asymptote and I need a little help. I would like to place a label exactly in the center of a closed path, for example in a triangle:

unitsize(1cm);
size(4cm);
draw((0, 0) -- (0, 3) -- (3, 0) -- cycle);
label("L", (1, 1));

I searched the documentation for a way to compute the center point but could not find anything. So my question is: Is it possible to find the center of the convex hull of a path or do I have to compute it manually?

Thank you,

Adrian

lockstep
  • 250,273
awaelchli
  • 195

2 Answers2

4

Asymptote does not even (currently) provide a function for computing a convex hull, much less its centroid (which I am assuming is what you mean by "center"). If you want to write your own function, you probably want to assume the cyclic path is in fact a closed, non-self-intersecting polygon. In that case, use can use this formula for the centroid, and you main find the following function useful:

// Extract the vertices of a path (cyclic or otherwise)
pair[] vertices(path g) { 
  return sequence(new pair(int i) { return point(g,i); }, 
                  size(g)); 
}
3

I happen to have functions I wrote a while back that might help you. The first function returns the convex hull of an array of points. You might use the function provided by @CharlesStaats to get the array of points in your original path. As-is, the convexHull function only works for piecewise linear paths.

path convexHull(pair[] in_pset)
{
    pair[] pset = copy(in_pset);

    if (pset.length == 0) { path hull; return hull; }

    { // remove duplicate points
        int indexDelete = 1;
        while (indexDelete > 0)
        {
            indexDelete = -1;
            for (int i = 1; i < pset.length; ++i)
            {
                for (int j = 0; j < i; ++j)
                {
                    if (pset[i] == pset[j])
                    {
                        indexDelete = i;
                        break;
                    }
                }
                if (indexDelete > 0) { break; }
            }
            if (indexDelete > 0) { pset.delete(indexDelete); }
        }
    }

    path hull;

    { // add point at min y (and min x if tie) to hull, delete point from pset
        int minIndex = 0;

        for (int i = 1; i < pset.length; ++i)
        {
            if (pset[i].y < pset[minIndex].y ||
                    (pset[i].y == pset[minIndex].y && pset[i].x < pset[minIndex].x))
            {
                minIndex = i;
            }
        }
        hull = pset[minIndex];
        pset.delete(minIndex);
    }

    while (pset.length > 0)
    {
        { // add next point to hull
            real minAngle = 361.0;
            int minAngleIndex = 0;
            for (int i = 0; i < pset.length; ++i)
            {
                real angle = degrees(pset[i] - relpoint(hull, 1.0), false);
                if (angle < minAngle)
                {
                    minAngle = angle;
                    minAngleIndex = i;
                }
            }
            hull = hull--pset[minAngleIndex];
            pset.delete(minAngleIndex);
        }

        { // remove points interior to current hull from pset
            path tempHull = hull--cycle;
            int[] deleteIndeces;
            for (int i = pset.length - 1; i > -1; --i)
            {
                if (inside(tempHull, pset[i])) { deleteIndeces.push(i); }
            }
            for (int i = 0; i < deleteIndeces.length; ++i)
            {
                pset.delete(deleteIndeces[i]);
            }
        }
    }
    return hull--cycle;
}

Now you can use a Monte Carlo technique to find the center of gravity (CG) of the convex hull path as follows.

pair pathCG(path p, int numTestPoints)
{
    int numInside = 0;

    pair bl = min(p);
    pair tr = max(p);

    pair sumPair = (0,0);

    for (int i = 0; i < numTestPoints; ++i)
    {
        pair testPoint = (
            unitrand() * (tr.x - bl.x) + bl.x,
            unitrand() * (tr.y - bl.y) + bl.y
            );

        if (inside(p, testPoint))
        {
            sumPair = sumPair + testPoint;
            ++numInside;
        }
    }

    pair CG = (0,0);
    if (numInside > 0) { CG = sumPair / numInside; }

    return CG;
}

The following code will use the functions above to draw your path in black, the convex hull of that path in dashed red, and the hull CG with the blue dot. As mentioned, curvature of the original path is not supported by the convexHull function.

unitsize(1inch);
// vertices function here
// convexHull function here
// pathCG function here
path origPath = (0,0)--(3,0){NNE}..(3,2)--(1.5,3)--(1,1)--(0,1.7)--(0.3,1)--cycle;
draw(origPath);
dot(origPath);
pair[] pset = vertices(origPath);
path hull = convexHull(pset);
draw(hull, dashed+red);
dot(Label("CG of hull"), pathCG(hull, 500), S, 6+blue);

enter image description here

For labeling angles as you mention in your comment, you might use a function like angleLabel in the following example. It places a label within an angle specified by three points as specified distance from the middle point, with an optional arrow drawn.

unitsize(1inch);

void angleLabel(string s, pair p1, pair p2, pair p3,
    real offset = 0.5, bool drawArrow = true)
{
    real angle1 = degrees(p1-p2);
    real angle2 = degrees(p3-p2);
    real midAngle = (angle1 + angle2) / 2.0;
    pair labelPos = shift(p2)*rotate(midAngle)*(offset,0);
    if (drawArrow)
    {
        draw(arc(p2, offset, angle1, angle2), Arrows);
    }
    label(s, labelPos, UnFill);
}

pair a = (2,0);
pair b = (0,0);
pair c = (-1,1);

label("$a$", a, S);
label("$b$", b, S);
label("$c$", c, N);

path p = a--b--c;

draw(p);
dot(p);

angleLabel("$B$", a, b, c, false);
angleLabel("$B$", a, b, c, 0.75);

enter image description here

James
  • 4,587
  • 1
  • 12
  • 27
  • Thank you for the effort you put into this answer. For my small figure, it is probably a bit overkill but I will try it out. I'm disappointed that this is not built into asymptote, since I often have to label angles/arcs in my drawings and sometimes it can be hard to center them correctly. – awaelchli Jun 11 '15 at 13:43
  • 1
    You're welcome. It wasn't much effort because I already had the functions. Regarding the overkill... you can place these functions in an asy fill in your asymptote installation directory. Then you can import the functions into your file using a single include statement. After that, calling the functions is as simple as using built-in commands. BTW, in your example with a triangle the CG is the average of the corner nodes. pair p1=(0,0); pair p2=(3,0); pair p3=(0,3); pair CG=(p1+p2+p3)/3.0; – James Jun 11 '15 at 14:49
  • @aeduG: See the added code in my answer for a potential solution for labeling angles. – James Jun 11 '15 at 15:32
  • Labeling the angles this way is very cool, I like it. I should use functions more often like you do. Also, thanks about the tip for importing functions! – awaelchli Jun 11 '15 at 16:59
  • @James: Please consider answering http://tex.stackexchange.com/q/249860/484 – Charles Staats Jun 12 '15 at 02:52
  • @CharlesStaats: Thanks for the heads-up. – James Jun 12 '15 at 11:35