A point on a perpendicular line on a specific side - c#

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;

Related

Rotate faces so normals align with an axis in C#

I have an array of faces, each face has an array of points in 3d space. I want to fill an array of unfolded faces that contains the faces with their normals all pointing along the z axis. DirectionA is the z axis, DirectionB is the normal of the face. I work out the angle and axis then apply it. As I have points, myPoint is a point not a vector could that be a problem? My logic is not right somewhere....
Here is my current code:
public void UnfoldAll()
{
Vector3d directionA = new Vector3d(0, 0, 1);//z axis
int iii = 0;
foreach (Face f in faces)
{
Vector3d directionB = normals[f.normal - 1]; //normal from face
float rotationAngle = (float)Math.Acos(directionA.DotProduct(directionB));
Vector3d rotationAxis = directionA.CrossProduct(directionB);
//rotate all points around axis by angle
for (int i = 0; i < f.Corners3D.Length; i++)
{
Vector3d myPoint;
myPoint.X = f.Corners3D[i].X;
myPoint.Y = f.Corners3D[i].Y;
myPoint.Z = f.Corners3D[i].Z;
myPoint = Vector3d.Normalize(myPoint);
Vector3d vxp = Vector3d.CrossProduct(rotationAxis, myPoint);
Vector3d vxvxp = Vector3d.CrossProduct(rotationAxis, vxp);
Vector3d final = directionB;
var angle = Math.Sin(rotationAngle);
var angle2 = 1 - Math.Cos(rotationAngle);
final.X += (angle * vxp.X) + (angle2 * vxvxp.X);
final.Y += (angle * vxp.Y) + (angle2 * vxvxp.Y);
final.Z += (angle * vxp.Z) + (angle2 * vxvxp.Z);
unfoldedFaces[iii].Corners3D[i].X = final.X;
unfoldedFaces[iii].Corners3D[i].Y = final.Y;
unfoldedFaces[iii].Corners3D[i].Z = final.Z;
}
}
iii++;
}
Any suggestions would be great. Thank you.
When doing any kind of 3D transformation, it is usually a good idea to stay away from angles if you can. Things tend to be easier if you stick to matrices, quaternions and vectors as much as possible.
If you want to rotate a face you should find a transform that describes the rotation, and then simply apply this transform to each of the vertices to get the rotated triangle. You could use either a matrix or a quaternion to describe a rotational transform.
The exact method will depend a bit on what library you are using for transforms. For Unity3D you have the Quaternion.FromToRotation that should do what you want, just input the current normal as the from vector, and the desired normal as the toDirection.
If you are using System.Numerics you can use Quaternion.FromAxisAngle. Just take the cross product your two normals to get the axis, and take the arc cos of the dot-product to get the angle. Don't forget to ensure the normals are normalized.
Thank you that was helpful, here is my code if anyone else needs help:
public void UnfoldAll()
{
Vector3d directionA = new Vector3d(0, 0, 1);//z axis
unfoldedFaces = new UnfoldedFace[faces.Length];
int iii = 0;
foreach (Face f in faces)
{
unfoldedFaces[iii].Corners3D = f.Corners3D;
Vector3d directionB = normals[f.normal - 1]; //normal from face
directionB = Vector3d.Normalize(directionB);
Vector3d vxp = Vector3d.CrossProduct(directionA, directionB);
float rotationAngle = (float)Math.Acos(directionA.DotProduct(directionB));
Quaternion q = Quaternion.FromAxisAngleQ(vxp, rotationAngle);
q.Rotate(unfoldedFaces[iii].Corners3D);
iii++;
}
}

How to randomly find one of the vectors from point P tangent to a sphere

I would like to randomly select a vector originating at point P, such that the line formed alongside this vector is tangent to the surface of some sphere. To do this, I need a collection of all points S forming a circle on the sphere, such that the line SP is tangent to the sphere at point S. Then, given this information, I can select one point S', and create a direction vector from point P to point S'.
I would like to do this in Unity. I don't have much preference over how the vector is created itself, as long as it can be randomised and points to a point on the abovementioned circle. I believe an ideal solution would consider an angle θ ∈ [0, 2π] which can be randomised to give a vector from the origin of the circle (not the sphere) to the associated S' point.
I would appreciate a solution in C#, but I am happy with other languages too. Please do note that while the mathematical solutions are appreciated, I am specifically looking for implementation details, as I am not very fluent with Unity engine, their coordinate system and vector operations yet.
Visualisation below:
The solution in Unity is as follows:
Compute the circle's center point
Compute the circle's radius
Create a projection using a randomly selected point in 3D coordinates and point P
Use the center point and the radius to position the point, which results in finding S', such that PS' is tangent to the sphere
using UnityEngine;
public class Script : MonoBehaviour
{
private readonly float _accuracy = 0.1f;
private readonly int _vectorsCount = 1000;
private readonly Vector3 _point = new Vector3(600, 600, 600);
private readonly float _sphereRadius = 500f;
private void Start()
{
// This value will be used to calculate both the circle coordinates and its radius.
var quadraticSum = Mathf.Pow(_point.x, 2) + Mathf.Pow(_point.y, 2) + Mathf.Pow(_point.z, 2);
// Find out coordinates of the circle created by the intersection of the plane with the sphere
Vector3 circleCenter;
circleCenter.x = _point.x * Mathf.Pow(_sphereRadius, 2) / quadraticSum;
circleCenter.y = _point.y * Mathf.Pow(_sphereRadius, 2) / quadraticSum;
circleCenter.z = _point.z * Mathf.Pow(_sphereRadius, 2) / quadraticSum;
// Find out radius of the above circle
var circleRadius = _sphereRadius * Mathf.Sqrt(quadraticSum - Mathf.Pow(_sphereRadius, 2)) /
Mathf.Sqrt(quadraticSum);
/*
* At this point, we can start drawing - let's draw:
*
* - the point using red colour
* - the sphere using blue colour
* - the circle using green colour
*
* Below assumes center of the sphere is at (0, 0, 0)
*/
Debug.DrawLine(Vector3.zero, _point, Color.red, 1000);
DrawSphere();
DrawCircle(circleCenter, circleRadius);
}
private void DrawSphere()
{
for (var theta = -Mathf.PI; theta < Mathf.PI; theta += _accuracy)
{
for (var phi = -Mathf.PI; phi < Mathf.PI; phi += _accuracy)
{
var ray = new Vector3(
_sphereRadius * Mathf.Sin(theta) * Mathf.Cos(phi),
_sphereRadius * Mathf.Sin(theta) * Mathf.Sin(phi),
_sphereRadius * Mathf.Cos(theta)
);
Debug.DrawLine(Vector3.zero, ray, Color.blue, 1000);
}
}
}
private void DrawCircle(Vector3 center, float radius)
{
for (int i = 0; i < _vectorsCount; i++)
{
// Since I wanted random vectors, I am repeatedly drawing a random vector on the circle
var tangentPoint = Vector3.ProjectOnPlane(Random.insideUnitSphere, _point).normalized * radius + center;
Debug.DrawLine(Vector3.zero, tangentPoint, Color.green, 1000);
//Debug.DrawLine(_point, tangentPoint, Color.cyan, 1000);
}
}
}
See the screenshots for visualisation:
Here is an angle with the red line drawn after the sphere and the circle are drawn, to see the center of the sphere:

How to make a semi-circular movement?

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));

How to find a position, knowing a direction, angle and distance?

Using the diagram provided, I know:
Character at position A, facing B (posA)
Object at position B (posB), facing a random direction.
desired angle (20) for C (desiredAngle), based in the direction A to B.
distance from B to C
How would I go about finding the position of C?
In the code below, I managed to get the vector BC, now how can define a point along that vector using the distance from A to B?
//The angle of the hand in relation to the object
public float angleFromPlayer = -60f;
//Get the direction vector from the selectedObject to the actor
Vector3 objectDirectionToActor = actorParent.position - selectedObjectCenter;
//Make it horizontal (Flatten the y)
objectDirectionToActor.y = 0;
//Get the rotated vector using the desiredAngle
Vector3 rotatedVector = Quaternion.Euler(0, angleFromPlayer, 0) * objectDirectionToActor;
Figured it out, here is the solution for posterity:
//How high the hand will be from the dropSurface/selectedObjectPivot
public float handOffsetFromDropSurface = 0.15f;
//The angle of the hand in relation to the object
public float targetAngleFromPlayer = -80f;
//Extra distance between selectedObjectCenter and wristBone (since the handDistance is a bit too short)
public float extraHandDistance = 0f;
Vector3 GetHandBonePositionForDropSurface(Vector3 selectedObjectCenter) {
//Get the direction vector from the selectedObject to the actor
Vector3 objectDirectionToActor = actorParent.position - selectedObjectCenter;
//Make it horizontal (Flatten the y)
objectDirectionToActor.y = 0;
//Change vector size to 1 for multiplication
objectDirectionToActor.Normalize();
//Get the rotated vector using the desiredAngle
Vector3 rotatedVector = Quaternion.Euler(0, targetAngleFromPlayer, 0) * objectDirectionToActor;
//Get the distance we want the point to be from the center of the selectedObject
float handDistance =
Vector3.Distance(playerProperties.boneRightIndexProximal.position,
playerProperties.boneRightHand.position) + extraHandDistance;
//Resize vector to match the hand distance
rotatedVector *= handDistance;
//Get finalPosition, adding the handOffsetFromDropSurface
Vector3 wristTargetPosition = selectedObjectCenter + rotatedVector + (Vector3.up * handOffsetFromDropSurface);
return wristTargetPosition;
}

Rotating Vector3D

I´m trying to rotate a Vector3D like this:
Vector3D i = new Vector3D(1, 1, 0);
i.Normalize();
Matrix3D m = Matrix3D.Identity;
Quaternion rot = GetShortestRotationBetweenVectors(i, new Vector3D(1, 0, 0));
m.Rotate(rot);
Vector3D j = new Vector3D(0, 1, 0);
Vector3D jRotated = m.Transform(j);
// j should be equal to i
public static Quaternion GetShortestRotationBetweenVectors(Vector3D vector1, Vector3D vector2)
{
vector1.Normalize();
vector2.Normalize();
float angle = (float)Math.Acos(Vector3D.DotProduct(vector1, vector2));
Vector3D axis = Vector3D.CrossProduct(vector2, vector1);
// Check to see if the angle is very small, in which case, the cross product becomes unstable,
// so set the axis to a default. It doesn't matter much what this axis is, as the rotation angle
// will be near zero anyway.
if (angle < 0.001f)
{
axis = new Vector3D(0.0f, 0.0f, 1.0f);
}
if (axis.Length < .001f)
{
return Quaternion.Identity;
}
axis.Normalize();
Quaternion rot = new Quaternion(axis, angle);
return rot;
}
I get the rotation matrix to pass from i = (0.77 ,0.77, 0) to (1,0,0) => 45º. So (0, 1, 0) rotates 45º, and the result should be (0.77, 0.77, 0). But the result is almost the same original vector (0,1,0), so no transform has been done.
How can I rotate vectors? I have a vector (x, y, z), and this vector should rotate to (1, 0, 0). For this operation, suppose we have to rotate 30º. So how do I rotate all the vectors I have this 30º?
At last I found the problem.
float angle = (float)Math.Acos(Vector3D.DotProduct(vector1, vector2));
gives the angle in radians.
Quaternion rot = new Quaternion(axis, angle);
expect the angle in degrees
So the solution is easy:
float angle = (float)(Math.Acos(Vector3D.DotProduct(vector1, vector2)) * (180 / Math.PI));
This means there is a bug in Avateering-XNA (Microsoft Official Software).

Categories