How to make a circle with three known points

I want to make a throwable weapon that will arc towards its target.

Right now the best way I can think of is to make a circle with three points and have the object travel along the minor arc

A = player position

B = target position

C = a point that’s perpendicular to the AB line and offset by the player

I can’t figure out how to get the point of the center of the circle (point D)

If it changes the math / code, I need this to work in 3D so the player can aim their arc up or down too

edit: (If 3d makes everything much more complex I think I thought of a way to make 2d work for me)

Thanks for any help, I’ve been stuck for awhile on this

144239-bandicam-2019-08-07-20-18-08-299.jpg

Not the prettiest picture but I hope it gets the point across

Answered:
My result is in a comment

For some reason I can not find the question at the moment but there was exact the same question already asked here.

I’ve written this method and actually created this gif image:

Utilities/MathHelper.cs at master · Bunny83/Utilities · GitHub

CalculateCircleCenter.gif

@NWin,

Found here: random/circle3d at master · sergarrido/random · GitHub

Given your 3 points p1 (p1x, p1y, p1z), p2 (p2x, p2y, p2z), p3 (p3x, p3y, p3z)

In summary, we have to solve the following 2x2 system:

(v1T·v1)k1 + (v1T·v2)k2 = 0.5·(v1T·v1)
(v1T·v2)k1 + (v2T·v2)k2 = 0.5·(v2T·v2)

where

v1 = p2-p1 = (v1x, v1y, v1z)T
v2 = p3-p1 = (v2x, v2y, v2z)T

The direct expressions for k1 and k2 can be derived obtaining:

k1 = 0.5·(v2T·v2)·[(v1T·v1) - (v1T·v2)]/[(v1T·v1)·(v2T·v2)-(v1T·v2)2]
k2 = 0.5·(v1T·v1)·[(v2T·v2) - (v1T·v2)]/[(v1T·v1)·(v2T·v2)-(v1T·v2)2]

After determining k1 and k2, the center of the circle is:

cx = p1x + k1v1x + k2v2x
cy = p1y + k1v1y + k2v2y
cz = p1z + k1v1z + k2v2z

Hi,

Here is a bit of math. If you have 3 points, say A, B and C - then first make a line L1 that passes between A and B. Next, make another line that passes between B and C. You end up with equations:

Y_Line1 = slope_Line1(x-x1)+y1
Y_Line2 = slope_Line2(x-x2)+y2

Next, get the perpendicular lines which should pass through the mid-points of the lines AB and BC. Then the centre of the circle will just be the intersection of these 2 perpendicular lines.

Equations of the perpendicular:

Y’Line1 = [(-1/slope_line1)(x-((x1+x2)/2))+(y1+y2)/2]
Y’Line2 = [(-1/slope_line2)
(x-((x2+x3)/2))+(y2+y3)/2]

These perpendiculars will intersect at the centre:

x = slope_line1slope_line2(y1-y3)+slope_line2(x1+x2) - slope_line1(x2+x3) whole divided by [2(slope_line2 - slope_line1)]

Once, you get the centre_x, then substitute this value in the perpendicular equations to get your centre_y

Hope this helps!

If anyone finds this later and wants my result based on @Meishin 's answer

void SetupCircle()
{
    Vector3 v1 = cPoint - transform.position;
    Vector3 v2 = targPoint - transform.position;    //targPoint being B in the diagram

    float k1 = 0.5f * Vector3.Dot(v2, v2) * ((Vector3.Dot(v1, v1) - Vector3.Dot(v1, v2)) / (Vector3.Dot(v1, v1) * Vector3.Dot(v2, v2) - (Vector3.Dot(v1, v2) * Vector3.Dot(v1, v2))));
    float k2 = 0.5f * Vector3.Dot(v1, v1) * ((Vector3.Dot(v2, v2) - Vector3.Dot(v1, v2)) / (Vector3.Dot(v1, v1) * Vector3.Dot(v2, v2) - (Vector3.Dot(v1, v2) * Vector3.Dot(v1, v2))));

    Vector3 p1 = transform.position;
    circleCenter = p1 + (k1 * v1) + (k2 * v2);

    float radius = Vector3.Distance(circleCenter, transform.position);
    GameObject instSphere = GameObject.Instantiate(sphere, circleCenter, Quaternion.identity) as GameObject;
    instSphere.transform.localScale = Vector3.one * radius * 2;
}
void SetupCircle()
{
    Vector3 p1 = transform.position;
    Vector3 p2 = targPoint;
    Vector3 p3 = cPoint;

    Vector3 v1 = p2 - p1;
    Vector3 v2 = p3 - p1;
    float v1v1 = Vector3.Dot(v1, v1);
    float v2v2 = Vector3.Dot(v2, v2);
    float v1v2 = Vector3.Dot(v1, v2);

    float b = 0.5f / (v1v1 * v2v2 - v1v2 * v1v2);
    float k1 = b * v2v2 * (v1v1 - v1v2);
    float k2 = b * v1v1 * (v2v2 - v1v2);
    circleCenter = p1 + v1 * k1 + v2 * k2;

    float radius = Vector3.Distance(circleCenter, p1);
}

The second method was my result from more directly converting what was in @Meishin 's link: random/circle3d/circle3d.cpp at master · sergarrido/random · GitHub

I quickly put the following script together to demonstrate how you would go about doing this. It’s commented, talking about how each part works. To see this in action, you’ll need three objects; 2 cubes and a sphere. Attach this script to the sphere, and set the 2 cubes as the start and end points of your arc (keep in mind this particular one is a hybrid 2D/3D solution on the XZ axis, so you’ll want to look at it top-down). Use the ‘position’ variable to move the sphere along the arc.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
  
public class ArcTest : MonoBehaviour
{
	public Transform startPoint;			//Arc start object
	public Transform endPoint;				//Arc end object
	public float arcHeight = 2f;			//Max height of the arc
  
	[Range(0f,1f)]
	public float position = 0f;				//How far along the arc the object should be
  
	//This basically returns the 2D centre of the circle in the [x,y] of the Vector3 and the radius in the [z] of the Vector3
	//Adapted from Erk Ekin's answer to https://math.stackexchange.com/questions/827072/finding-an-equation-of-circle-which-passes-through-three-points
	Vector3? Circle3 (Vector2 p0, Vector2 p1, Vector2 p2)
	{
		if (p1.x == p0.x || p2.x == p1.x)
			return null;
  
		float mr = (p1.y - p0.y) / (p1.x - p0.x);
		float mt = (p2.y - p1.y) / (p2.x - p1.x);
  
		if (mr == mt)
			return null;
  
		float x = (mr * mt * (p2.y - p0.y) + mr * (p1.x + p2.x) - mt * (p0.x + p1.x)) / (2f * (mr - mt));
		float y = (p0.y + p1.y) / 2f - (x - (p0.x + p1.x) / 2f) / mr;
  
		float radius = Mathf.Sqrt (Mathf.Pow (p1.x - x, 2f) + Mathf.Pow (p1.y - y, 2f));
		Vector2 centre = new Vector2 (x, y);
  
		return new Vector3 (centre.x, centre.y, radius);
	}
  
	public void Update ()
	{
		//For the arc beginning and end points, I used the [x,z] axis in 3D and looked down (for testing)
		Vector2 a = new Vector2 (startPoint.position.x, startPoint.position.z);
		Vector2 b = new Vector2 (endPoint.position.x, endPoint.position.z);
  
		//This finds the relative 'up' direction from the two given start and end points
		Vector3 dir3 = Vector3.Cross ((endPoint.position - startPoint.position).normalized, Vector3.down).normalized;
		Vector2 dir = new Vector2 (dir3.x, dir3.z);
  
		//This is the actual height of the arc. We want the minor arc, so clamp it such that it can't be more than the radius of the circle
		float height = Mathf.Min (arcHeight, Vector3.Distance (startPoint.position, endPoint.position) * 0.5f);
  
		//This is the middle point (you can replace it with your own, I just calculate it automatically based on the two input points)
		Vector2 c = (a + b) * 0.5f + dir * height;
  
		//Find the actual centre and radius of the circle
		//This is a nullable type because there's some funky maths, so we need to make sure we actually get useable values
		Vector3? circle = Circle3 (a, c, b);
		if (circle == null)
			return;
  
		//Extract the values from the nullable type
		Vector2 centre = circle.Value;
		float radius = circle.Value.z;
  
		//Find the relative angles to the 'origin' and blend between them so that;
		//		position(0) = the start of the arc
		//		position(1) = the end of the arc
		//Also make sure we're in radians
		float t = Mathf.Lerp (Vector2.SignedAngle (Vector2.right, a - centre), Vector2.SignedAngle (Vector2.right, b - centre), position) * Mathf.Deg2Rad;
  
		//Find the current point on the arc through sin/cos circle function using calculated radius and centre
		Vector2 pos = centre + new Vector2 (Mathf.Cos (t), Mathf.Sin (t)) * radius;
  
		//Move this object to the target point on the arc
		transform.position = new Vector3 (pos.x, 0, pos.y);
	}
}
using UnityEngine;

[ExecuteInEditMode]
public class DrawCircleOnPlane : MonoBehaviour
{
    public Vector3 planeNormal = Vector3.up;  
    public float radius = 5.0f;              
    public int segments = 100;     
    public LineRenderer lineRenderer;

    void OnValidate()
    {
        lineRenderer = GetComponent<LineRenderer>();
        if (lineRenderer != null)
        {
            DrawCircle(planeNormal, radius, segments);
        }
    }

    void DrawCircle(Vector3 normal, float radius, int segments)
    {
        lineRenderer.positionCount = segments + 1;

        Vector3 basis1 = Vector3.Cross(normal, Vector3.up).normalized;
        if (basis1 == Vector3.zero)
            basis1 = Vector3.Cross(normal, Vector3.forward).normalized;
        Vector3 basis2 = Vector3.Cross(normal, basis1).normalized;

        Vector3[] positions = new Vector3[segments + 1];
        float angleStep = 360f / segments;

        for (int i = 0; i <= segments; i++)
        {
            float angle = Mathf.Deg2Rad * angleStep * i;
            Vector3 pointOnCircle = Mathf.Cos(angle) * basis1 * radius + Mathf.Sin(angle) * basis2 * radius;
            positions[i] = normal + pointOnCircle; // Adjust the circle position to the object's position
        }

        lineRenderer.SetPositions(positions);
    }
}