Search Unity

Getting some imprecision after calculations involving Vector2, while trying to find a line's pixels

Discussion in 'Scripting' started by TheBestTroll, Nov 7, 2021.

  1. TheBestTroll

    TheBestTroll

    Joined:
    Nov 22, 2019
    Posts:
    6
    I'm trying to create a function that returns every position of the pixels ('Cell's, in this case. They're 1X1 squares, in Unity's unit) that snap within a straight line. This is what I have so far:


    Code (CSharp):
    1. public Cell[] FindLinePixels (Vector2 pointA, Vector2 pointB)
    2.     {
    3.  
    4.         // returned array
    5.         Cell[] line = new Cell[0];
    6.  
    7.         //
    8.  
    9.         // rounded values
    10.         Vector2 pixelA = new Vector2 (Mathf.Round (pointA.x), Mathf.Round (pointA.y));
    11.         Vector2 pixelB = new Vector2 (Mathf.Round (pointB.x), Mathf.Round (pointB.y));
    12.  
    13.  
    14.         // current point
    15.         Vector2 curPoint = pointA;
    16.  
    17.         // current rounded point
    18.         Vector2 curPixel = pixelA;
    19.  
    20.         // value summed to point every iteration, for every pixel
    21.         Vector2 increment = Vector2.zero;
    22.  
    23.  
    24.         // is this line horizontal?
    25.         bool horizontal = (Mathf.Abs (pointB.y - pointA.y) < Mathf.Abs (pointB.x - pointA.x));
    26.  
    27.         // the difference between point A and B
    28.         Vector2 relative = new Vector2 (pointB.x - pointA.x, pointB.y - pointA.y);
    29.  
    30.         //
    31.  
    32.         if (horizontal) {
    33.  
    34.             increment.x = Mathf.Sign (relative.x);
    35.             increment.y = (relative.y / relative.x) * Mathf.Sign (relative.x);
    36.  
    37.         } else {
    38.  
    39.  
    40.             increment.x = (relative.x / relative.y) * Mathf.Sign (relative.y);
    41.             increment.y = Mathf.Sign (relative.y);
    42.  
    43.  
    44.         }
    45.  
    46.         //
    47.  
    48.         // bool for stopping the do-while loop
    49.         bool reachedLastPixel = false;
    50.  
    51.         do {
    52.  
    53.             // if we hit the last pixel, this bool stops the execution
    54.             if (horizontal) {
    55.  
    56.                 if (curPixel.x == pixelB.x) {
    57.  
    58.                     reachedLastPixel = true;
    59.  
    60.                 } else
    61.                     reachedLastPixel = false;
    62.  
    63.             } else {
    64.  
    65.                 if (curPixel.y == pixelB.y) {
    66.  
    67.                     reachedLastPixel = true;
    68.  
    69.                 } else
    70.                     reachedLastPixel = false;
    71.  
    72.             }
    73.  
    74.             //
    75.  
    76.             // adds the current pixel to the list. its value was found in the last iteration
    77.             line = CellArrayAddItem (line, GetCellByPosition (new Vector2 (curPixel.x, curPixel.y)));
    78.  
    79.             //
    80.  
    81.             // adds 'increment' to the current point. that makes possible to always reach one pixel at a time, rounding 'point' to 'pixel'
    82.             curPoint = curPoint + increment;
    83.  
    84.             // rounds 'point' to 'pixel', which will be added to the list next iteration
    85.             curPixel.x = Mathf.Round (curPoint.x);
    86.             curPixel.y = Mathf.Round (curPoint.y);
    87.  
    88.  
    89.         } while (reachedLastPixel == false);
    90.  
    91.         //
    92.  
    93.         // here we debug the list and the line, and also return the list
    94.  
    95.         DebugCellArray (line, Time.deltaTime);
    96.         Debug.DrawLine (pointA, pointB, Color.red, Time.deltaTime);
    97.  
    98.         //
    99.  
    100.         return line;
    101.  
    102.     }
    The base of my code is to create a Vector2 that is summed to the start point in every iteration of a loop.
    That Vector2 is created based on the difference of start and end points. The increment value for the axis that is bigger than the other one is 1, while for the smallest is (smallest / biggest).

    This seems to work well, but I found some imprecision with the values, since some pixels are added to the list when they should not.

    I rewrote all the code for my function, and so far I have no idea what is causing this problem.

    I'm also posting some screenshots of the values and the results of them.

    Here, the console. Note that the latest curPointX returns 1.554...
    console.PNG

    Now, the Inspector. The value that entered as input to the function is seen in 'mouse'. Its X is 1.438... and that value is exactly where my mouse was when I paused the editor.
    inspector.PNG

    That means that the value entering, and the value being returned are slightly different, but enough to cause bugs.

    Also, I'm posting the Scene, so you can visualize what is actually happening:
    scene open.PNG
    scene close.PNG

    • At this point, I think I haven't mentioned that I'm using 'Cell' (which is basically how I decided to call nodes) to put a pathfinding algorithm to work. But that class only gets a few infos about the area. Instead, 'line', the Cell array, could be a Vector2 array instead, with the positions found. With that said, it's easy to notice that 'GetCellByPosition' and 'CellArrayAddItem' are functions based on 'Cell' class.
    • I'm not posting the original version of the code. I decided to comment a little bit and remove the debug lines for better readability.
    • Also, to make sure that things are crystal clear, I was calling 'GetLinePixels' every frame on 'Update' method, with 'pointA' being Vector3.zero, and 'pointB' being the mouse position.

    I really can't imagine what's going on here, and if you guys notice something I did not, would be great to get some help.

    I'll be ready to answer any question.

    Thanks in advance!
     
  2. ubbelito

    ubbelito

    Joined:
    Sep 24, 2018
    Posts:
    23
    Could something like this work?

    Code (CSharp):
    1. Vector2 relative = new Vector2 (pointB.x - pointA.x, pointB.y - pointA.y);
    2.  
    3. float steps = Mathf.Max(Mathf.Abs(relative.x), Mathf.Abs(relative.y));
    4. Vector2 increment = relative / steps;
    5.  
    6. for (int i = 0; i < steps; i++) {
    7.     Vector2 point = pointA + increment * i;
    8.     // add the point to the list as normal here.
    9. }
    My reasoning is that you want to make the largest component in relative move 1 unit for every iteration regardless.

    Example:
    If relative is (3, -15) then you want to move (3/15, -1) on each step. That way, after 15 steps, you will have moved exactly (3, 15).

    Edit:
    There is likely an off-by-one error in the for-loop above. You probably want to include the very last step as well, so

    Code (CSharp):
    1. for (int i = 0; i <= steps; i++) {
    2.     Vector2 point = pointA + increment * i;
    3.     // add the point to the list as normal here.
    4. }
     
    Last edited: Nov 7, 2021
    TheBestTroll likes this.
  3. TheBestTroll

    TheBestTroll

    Joined:
    Nov 22, 2019
    Posts:
    6

    I'm pissed off, because I swear, I first tried to do it this way. For some reason, I missed some detail and it didn't work.
    Now, this is working, although I had to make a few changes. Here's it:

    Code (CSharp):
    1. public Cell[] SugestionFindLinePixels (Vector2 pointA, Vector2 pointB)
    2.     {
    3.  
    4.         // returned array
    5.         Cell[] line = new Cell[0];
    6.  
    7.         //
    8.  
    9.         // the difference between point A and B
    10.         Vector2 relative = pointB - pointA;
    11.  
    12.         // number of pixels of the line
    13.         int steps = Mathf.RoundToInt (Mathf.Max (Mathf.Abs (relative.x), Mathf.Abs (relative.y)));
    14.  
    15.         // value added to change the pixels every step
    16.         Vector2 increment = relative / steps;
    17.  
    18.         //
    19.  
    20.         // current pixel
    21.         Vector2 curPixel = Vector2.zero;
    22.  
    23.         for (int i = 0; i <= steps; i++) {
    24.  
    25.             curPixel = pointA + (increment * i);
    26.             curPixel.x = Mathf.Round (curPixel.x);
    27.             curPixel.y = Mathf.Round (curPixel.y);
    28.  
    29.             line = CellArrayAddItem (line, GetCellByPosition (curPixel));
    30.  
    31.         }
    32.  
    33.         //
    34.  
    35.         // here we debug the list and the line, and also return the list
    36.  
    37.         DebugCellArray (line, Time.deltaTime);
    38.         Debug.DrawLine (pointA, pointB, Color.red, Time.deltaTime);
    39.  
    40.         //
    41.  
    42.         return line;
    43.  
    44.     }
    Anyway, I'm annoying when it's about details.
    I found that sometimes, it won't choose the best pixel in that situation, like in this one:
    scene open sug.PNG

    Well, it's way faster and better organized than my previous one. I don't know if I will choose your function, or try to fix my one.

    But you really showed me a way to handle the problem that I didn't notice, so thank you!

    I simply cannot understand the imprecision in my original function, and that's the problem. I really want to understand what's going on, so maybe I can fix it