How to make a semi-circular movement? - c#

Suppose I have a car in position P0 and I want to move it to position P1, just like these 4 examples:
The linear distance between P0 and P1 is d and the perpendicular maximum height the movement reaches is d/3. I want to simulate this clockwise semi-circular movement from P0 to P1.
Suppose dir = P1 - P0 (length d) and perp is the vector (of length d/3) perpendicular to dir.
Suppose t = 0 is the beginning of the semi-circular movement and t = 1 is the end, how can I measure the angle and the position of the car at t = i?

We have to find angle of this arc and circle center.
At first find radius.
R^2 = (d/2)^2 + (R-d/3)^2 //pythagorean
R = 13/24 * d
Now angle
half_angle = arcsin(12/13) ~ 67.4 degrees
angle = 2 * half_angle ~ 135 degrees = 2.35 radians
Normalize perp vector
uperp = perp / len(perp)
Get circle center
M = (P0 + P1)/2 //chord middle
C = M + uperp * 5/24 * d
Starting angle
A0 = atan2(P0.Y-C.Y, P0.X-C.X)
And finally coordinates
Car.X = C.X + R * Cos(A0 + t * angle)
Car.Y = C.Y + R * Sin(A0 + t * angle)
In Unity this would look like:
Vector2 startPosition;
Vector2 endPosition;
Vector2 perp;
float t;
float d = (endPosition - startPosition).magnitude;
float radius = 13f/24f * d;
float angle = 2f * Mathf.Asin(12f/13f);
Vector2 uperp = perp.normalized;
Vector2 M = (startPosition+endPosition)*0.5f;
Vector2 C = M + uperp * 5f/24f * d;
float A0 = Mathf.Atan2(startPosition.y-C.y, startPosition.x-C.x);
float At = A0 + t * angle;
Vector2 newPos = C + radius * new Vector2(Mathf.Cos(At), Mathf.Sin(At));

Related

A point on a perpendicular line on a specific side

I have a point P1 and P2 (this gives me a Vector that has a direction and a turn)
How to determine P1L and P1R?
L is always on the left side and R is always on the right side (no matter how the line is marked)
In the code below, I add and subtract values, but that doesn't tell me when it's going to be right and left.
I would like to point specifically to the point to the left and right as I stand in P1 and look towards P2
Vector2 vNormalized = (endP - startP).normalized;
Vector2 vPerpendicular = new Vector2(vNormalized.y, -vNormalized.x).normalized;
var P1 = startP + vPerpendicular * Thickness / 2;
var P2 = startP - vPerpendicular * Thickness / 2;
var P3 = endP - vPerpendicular * Thickness / 2;
var P4 = endP + vPerpendicular * Thickness / 2;
You can think in 3d and it will be easier:
You have your P1-P2 vector in 3d:
Vector3 v = new Vector3( vNormalized.x, vNormalized.y, 0.0f );
and the normal vector:
Vector3 normal = new Vector3( 0.0f, 0.0f, 1.0f );
Then by using Cross product you can calculate the left and right vectors:
Vector3 perpendicularLeft = Vector3.Cross( v, normal ).normalized;
Vector3 perpendicularRight = Vector3.Cross( normal, v ).normalized;
And then you can calculate your points as:
Vector3 PLeft = startP + Thickness * perpendicularLeft;
Vector3 PRight = startP + Thickness * perpendicularRight;
Where:
Vector2 left = new Vector2( PLeft.x, PLeft.y );
Vector2 right = new Vector2( PRight.x, PRight.y );
You are almost there. P1 and P2 will always be clockwise/counterclockwise compared to the direction of the line.
If you want P1L and P1R to be on the Left/right side relative to the viewer you can simply compare the X-coordinate and switch the order of them. or you can switch the sign of the line direction:
if(vPerpendicular.X < 0){
vPerpendicular = -vPerpendicular;
}
That should ensure that P1 and P2 has a consistent left/right order. But you might need to change the check to vPerpendicular.X > 0 depending on the desired order. It might depend on the coordinate system you are using.
Also, there should be no need to normalize twice. Once should be sufficient.
Find the rotation angle from P1 to P2:
Vector2 diff = P2 - P1;
float angle = Mathf.Atan2(diff.y, diff.x);
Add 90 degrees to the angle to get the angle to P1L. Note that Mathf.Atan2 will return angles in radians:
float P1L_angle = angle + 0.5*Mathf.PI;
Now pick some length p1l_length and use sine and cosine to get the x/y values:
float P1L_length = 0.5f;
Vector2 P1L = P1L_length*(new Vector2(Mathf.Cos(P1L_angle), Mathf.Sin(P1L_angle)));
Without offsetting by P1, the P1R is just the opposite of P1L
Vector2 P1R = -P1L;
And then you add P1 to both to get your final answer:
P1L += P1;
P1R += P1;

Find an angle to launch the projectile at to reach a specific point

So, Dani in his slightly new video -> "Making a Game, But I Only Have 3 Days" (https://youtu.be/S7Dl6ATRK2M) made a enemy which has a bow and arrow (at 5:39). I tried to recreate that but had no luck... I also can't find the website that he used... Today I found this https://physics.stackexchange.com/questions/56265/how-to-get-the-angle-needed-for-a-projectile-to-pass-through-a-given-point-for-t. It worked very well but still had problems if the target was far away and also it wasn't as accurate. The code so far is
float CalculateAngle()
{
float gravity = Physics.gravity.magnitude;
float deltaX = targetPositionMod.x - currentPosition.x;
float deltaY = targetPositionMod.y - currentPosition.y;
float RHSFirstPart = (velocity * velocity) / (gravity * deltaX);
float RHSSecondPart = Mathf.Sqrt(
((velocity * velocity) * ((velocity * velocity) - (2 * gravity * deltaY))
/ (gravity * gravity * deltaX * deltaX))
- 1);
float tanθ = RHSFirstPart - RHSSecondPart;
float angle = Mathf.Atan2(tanθ, 1) * Mathf.Rad2Deg;
if (angle < 0) return angle;
return -angle;
}
The -angle is because the forward axis starts points up when the x-rotation is negative (Unity). Maybe the reason of this not working as intended is that I am not that good at this kind of Physics (Part of that is me being only 14). Maybe the problem is in the code, maybe it is the formula. Any help is appreciated.
Thanks...
Edit:
The Archer class is:
using UnityEngine;
using System;
public class Archer : MonoBehaviour
{
[SerializeField] float velocity = default;
[SerializeField] Transform target = default;
[SerializeField] GameObject arrowPrefab = default;
[SerializeField] float coolDown = default;
Vector3 targetPositionMod;
Vector3 currentPosition;
Vector3 targetPosition;
float countDown = 0f;
void Start()
{
countDown = coolDown;
UpdateVariables();
}
void Update()
{
UpdateVariables();
SetAngle();
ShootBullet();
}
void UpdateVariables()
{
currentPosition = transform.position;
targetPositionMod = Mod(target.position);
targetPosition = target.position;
targetPosition.x /= 10;
targetPosition.y /= 10;
targetPosition.z /= 10;
countDown -= Time.deltaTime;
}
void SetAngle()
{
Vector3 direction = targetPosition - currentPosition;
Quaternion lookRotation = Quaternion.LookRotation(direction);
Vector3 rotation = lookRotation.eulerAngles;
rotation.x = (float) CalculateAngle();
transform.rotation = Quaternion.Euler(rotation.x, rotation.y, 0f);
}
void ShootBullet()
{
if (!(countDown <= 0f)) return;
countDown = coolDown;
GameObject arrow = Instantiate(arrowPrefab, transform.position, transform.rotation);
Rigidbody Rigidbody = arrow.GetComponent<Rigidbody>();
Rigidbody.AddForce(transform.forward * velocity, ForceMode.Impulse);
}
double CalculateAngle()
{
double gravity = Physics.gravity.magnitude;
double deltaX = targetPositionMod.x - currentPosition.x;
double deltaY = targetPositionMod.y - currentPosition.y;
double RHSFirstPart = (velocity * velocity) / (gravity * deltaX);
double RHSSecondPart = Math.Sqrt(
(((velocity * velocity) * ((velocity * velocity) - (2 * gravity * deltaY))
/ (gravity * gravity * deltaX * deltaX))
- 1));
double tanθ = RHSFirstPart - RHSSecondPart;
double angle = Math.Atan2(tanθ, 1) * Mathf.Rad2Deg;
if (angle < 0) return angle;
return -angle;
}
Vector3 Mod(Vector3 Vec)
{
if (Vec.x < 0) Vec.x -= 2 * Vec.x;
if (Vec.y < 0) Vec.y -= 2 * Vec.y;
if (Vec.z < 0) Vec.z -= 2 * Vec.z;
Vec.x /= 10;
Vec.y /= 10;
Vec.z /= 10;
return Vec;
}
}
Ok, as I can see, your implementation of formula from StackExchange is right, but you have to remember two things:
In unity there is a 3D world, so horizontal distance is not just pos1.x - pos2.x, but
Mathf.Sqrt( deltaX * deltaX + deltaZ * deltaZ ), where deltaX = targetPositionMod.x - currentPosition.x and deltaZ = targetPositionMod.z - currentPosition.z
In computer implementation you have no 100% accuracy of math, so some problems can appear because of computational accuracy. And it can have affect on big distances. You can try to use double instead of float or find another implementation for arctangent function (I think, this can really help). But try this (second) advice only if first didn't help. It's harder to implement and it slows computations a bit.
Algorithm:
Step 1: Set up a function that calculates the appropriate solution of a quadratic equation
a*x^2 + b*x + c = 0
double quadratic_root(a,b,c){
D = b^2 - 4*a*c
return ( - b - Math.Sqrt(D) ) / (2 * a)
}
Step 2: Input
current.x
current.y
current.z
target.x
target.y
target.z
velocity
gravity
Step 3: Calculate coefficients of the quadratic polynomial:
dist = Math.Sqrt( (target.x - current.x)^2 + (target.y - current.y)^2 )
a = gravity * dist^2 / (2 * velocity^2)
b = -dist
c = target.z - current.z + a
Step 4:
theta = Math.Atan2( quadratic_root(a,b,c), 1 )
Calculation behind the algorithm. You are in three space. The current position has coordinates
x = current.x
y = current.y
z = current.z
and the target has coordinates
x = target.x
y = target.y
z = target.z
Assume the angle between the initial velocity and the horizontal plane is theta. The magnitude of the projection of the distance between the current position and the target onto the horizontal $x,y-$plane is
dist = sqrt( (target.x - current.x)^2 - (target.y - current.y)^2 )
You are given the velocity magnitude velocity. Then, the speed with which the shadow (i.e. the orthogonal projection) of the arrow moves along the horizontal line between the source and the target is the magnitude of the shadow (i.e. the orthogonal projection) of the actual velocity
velocity * cos(theta)
The vertical speed of the arrow is then
velocity * sin(theta)
So the motion along dist follows the equation
dist = time * velocity * cos(theta)
and hence
time = dist / (velocity * cos(theta))
In the vertical direction, the motions is described by the equation
z = current.z + time * velocity * sin(theta) - time^2 * gravity / 2
You are interested in the time for which the arrow hits the target, which has vertical coordinate target.z, so
target.z = current.z + time * velocity * sin(theta) - time^2 * gravity / 2
The equation can be written as:
0 = - (target.z - current.z) + time * velocity * sin(theta) - time^2 * gravity / 2
We already know that
time = dist / (velocity * cos(theta))
so
0 = - (target.z - current.z) + dist * velocity * sin(theta) / (velocity * cos(theta)) - dist^2 * gravity / ( 2 * (velocity * cos(theta))^2 )
which can be slightly simplified to
0 = - (target.z - current.z) + dist * sin(theta) / cos(theta) - gravity * dist^2 / ( 2 * (velocity * cos(theta))^2 )
Because 1/( cos(theta)^2 ) = 1 + ( tan(theta) )^2 we obtain the quadratic in tan(theta) equation
a * ( tan(theta) )^2 + b * tan(theta) + c = 0
where
a = gravity * dist^2 / (2 * velocity^2)
b = - dist
c = target.z - current.z + a

How to make a b-spline cubic curve with 4 points in C# Unity

I need to calculate a 4-point cubic non-uniform B-Spline (p0, p1, p2, p3) that interpolates p0 and p3.
Until Now I was able to make the function calculate the curve but I don't know how to add the multiplicity at points p0 and p3 to do the interpolation. The code created until now is as follows.
private Vector2 CalculaB_Spline(float t, Vector2 p0, Vector2 p1, Vector2 p2, Vector2 p3)
{
//(t)=0×B0(u−2)−1×B1(u−2)+1×B2(u−2)+2×B3(u−2)
float tt = t * t;
float ttt = t * t * t;
float u = 1 - t;
float uuu = u * u * u;
float q3 = uuu;
float q2 = 3f * ttt - 6f * tt + 4f;
float q1 =-3f * ttt + 3f * tt + 3f * t + 1f;
float q0 = ttt;
Vector2 p = (p0 * q3 +
p1 * q2 +
p2 * q1 +
p3 * q0) ;
p /= 6;
return p;
}
I hope to be able to use this function to do a C2 continuity by joining with a grade 5 Bezier curve.
I'm not exactly sure what knot vector should your B-spline have and how you obtained q0 to q3.
What I would propose would be to use a cubic Bezier curve (which is a special case of a non-uniform B-spline and which should be sufficient in your situation). It interpolates p0 and p3; later you can choose p1 and p2 according to the C2 continuity condition.
Trying to keep the changes of your function to minimum, multiplying the Bernstein polynomials on a paper then lead me to the following.
private Vector2 evalBezier(float t, Vector2 p0, Vector2 p1, Vector2 p2, Vector2 p3)
{
float tt = t * t;
float ttt = t * t * t;
float u = 1 - t;
float uuu = u * u * u;
float q3 = uuu;
// q1 and q2 changed:
float q2 = 3f * ttt - 6f * tt + 3f * t;
float q1 =-3f * ttt + 3f * tt;
float q0 = ttt;
Vector2 p = (p0 * q3 +
p1 * q2 +
p2 * q1 +
p3 * q0) ;
// No division by 6.
return p;
}
However, rather than evaluating a Bezier curve in monomial basis (as you do), it is generally more stable to use de Casteljau algorithm. If you want to learn more about Bezier curves and B-splines, I can recommend for instance these lecture notes.

Instantiate gameobjects in a segment of a circle if you have radius of the circle

I want to instantiate gameobjects in a segment of a circle e.g in between 10 degrees and 100 degrees from a known vector3 position. (Imagine a shape of pizza). I found following code that helps me instantiate objects between 0 and 180 degrees. Can someone help me instantiate gameobjects between 10 and 100 degrees as an example.
Vector3 randomCircle ( Vector3 center , float radius )
{
float ang = Random.value * 180;
Vector3 pos;
pos.x = center.x + radius * Mathf.Sin(ang * Mathf.Deg2Rad);
pos.y = center.y + radius * Mathf.Cos(ang * Mathf.Deg2Rad);
pos.z = center.z;
return pos;
}
Copying and modifying my answer to different topic would do what you need.
// get the new position
public Vector3 OrbitPosition(Vector3 centerPoint, float radius, float angle)
{
Vector3 tmp;
// calculate position X
tmp.x = Mathf.Sin(angle * (Mathf.PI / 180)) * radius + centerPoint.x;
// calculate position Y
tmp.y = Mathf.Sin(angle * (Mathf.PI / 180)) * radius + centerPoint.y;
tmp.z = centerPoint.z;
return tmp;
}
// instantiate element at random orbit position
public GameObject InstantiateRandom(GameObject toInstantiate, Vector3 centerPoint)
{
// get calculated position
Vector3 newPosition = OrbitPosition(centerPoint, 10.0f, Random.Range(10.0f, 100.0f));
// instantiate a new instance of GameObject
GameObject newInstance = Instantiate(toInstantiate) as GameObject;
// check if instance was created
if ( newInstance )
{
// set position to the newly created orbit position
newInstance.transform.position = newPosition;
}
// return instance of the newly created object on correct position
return newInstance;
}

Planet Orbit Counter clockwise insead of Clockwise

I made a game where planets orbit clockwise (More of a simulation). I'm wondering how I would make it go counter clockwise.
Host = the object the planet is orbiting
entity = planet
double angle;
if (host.Y == entity.Y && host.X == entity.X) //small offset
angle = Math.Atan2(host.Y - entity.Y + (Random.NextDouble()*2 - 1),
host.X - entity.X + (Random.NextDouble()*2 - 1));
else
angle = Math.Atan2(host.Y - entity.Y, host.X - entity.X);
float angularSpd = host.GetSpeed(s.Speed)/s.Radius;
angle += angularSpd*(time.thisTickTimes/1000f);
double x = entity.X + Math.Cos(angle)*radius;
double y = entity.Y + Math.Sin(angle)*radius;
Vector2 vect = new Vector2((float) x, (float) y) - new Vector2(host.X, host.Y);
vect.Normalize();
vect *= host.GetSpeed(s.Speed)*(time.thisTickTimes/1000f);
host.MoveTo(host.X + vect.X, host.Y + vect.Y);
Try changing the angle += angularSpd... to angle -= angularSpd...

Categories