0

I am trying to find the vector or angle in order to calculate the points where two "thick" lines would intersect. The end goal is to draw simple flat planes for a thick line renderer.

Although I'm using Unity and am working with Vector3, for my purposes, assume the Z-value is always 0. This is a 2d problem. It is not a Unity specific problem and I'm not sure why I'm having a hard time finding a solution that I can understand. Getting points for drawing a "thick" line is surely not new. But for the life of me, I can't find a solution.

The closest I've come is figuring out points along the line perpendicular to the one I want. I know that this is the "bounce" line. In other words, with 3 points, I can get the vector for the line that represents the side of a billiard ball table assuming the middle point is the point of impact with the side of the table. What I want is the vector perpendicular to that side. I worked out the code below according to the post here: https://answers.unity.com/questions/1503945/how-to-get-the-cross-direction-for-3-points.html However, it's not working and I don't understand matrix math or Vector API's enough to figure out how to rotate my points, etc. My math, vector, and trigonometry skills are just not up to this challenge.

I've included an image from in Unity showing what I can get. You can see the blue line which is so close to what I want. I can get points C1 and C2 that are an arbitrary distance (thickness) out from the point of impact. They just need to be rotated 90 degrees to give me D1 and D2. Then I can get the points to use in the drawing api.

The second image illustrates the actual information I'm trying to get. If I can just get the points, I can work on the custom mesh rendering myself.

The code below should be ready to display instantly once added to an empty GameObject in Unity in case that helps.

Maybe I'm trying to do this all wrong and need to instead start with two parallel lines set "thickness" apart from the points comprising the line and calculate the intersect points of those? Any feedback, not to mention an actual solution, would be greatly appreciated.

using UnityEngine;

public class ThickLineRenderer : MonoBehaviour {

  private void OnDrawGizmos() {

    Vector3[] points = new Vector3[] { new Vector3(-1, -1), new Vector3(0, 0), new Vector3(-1, 1) };

    Vector3 p1 = points[0];
    Vector3 p2 = points[1];
    Vector3 p3 = points[2];
  
    Gizmos.color = Color.white;
    Gizmos.DrawLine(p1, p2);
    Gizmos.DrawLine(p2, p3);

    Vector3 n1 = (p2 - p1).normalized;
    Vector3 n2 = (p3 - p2).normalized;
  
    Vector3 n = (n1 + n2).normalized;

    Vector3 d = p2 + n;
    Vector3 d2 = p2 - n;
  
    Gizmos.color = Color.blue;
    Gizmos.DrawLine(p2, d);
    Gizmos.DrawLine(p2, d2);


  }
}

I have the C points, I want the D points Another image of what I'm trying achieve

jpwrunyan
  • 530
  • 5
  • 22

3 Answers3

2

Not 100% sure of the intended logic here, but the part that seems screwy to me in your code is this bit:

Vector3 n1 = (p2 - p1).normalized;
Vector3 n2 = (p3 - p2).normalized;

Here, you're getting a unit vector of one line and the inverted unit vector of the other line, which you later add together. This leads to the "bounce wall" line you described because you're mirroring one unit vector across the intersection point. Don't you mean:

Vector3 n1 = (p3 - p2).normalized;
Vector3 n2 = (p1 - p2).normalized;

When you make the signs the same by fixing that issue, both vectors are on one side of the intersection point. Adding them together then leads to y-components that somewhat cancel each other out, and an x-component on the correct side of the intersection point. The drawn blue line then takes the angle you want it to (fixed unit vectors in red):

Intersecting geometry lines.

Full modified version of your code I used to make this:

using UnityEngine;

public class ThickLineRenderer : MonoBehaviour {
    public Vector3 line1Start = new Vector3(-1, -1);
    public Vector3 intersect = new Vector3(0, 0);
    public Vector3 line2Start = new Vector3(-1, 1);

    private void OnDrawGizmos() {
        Vector3[] points = new Vector3[] { line1Start, intersect, line2Start };

        Vector3 p1 = points[0];
        Vector3 p2 = points[1];
        Vector3 p3 = points[2];

        Gizmos.color = Color.white;
        Gizmos.DrawLine(p1, p2);
        Gizmos.DrawLine(p2, p3);

        Vector3 n1 = (p3 - p2).normalized;
        Vector3 n2 = (p1 - p2).normalized;
        Gizmos.color = Color.red;
        Gizmos.DrawLine(p2, n1);
        Gizmos.DrawLine(p2, n2);

        Vector3 n = (n1 + n2).normalized;

        Vector3 d = p2 + n;
        Vector3 d2 = p2 - n;

        Gizmos.color = Color.blue;
        Gizmos.DrawLine(p2, d);
        Gizmos.DrawLine(p2, d2);
    }
}

(Sorry I don't quite have the right vocabulary to describe this issue, it's been a long time since trigonometry. Hopefully this makes sense.)

  • Yes! That's it! Amazing! You figured out my problem! It was just a stupid inability on my part to understand the normalization. Now instead of using a normalized value to draw the blue line's length, I need to dynamically determine it based on how far apart the parallel lines are from the central white one. – jpwrunyan Oct 12 '21 at 20:17
  • Just a quick note that a *normal* is a vector perpendicular to a surface. But to *Normalize* means to scale a vector to a single 'unit' length. – Immersive Oct 14 '21 at 04:49
  • 1
    Yes, sorry. Thanks @Immersive. What I mean to say is that .normalized are the unit vectors of his original vectors. – Declan McKelvey-Hembree Dec 06 '21 at 17:27
1

Hello I am not really sure if I reaaally understood what you were trying to accomplish but to me your problem looks like this you got three points P1,P2,P3 and you wish to find exactly the "normal angle" the yellow angle between the P12 and P23 lines.

One way to do this is to calculate the absolute angles this means the angle formed between the x axis and each line section.In the image bellow they are the orange and purple angles. Then the substraction will tell you the angle formed between P12 and P23 this is the green angle. Finally to obtain what i believe is what you call the "normal angle", which lies precisely at the middle of the green angle, you just need to subtract half of the green angle from the bigger angle in this case the purple one.

enter image description here

I made a simple Console program to make the calculations here'sthe code

using System;

namespace ConsoleApp1
{
    class Program
    {
        public static double GetAbsAngle(double x, double y)
        {
            double radsAbsAngle;
            //Get the absolute angle from respect to the x axis given a podouble x,y
            if (y > 0)
            {
                radsAbsAngle = Math.Atan2(y, x);
                return radsAbsAngle;
            }
            //Here Math.Atan2(y, x) will always result negative
            radsAbsAngle = 2*Math.PI+Math.Atan2(y, x);
            return radsAbsAngle;
        }
        public static double AngleBetweenPoints(double x1, double y1, double x2, double y2)
        {
            double absAngleP1 = Program.GetAbsAngle(x1, y1);
            Console.WriteLine("Abs angle P1 in degrees: {0}", Program.RadiansToDegrees(absAngleP1));
            double absAngleP2 = Program.GetAbsAngle(x2, y2);
            Console.WriteLine("Abs angle P2 in degrees: {0}", Program.RadiansToDegrees(absAngleP2));
            double angleBetween;
            angleBetween = (x1 > x2) ? absAngleP1 - absAngleP2 : absAngleP2 - absAngleP1;
            return angleBetween;
        }
        public static double RadiansToDegrees(double radians)
        {
            double degrees = (180 / Math.PI) * radians;
            return (degrees);
        }
        static void Main(string[] args)
        {
            //P1 with 
            double x1 = -4;
            double y1 = 4;
            //Assuming that P2 is always at 0,0
            //P3 with
            double x3 = -4;
            double y3 = -4;

            double angleBetween = Program.AngleBetweenPoints(x1, y1, x3, y3);
            Console.WriteLine("Angle between P1 and P3 in degrees: {0}",Program.RadiansToDegrees(angleBetween));
            double p1Angle = Program.GetAbsAngle(x1, y1);
            double p3Angle = Program.GetAbsAngle(x3, y3);
            double absNormalAngle = (p1Angle > p3Angle) ? p1Angle - (angleBetween/ 2) : p3Angle - (angleBetween / 2);
            Console.WriteLine("The normal abs angle between P1 and P3 in degrees: {0}", Program.RadiansToDegrees(absNormalAngle));
        }
        
    }
}

The result is the following

Abs angle P1 in degrees: 135
Abs angle P2 in degrees: 225
Angle between P1 and P3 in degrees: 90
The normal abs angle between P1 and P3 in degrees: 180
Dharman
  • 30,962
  • 25
  • 85
  • 135
Jóskua
  • 76
  • 3
  • Thank you! I believe that is indeed getting the information I want. However, I think I'm also mistaken in my actual approach. I don't think getting the angle alone is going to accomplish my ultimate goal because the points C1, C2, and D1, D2 are going to be variable lengths from the center point. I will post the solution I came up with for your consideration. – jpwrunyan Oct 12 '21 at 19:54
0

I have come up with a solution for the nonce that relies on using parallel lines and line intersection to find the corner points I need in order to draw a "fat" line.

I'm not fully convinced this is the right way to do it and am open to accepting a better answer (or editing this one). I hope however that a working answer will help lead to a better one.

The picture below shows my end result. Yes, there are obvious problems if p1 == p3. And acute angles will similarly cause issues. However, at the moment this works for my use-case.

using System;
using System.Collections.Generic;
using UnityEngine;

[RequireComponent(typeof(MeshRenderer))]
[RequireComponent(typeof(MeshFilter))]
public class OutlineRenderer : MonoBehaviour {
  //Add 5 GameObjects as children to this one at:
  //0, 0, 0
  //12, 4.5, 0
  //10, -0.5, 0
  //15, -6, 0
  //2, -6, 0
  public GameObject[] points;

  private void OnDrawGizmos() {
    //Hold a list of corner vertices alternating by left-right as the line progresses.
    List<Vector2> corners = new List<Vector2>();

    //For now, thickness is an inverse-multiplier.
    //With a little extra math, it can be converted to a scalar unit.
    float thickness = 1.5f;

    //This logic is going to connect the line into a loop (which is my end use-case).
    //For a straight line, modify the logic for the starting and ending vertices.
    for (int i = 0; i < points.Length; i++) {
      //The prev point. If p2 is index 0, then p1 is the last point in the list.
      Vector3 p1 = i > 0 ? points[i - 1].transform.position : points[points.Length - 1].transform.position; 
      //The current point.
      Vector3 p2 = points[i].transform.position;
  
      float dist = Vector2.Distance(p1, p2);
  
      float dx = p2.x - p1.x;
      float dy = p2.y - p1.y;
  
      dx /= dist * thickness;
      dy /= dist * thickness;

      Vector3 a = new Vector3(-dy + p1.x, dx + p1.y);
      Vector3 b = new Vector3(dy + p1.x, -dx + p1.y);
      Vector3 a2 = a + new Vector3(dx, dy);
      Vector3 b2 = b + new Vector3(dx, dy);

      //----------------------------------------
      //The next point. If p2 is the last index, then p3 is the first point in the list.
      Vector3 p3 = i < points.Length - 1 ? points[i + 1].transform.position : points[0].transform.position;

      dist = Vector2.Distance(p3, p2);

      dx = p2.x - p3.x;
      dy = p2.y - p3.y;

      dx /= dist * thickness;
      dy /= dist * thickness;

      Vector3 c = new Vector3(dy + p3.x, -dx + p3.y);
      Vector3 d = new Vector3(-dy + p3.x, dx + p3.y);
      Vector3 c2 = c + new Vector3(dx, dy);
      Vector3 d2 = d + new Vector3(dx, dy);
  
      Vector2 i1 = findSegmentIntersection(a, a2, c, c2);
      Vector2 i2 = findSegmentIntersection(b, b2, d, d2);

      corners.Add(i1);
      corners.Add(i2);
    }

    //Corners are the actual vertices I'm going to need.
    //Draw logic (for Gizmos only).
    //Mesh rendering is completely separate.
    int n = corners.Count;
    for (int i = 0; i < n - 2; i += 2) {
      Vector3 p = points[i / 2].transform.position;
      Gizmos.color = Color.blue;
      Gizmos.DrawLine(p, corners[i]);
      Gizmos.DrawLine(p, corners[i + 1]);
      Gizmos.color = Color.red;
      Gizmos.DrawLine(corners[i], corners[i + 2]);
      Gizmos.DrawLine(corners[i + 1], corners[i + 3]);
    }
    Gizmos.color = Color.blue;
    Gizmos.DrawLine(points[points.Length - 1].transform.position, corners[n - 2]);
    Gizmos.DrawLine(points[points.Length - 1].transform.position, corners[n - 1]);
    Gizmos.color = Color.red;
    Gizmos.DrawLine(corners[n - 2], corners[0]);
    Gizmos.DrawLine(corners[n - 1], corners[1]);
  }

  //A utility method I converted from ActionScript.
  //There's probably something in the Unity library that can also do it.
  public Vector2 findSegmentIntersection(Vector2 p1, Vector2 p2, Vector2 p3, Vector2 p4) {
    var x1 = p1.x;
    var x2 = p2.x;
    var x3 = p3.x;
    var x4 = p4.x;
    var y1 = p1.y;
    var y2 = p2.y;
    var y3 = p3.y;
    var y4 = p4.y;
    var z1 = x1 - x2;
    var z2 = x3 - x4;
    var z3 = y1 - y2;
    var z4 = y3 - y4;
    var d = z1 * z4 - z3 * z2;

    //If d is zero, there is no intersection.
    if (d == 0) {
      throw new Exception("Lines do not intersect!");
    }

    //Get the x and y.
    var pre = x1 * y2 - y1 * x2;
    var post = x3 * y4 - y3 * x4;
    var x = (pre * z2 - z1 * post) / d;
    var y = (pre * z4 - z3 * post) / d;

    //Return the point of intersection.
    return new Vector2(x, y);
  }
}

This shows the result of the above code in Unity.

jpwrunyan
  • 530
  • 5
  • 22