Rotate faces so normals align with an axis in C# - 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++;
}
}

Related

How to calculate position of edge circle and rotation to center

i have mathematical formula and i have spawn points. but, i have so differenet object sizes and radiuses. how i can instantiate a objects in the edge of circle and rotate them onto center circle like picture;
i am bad with this stuf and my code is wrong or not i dont know
```
public void GetObstacle(int count, CircleRotator rotator)
{
float TAU = 6.283185307179586f;
for (int i = 0; i < count; i++)
{
Transform cameraTR = mainCamera.transform;
Transform tr = rotator.transform;
var obj = new GameObject();
float t = (float)i / count;
float angle = t * TAU * 0.25f; //
Vector3 itemCenter = new Vector3(Mathf.Cos(angle) * rotator.GetRadius + tr.position.x,
tr.position.y + Mathf.Sin(angle) * rotator.GetRadius);
obj.transform.position = itemCenter;
Vector3 current = tr.position - cameraTR.position;
Vector3 target = obj.transform.position - cameraTR.position;
obj.transform.Rotate(Vector3.forward, Vector3.Dot(current, target));
obj.transform.SetParent(tr);
}
}
```
where 0.25 in here i dont understand. But without 0.25 all will broke. angle = t * TAU * 0.25f;
help pls or give some adwice. https://ibb.co/qNhZCh3 what i would like but what i have https://ibb.co/NFSRmSn
so many formulas and watched videos. but i can't understand how
Thanks #Ruzihm with help rotations.
spawn outside edge I got this way, where I'm not sure if this is the right way, where Size is the size of y collider. If someone show another good way i will be gratitude!
Vector3 direction = (tr.position - obj.transform.position);
obj.transform.rotation = Quaternion.LookRotation(Vector3.forward, direction);
obj.transform.position -= direction.normalized * obj.Size()/2f;

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:

Modern OpenGL pan function using world space origin in perspective view

I am implementing a pan function in a 3D perspective view using OpenTK and C#. The idea is to have an intuitive 'click and drag' functionality with the right mouse button. One obvious complication is which depth within the scene to use for the click/drag, since the amount of displacement is depth dependent.
I have got mostly blank space in the scene so expecting to get a depth from an object under the pointer doesn't seem a good solution. Therefore my idea is to use the model space origin (which is also the centre of rotation) as the reference, and have that move with the mouse.
I have used the code below. It works as intended with the original zoom level, but once I zoom in or out the displacement becomes too much (when zoomed in) or too little (when zoomed out).
The method is this, when the right button is pressed:
Get the screen coordinates at the start and end of the move.
Get the screen z (depth within the scene) for the world space origin (0,0,0).
Apply that z value to the start/end screen coordinates and convert them to model space.
Get the vector between the two.
Apply that vector to the model matrix as a translation, send to vertex shader and render scene.
My questions are:
Am I overdoing the calculations here? Could the method be simplified?
Why would my mouse pointer not being staying a fixed distance from the origin as intended once the zoom level changes?
.
private Vector3 pan = new Vector3();
private float zoom = -3;
private Point mouseStartDrag;
private void GlControl1_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Right)
{
mouseStartDrag = new Point(e.X, e.Y);
}
}
private void GlControl1_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Right) {
Vector3 origin_screen = Project( new Vector3(0, 0, 0) ); // get the screen z for the world space origin
Vector4 screen1 = ScreenToViewSpace( mouseStartDrag, origin_screen.Z ); // start
Vector4 screen2 = ScreenToViewSpace( new Point(e.X, e.Y), origin_screen.Z ); // end
pan = new Vector3(screen2 - screen1);
ApplyPanZoom();
}
}
private Vector4 ScreenToViewSpace(Point MousePos, float ScreenZ) {
int[] viewport = new int[4];
OpenTK.Graphics.OpenGL.GL.GetInteger(OpenTK.Graphics.OpenGL.GetPName.Viewport, viewport);
Vector4 pos = new Vector4();
// Map x and y from window coordinates, map to range -1 to 1
pos.X = (MousePos.X - (float)viewport[0]) / (float)viewport[2] * 2.0f - 1.0f;
pos.Y = 1 - (MousePos.Y - (float)viewport[1]) / (float)viewport[3] * 2.0f;
pos.Z = ScreenZ * 2.0f - 1.0f;
pos.W = 1.0f;
return pos * view;
}
private Vector3 Project(Vector3 p)
{
Vector4 clipSpace = new Vector4(p.X, p.Y, p.Z, 1.0f) * model * view * projection; // clip space coordinates
Vector4 ndc = Vector4.Divide(clipSpace, clipSpace.W); // normalised device coordinates
return new Vector3(
glControl1.Width * (ndc.X + 1) / 2,
glControl1.Height * (ndc.Y + 1) / 2,
(ndc.Z + 1) / 2
);
}
private void ApplyPanZoom() {
view = Matrix4.CreateTranslation(pan.X, pan.Y, zoom);
SetMatrix4(Handle, "view", view);
glControl1.Invalidate();
}
Answering my own question. Got it eventually through trial, error and Googling. The problem was that my 'screen to view space' method wasn't quite right. I had to multiply by the inverse projection matrix. This works, and it results in the sort of click-and-drag pan that I was trying to achieve:
private Vector3 ScreenToViewSpace(Point MousePos, float ScreenZ) {
int[] viewport = new int[4];
OpenTK.Graphics.OpenGL.GL.GetInteger(OpenTK.Graphics.OpenGL.GetPName.Viewport, viewport);
Vector4 pos = new Vector4();
// Map x and y from window coordinates, map to range -1 to 1
pos.X = (MousePos.X - (float)viewport[0]) / (float)viewport[2] * 2.0f - 1.0f;
pos.Y = 1 - (MousePos.Y - (float)viewport[1]) / (float)viewport[3] * 2.0f;
pos.Z = ScreenZ * 2.0f - 1.0f;
pos.W = 1.0f;
Vector4 a = Vector4.Transform(pos, Matrix4.Invert(projection));
Vector3 b = new Vector3(a.X, a.Y, a.Z);
return b / a.W;
}

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