AggieDev
AggieDev

Reputation: 5045

Calculating slopes of a line if it is infinity

I am making something in Java (particularly Android) that displays something like the following:

enter image description here

The user can drag the corners of the box, and as expected, the dashed lines should move accordingly, always having 3 horizontal lines (one half way, one 25%, and one 75% between the top and the bottom) and 3 vertical lines. However, if either the left or right edge is infinity (which it is when the user first sees the screen), it looks something like this:

enter image description here

How can I change my code so that the infinite slope edges are accounted for and still show the desired screen? Here is my code for detecting the positions of where the dashed lines should be, their slopes, and finally them drawn to screen.

//get edge slopes to calculate dashed lines
//c1X is the top left corner X position, c2X is for the top right corner, etc.
            float topLineSlope = (c2Y - c1Y)/(c2X - c1X);
            float rightLineSlope = (c3Y - c2Y)/(c3X - c2X);
            float bottomLineSlope = (c4Y - c3Y)/(c4X - c3X);
            float leftLineSlope = (c1Y - c4Y)/(c1X - c4X);


            //b in y=mx+b
            float topLineB = c1Y - (topLineSlope * c1X);
            float rightLineB = c2Y - (rightLineSlope * c2X);
            float bottomLineB = c3Y - (bottomLineSlope * c3X);
            float leftLineB = c4Y - (leftLineSlope * c4X);

            //final dashed line coordinates
            float topLineMiddleX = (c1X + c2X) / 2.0f;
            float topLineMiddleY = topLineSlope * topLineMiddleX + topLineB;
            float bottomLineMiddleX = (c3X + c4X) / 2.0f;
            float bottomLineMiddleY = bottomLineSlope * bottomLineMiddleX + bottomLineB;
            float leftLineMiddleX = (c4X + c1X) / 2.0f;
            float leftLineMiddleY = leftLineSlope * leftLineMiddleX + leftLineB;
            float rightLineMiddleX = (c2X + c3X) / 2.0f;
            float rightLineMiddleY = rightLineSlope * rightLineMiddleX + rightLineB;

            float topLineLeftX = (c1X + topLineMiddleX) / 2.0f;
            float topLineLeftY = topLineSlope * topLineLeftX + topLineB;
            float bottomLineLeftX = (c4X + bottomLineMiddleX) / 2.0f;
            float bottomLineLeftY = bottomLineSlope * bottomLineLeftX + bottomLineB;
            float topLineRightX = (topLineMiddleX + c2X) / 2.0f;
            float topLineRightY = topLineSlope * topLineRightX + topLineB;
            float bottomLineRightX = (c3X + bottomLineMiddleX) / 2.0f;
            float bottomLineRightY = bottomLineSlope * bottomLineRightX + bottomLineB;

            float leftLineTopX = (c1X + leftLineMiddleX) / 2.0f;
            float leftLineTopY = leftLineSlope * leftLineTopX + leftLineB;
            float rightLineTopX = (c2X + rightLineMiddleX) / 2.0f;
            float rightLineTopY = rightLineSlope * rightLineTopX + rightLineB;
            float leftLineBottomX = (leftLineMiddleX + c4X) / 2.0f;
            float leftLineBottomY = leftLineSlope * leftLineBottomX + leftLineB;
            float rightLineBottomX = (c3X + rightLineMiddleX) / 2.0f;
            float rightLineBottomY = rightLineSlope * rightLineBottomX + rightLineB;

            canvas.drawLine(topLineMiddleX, topLineMiddleY, bottomLineMiddleX, bottomLineMiddleY, dashedLine);
            canvas.drawLine(leftLineMiddleX, leftLineMiddleY, rightLineMiddleX, rightLineMiddleY, dashedLine);
            canvas.drawLine(topLineLeftX, topLineLeftY, bottomLineLeftX, bottomLineLeftY, dashedLine);
            canvas.drawLine(topLineRightX, topLineRightY, bottomLineRightX, bottomLineRightY, dashedLine);
            canvas.drawLine(leftLineTopX, leftLineTopY, rightLineTopX, rightLineTopY, dashedLine);
            canvas.drawLine(leftLineBottomX, leftLineBottomY, rightLineBottomX, rightLineBottomY, dashedLine);

Upvotes: 1

Views: 1347

Answers (3)

paxdiablo
paxdiablo

Reputation: 881193

If it's always a rectangle where the bottom side is perfectly horizontal, you shouldn't need to use slopes at all, you simply draw a horizontal line from left to right at the two internal levels, such as:

bottom +     (top - bottom) / 3
bottom + 2 * (top - bottom) / 3

And ditto for vertical lines top to bottom.

That's basically just performing the following operations, assuming bottom-left is the closest point to the origin:

hspread = (right - left) / 3
line (top, left +     hspread) - (bottom, left +     hspread)
line (top, left + 2 * hspread) - (bottom, left + 2 * hspread)

vspread = (top - bottom) / 3
line (bottom +     vspread, left) - (bottom +     vspread, right)
line (bottom + 2 * vspread, left) - (bottom + 2 * vspread, right)

If the corners can move independently (i.e., it doesn't have to be rectangular), you still don't need to involve yourself with slopes.

What you need to do then is to find the points along each side that equate to 1/3 and 2/3 distance along the line, and join them. For a line (x1,y1)-(x2,y2), you can find the point 4/5 along it simply by using:

x = x1 + (x2-x1) * 4/5
y = y1 + (y2-y1) * 4/5

For example, if the shape is thus:

       (x1,y1)            (x2,y2)
                    ___--+
              +----'      \
              |            \
              |             \
              |              \
              |               \
              +----------------+
       (x3,y3)                  (x4,y4)
     :
     :
     :......
(0,0)

(apologies for the "graphics"), the topmost horizontal line can be drawn as:

line (x3 + (x1-x3) * 2/3, y3 + (y1-y3) * 2/3)
   - (x4 + (x2-x4) * 2/3, y4 + (y2-y4) * 2/3)

This is the more generalised form of an earlier answer of mine to find the midpoint of a given line.

So, in both cases, all you need is a line drawing primitive based on start and end points (which you already have), with no messing about with potentially infinite slopes.

Upvotes: 0

Andrew Faulkner
Andrew Faulkner

Reputation: 3930

Without knowing more about the program I can't definitively answer. That said, I have some ideas for things you can try, but I have questions related to each:

  1. I find it strange that the left and right edges are equal to infinity when the screen first loads. What are you basing your left and right edge positions on? Are they stored in advance? Based on the screen dimensions and density? If it's based on the screen dimensions, you shouldn't be having this problem. Measure the screen width as follows (may vary dependent on Android versions, but there's lots of online tutorials for this):

    Display d = getWindowManager().getDefaultDisplay();
    Point point = new Point();
    d.getSize(point);
    int width = point.x;
    int height = point.y;
    

    Have those values before calculating anything. Adjust the initial coordinate locations based on the screen edges.

    1. Does the box start with the same coordinates every time? And is there a screen in the app prior to the ones you're running above? If so for both, either:

      • a) calculate all the positions when the app first loads, and pass them to the new Activity / Fragment; or
      • b) generate the actual drawn Canvas asynchronously when the app loads (in the screen prior to loading the image), then pass the actual Canvas to the new Activity / Fragment.
    2. What part of the Activity / Fragment lifecycle are you running the method in? If you're running it in onResume(), try running aspects of it earlier in the cycle, or making special calculations early. More specifically:

      • a) Calculate special line slopes in an earlier part of the cycle with static pre-calculated angles and given coordinates passed manually, to be used until the rest of it fully loads. Following the full load, calculate the actual slopes. On the initial load - when the app is used for the first time - provide default values. On each exit following that, store the coordinate, slope, etc. values, and retrieve them when you load that screen again.
      • b) Do the calculations and generate the box only AFTER the screen finishes loading - i.e. make it the very last step, and only fired by tasks its dependent on completing.
      • c) Check that the left and right edge are not equal to infinity before generating the box. Use a listener on (I think it's the window object?) to watch for a change in the value of the left and right edges.
        • ...or use an asynctask that just keeps checking the edge values until they're both not infinity, then quits as soon as this condition is met.
      • on that note...are you running it in a Fragment, or an Activity?

Would you be able to provide the full Activity or Fragment that launches it and the rest of the method?

Note that I only too a brief look at the math itself and did't find any issues, so I'm going to assume it's fine since you said has no issues after loading completes.

Upvotes: 0

Paul Boddington
Paul Boddington

Reputation: 37645

This is very easy if you don't use equations of lines or slopes. What you have done is calculated all the x-coordinates by finding averages of pairs of x-coordinates. You've then substituted these x-coordinates into the equations of the sides. As you have identified this won't work if the slopes are infinite. There is a very simple solution to this. All you have to do is calculate y-coordinates in exactly the same way you calculated x-coordinates and the problem disappears.

Upvotes: 1

Related Questions