I am trying to determine the angle where the camera forward vector intersect with an object vector.
Sorry, not straight forward to explain with my knowledge, please find attached a diagram: The camera may not be looking directly at the object (OBJ) and I'd like to know the angle ( ? in the diagram) where the camera's forward vector (V1 in red) intersects with the vector of the object (V2 in red) (if it does), e.g. point A, or B, or C, etc depending on the x-rotation of the camera.
I tried calculating a normalized vector for the red lines, v1 and v2.
Then calculate the angle between two vectors https://onlinemschool.com/math/library/vector/angl/
But the results don't match the expected values when testing.
//v1
Vector3 hypoth = Camera.main.transform.forward.normalized;
//v2
Vector3 adjacent = (new Vector3(obj.transform.position.x, obj.transform.position.y, Camera.main.transform.position.z)
-obj.transform.position).normalized;
float dotProd = Vector3.Dot(adjacent, hypoth);
float cosOfAngle = dotProd / (Vector3.Magnitude(adjacent) * Vector3.Magnitude(hypoth));
double radAngle = Math.Acos(cosOfAngle);
float angle = (float)((180 / Math.PI) * radAngle);
Finding the angle between v1 and v2 gives you this angle, which doesn't match what you mark in your diagram:
Instead, solve for the angle between v1 and the plane normal to v2:
We can do this in unity by projecting v1 to the plane normal to v2 using Vector3.ProjectOnPlane, and then finding the angle between that projection and v1 using Vector3.Angle:
Vector3 projection = Vector3.ProjectOnPlane(hypoth, adjacent);
float angle = Vector3.Angle(projection, hypoth);
I've a similar situation where I wanted to set the collidars of the terrain units on the same height of the player Jet and at the same time it must be on the line of sight of the camera, otherwise when u shoot the terrain units , the bullets will appear like moving through enemy units on the ground , this only works when you work with prospective camera, on orthongal , u may dont need to do this at all, its just set the object on the same height as the camera and everything will be aligned .
Here is my code
void SetColliderLocation()
{
// Object on the ground
A = TerrainUnit.transform.position;
// Camera location
B = cam.transform.position;
// Enemy jet height
height = mainPlayerTransform.position.y;
// Unit Vector normalized between A and B
AB_Normalized = (A - B).normalized;
// The unit vector required to move the collider to maintain its hieght and line of sight with the camera
unitVector = (height - A.y) / AB_Normalized.y;
// Setting the location of the collidar .
collidarGameObject.transform.position = (AB_Normalized * unitVector) + A;
}
I hope its some how similar of what you are looking for.
Edit:
If you applied this script and instead of collider you put a box , you will see the box location will be always between the camera on the sky and object on the ground however the camera or the object on the ground is moving.
Related
I have got a very large problem with rotation in Unity. What I want:
I have two 3D objects. Just one is for player manipulating, second object Transform.rotation and Transform.position is dependent on object number one with scale of 1/10. It means if I will move first object from (0,0,0) to (10,30,90) then obj.2 will move from (0,0,0) to (1,3,9). It's simple. But I have got LARGE problem with rotation.
I can't make rotation on normal transform because it's based on "local position".
Below I present my problem with simplest 2D object situation:
As you can see when I rotate red object +90 degrees the second object rotate +9 degrees and the axes become different in relation to the world. After more transformations in 3D world it make a large mess. For example after some transformations if I will want to rotate 3D object from me (like using accelerator on motocycle) on first make second object rotating from left to right (because it's based on object axis).
Of course using Transform.Rotate instead of Transform.localRotate (or Transform.EulerAngles instead of Transform.localEulerAngles) is not a solutions because it's means only if objects are childrens (it this this case are not).
WHAT I FOUND:
Using Transform.Rotate(Xdegree,Ydegree,Zdegree, Space.World) is solution for rotating second object !
What I need:
Xdegree, Ydegree and Zdegree from first (manipulated by player) object.
Transform.EulerAngles and Transform.Rotation DOESN'T work because it's returns "local objects" rotations.
So... I know that if 3D obj.2 rotation is (0;30;0) and i use obj2.Rotate(45,0,0) then the obj.2 rotation will be (~37.76;~39.23;~26.56) and it's okay. But I dont know how to convert the other way (from "local" rotation XYZ to degrees that I can use on Transform.Rotate() (of course I will divided this values (xyz) by 10 at the end because I have got 1/10 moving scale))
If you need one GameObject to have 1/10 of the rotation and position of another, you could use something like:
//the player-controlled cube
public Transform t1;
//the 1/10 cube
public Transform t2;
void Update(){
//set the position of t2 to 1/10 of the position of t1
t2.position = 0.1f * t1.position;
//get the axis and angle of t1's rotation
t1.rotation.ToAngleAxis(out float angle, out Vector3 axis);
//t2 should be rotated in the same direction (axis), but with 1/10th of the angle
t2.rotation = Quaternion.AngleAxis(angle * 0.1f, axis);
}
Edit: To allow resetting delta rotation and changing targets, you could do something like this. Note: this glitches when it wraps more than a full circle, I'm not an expert on Quaternions so you'd have to figure it out yourself.
//the player-controlled cube
public Transform t1;
//the 1/10 cube
public Transform t2;
private Vector3 t1originalPosition;
private Quaternion t1originalRotation;
private Vector3 t2originalPosition;
private Quaternion t2originalRotation;
void Start()
{
ResetTarget(t1);
}
void Update()
{
if (t1 != null)
{
//set the position of t2 to 1/10 of the position of t1
t2.position = t2originalPosition + 0.1f * (t1.position - t1originalPosition);
Quaternion t1Rotation = t1.rotation * Quaternion.Inverse(t1originalRotation);
//get the axis and angle of t1's rotation
t1Rotation.ToAngleAxis(out float angle, out Vector3 axis);
//t2 should be rotated in the same direction (axis), but with 1/10th of the angle
t2.rotation = Quaternion.AngleAxis(angle * 0.1f, axis) * t2originalRotation;
}
}
public void ResetTarget(Transform target = null)
{
t2originalPosition = t2.position;
t2originalRotation = t2.rotation;
t1 = target;
t1originalPosition = t1.position;
t1originalRotation = t1.rotation;
}
Use quaternions instead of the euler angles (xyz rotation angles). And simply give the global rotation value (quaternion) of one object to the other.
To add together quaternions, you just multiply them together.
I would like to recreate one on one the rotation of the real life controller joystick (i.e. 360 controller) into a 3D joystick mesh (that resembles the 360 controller one).
I thought about doing it by rotating the joystick in the X axis according to the magnitude of the input (mapping it to a min and max rotation in the X axis). And then figure the angle of the input and apply it to the Y axis of the 3D joystick.
This is the code I have, the joystick tilts properly in the X axis but the rotation in the Y axis doesn't work:
public void SetStickRotation(Vector2 stickInput)
{
float magnitude = stickInput.magnitude;
// This function converts the magnitude to a range between the min and max rotation I want to apply to the 3D stick in the X axis
float rotationX = Utils.ConvertRange(0.0f, 1.0f, m_StickRotationMinX, m_StickRotationMaxX, magnitude);
float angle = Mathf.Atan2(stickInput.x, stickInput.y);
// I try to apply both rotations to the 3D model
m_Stick.localEulerAngles = new Vector3(rotationX, angle, 0.0f);
}
I am not sure why is not working or even if I am doing it the right way (i.e. perhaps there is a more optimal way to achieve it).
Many thanks for your input.
I would recommend rotating it by an amount determined by the magnitude around a single axis determined by the direction. This will avoid the joystick spinning around, which would be especially noticeable in cases of asymmetric joysticks such as pilots joysticks:
Explanation in comments:
public void SetStickRotation(Vector2 stickInput)
{
/////////////////////////////////////////
// CONSTANTS (consider making a field) //
/////////////////////////////////////////
float maxRotation = 35f; // can rotate 35 degrees from neutral position (up)
///////////
// LOGIC //
///////////
// Convert input to x/z plane
Vector3 stickInput3 = new Vector3(stickInput.x, 0f, stickInput.y);
// determine axis of rotation to produce that direction
Vector3 axisOfRotation = Vector3.Cross(Vector3.up, stickInput3);
// determine angle of rotation
float angleOfRotation = maxRotation * Mathf.Min(1f, stickInput.magnitude);
// apply that rotation to the joystick as a local rotation
transform.localRotation = Quaternion.AngleAxis(angleOfRotation, axisOfRotation);
}
This will work for joysticks where:
the direction from its axle to its end is the local up direction,
it should have zero (identity) rotation on neutral input, and
stickInput with y=0 should rotate the knob around the stick's forward/back axis, and stickInput with x=0 should rotate the knob around the stick's left/right axis.
Figure out the problem, atan2 returns the angle in radiants, however the code assumes it is euler degrees, as soon as I did the conversion it worked well.
I put the code here if anyone is interested (not the change in the atan2 function):
public void SetStickRotation(Vector2 stickInput)
{
float magnitude = stickInput.magnitude;
// This function converts the magnitude to a range between the min and max rotation I want to apply to the 3D stick in the X axis
float rotationX = Utils.ConvertRange(0.0f, 1.0f, m_StickRotationMinX, m_StickRotationMaxX, magnitude);
float angle = Mathf.Atan2(direction.x, direction.y) * Mathf.Rad2Deg;
// Apply both rotations to the 3D model
m_Stick.localEulerAngles = new Vector3(rotationX, angle, 0.0f);
}
Hi,
I found a large number of references but without being able to adapt them to my needs.
As per attached figures I have my character in a given position. Below the character's feet is a new plane (). With the mouse wheel I move the character up along the Y axis and the plane moves with it. Then I drag the character to any position and I join the three vector3s with Gizmos lines. Now I need to know the slope in degrees between the starting point (the red point) and the new position of the character. I tried to use Vector3.Angle or Atan2 and many examples found around but all return different values when you rotate the character despite the slope is always the same. For example charAngle = Vector3.Angle (initialCharPos - character.transform.position, Vector3.left) returns the correct value only in that certain direction and I can get the 4 points left, right, forward, back. But for directions other than these? I was wondering if for each of the 360 points it is necessary to make checks based on the direction or if there is a faster way to get this value.
You can use Vector3.Angle, you just need to take it between the down direction & the direction from the new feet position to the start feet position, and subtract the result from 90:
Vector3 newFeetPosition;
Vector3 startFeetPosition;
// direction of "down", could be different in a zero g situation for instance
Vector3 downDirection = Vector3.down:
float slopeDegrees = 90f - Vector3.Angle(newFeetPosition - startFeetPosition, downDirection);
If you need the rise/run for other reasons, you can get them in the process of calculating the angle yourself using vector math:
Vector3 newFeetPosition;
Vector3 startFeetPosition;
// direction of "up", could be different in a zero g situation for instance
Vector3 upDirection = Vector3.up:
Vector3 feetDiff = newFeetPosition - startFeetPosition:
float riseMagnitude = Vector3.Dot(feetDiff, upDirection);
Vector3 riseVector = riseMagnitude * upDirection;
float runMagnitude = (feetDiff - riseVector).magnitude;
float slopeDegrees = Mathf.Rad2Deg * Mathf.Atan2(riseMagnitude, runMagnitude);
For a project in Unity3D I'm trying to transform all objects in the world by changing frames. What this means is that the origin of the new frame is rotated, translated, and scaled to match the origin of the old frame, then this operation is applied to all other objects (including the old origin).
For this, I need a generalized, 3-dimensional (thus 4x4) Transformation-Matrix.
I have looked at using Unity's built-in Matrix4x4.TRS()-method, but this seems useless, as it only applies the Translation, Rotation & Scale to a defined point.
What I'm looking for, is a change of frames, in which the new frame has a different origin, rotation, AND scale, with regards to the original one.
To visualize the problem, I've made a small GIF (I currently have a working version in 3D, without using a Matrix, and without any rotation):
https://gyazo.com/8a7ab04dfef2c96f53015084eefbdb01
The values for each sphere:
Origin1 (Red Sphere)
Before:
Position (-10, 0, 0)
Rotation (0,0,0)
Scale (4,4,4)
After:
Position (10, 0, 0)
Rotation (0,0,0)
Scale (8,8,8)
-
Origin2 (Blue Sphere)
Before:
Position (-20, 0, 0)
Rotation (0,0,0)
Scale (2,2,2)
After:
Position (-10, 0, 0)
Rotation (0,0,0)
Scale (4,4,4)
-
World-Object (White Sphere)
Before:
Position (0, 0, 10)
Rotation (0,0,0)
Scale (2,2,2)
After:
Position (30, 0, 20)
Rotation (0,0,0)
Scale (4,4,4)
Currently I'm simply taking the Vector between the 2 origins, scaling that to the difference between the two origins, then applying that on top of the new position of the original (first) origin.
This will of course not work when rotation is applied to any of the 2 origins.
// Position in original axes
Vector3 positionBefore = testPosition.TestPosition - origin.TestPosition;
// Position in new axes
Vector3 positionAfter = (positionBefore * scaleFactor) + origin.transform.position;
What I'm looking for is a Matrix that can do this (and include rotation, such that Origin2 is rotated to the rotation Origin1 was in before the transformation, and all other objects are moved to their correct positions).
Is there a way to do this without doing the full calculation on every Vector (i.e. transforming the positionbefore-Vector)? It needs to be applied to a (very) large number of objects every frame, thus it needs to be (fairly) optimized.
Edit: Scaling will ALWAYS be uniform.
There might be other solutions but here is what I would do
Wrap your objects into the following hierarchy
WorldAnchorObject
|- Red Sphere
|- Blue Sphere
|- White Sphere
Make sure the WorldAnchorObject has
position: 0,0,0
rotation: 0,0,0
localScale: 1,1,1
position/rotate/scale the Spheres (this will now happen relative to WorldAnchorObject)
Now all that is left is to transform the WorldAnchorObject -> it will move, scale and rotate anything else and keeps the relative transforms intact.
How exactly you move the world anchor is your thing. I guess you want to allways center and normalize a certain child object. Maybe something like
public void CenterOnObject(GameObject targetCenter)
{
var targetTransform = targetCenter.transform;
// The localPosition and localRotation are relative to the parent
var targetPos = transform.localPosition;
var targetRot = targetTransform.localRotation;
var targetScale = targetTransform.localScale;
// First reset everything
transform.position = Vector3.zero;
transform.rotation = Quaternion.Idendity;
transform.localScale = Vector3.one;
// set yourself to inverted target position
// After this action the target should be on 0,0,0
transform.position = targetPos * -1;
// Scale yourself relative to 0,0,0
var newScale = Vector3.one * 1/targetScale.x;
ScaleAround(gameObject, Vector3.zero, newScale);
// Now everything should be scaled so that the target
// has scale 1,1,1 and still is on position 0,0,0
// Now rotate around 0,0,0 so that the rotation of the target gets normalized
transform.rotation = Quaternion.Inverse(targetRotation);
}
// This scales an object around a certain pivot and
// takes care of the correct position translation as well
private void ScaleAround(GameObject target, Vector3 pivot, Vector3 newScale)
{
Vector3 A = target.transform.localPosition;
Vector3 B = pivot;
Vector3 C = A - B; // diff from object pivot to desired pivot/origin
float RS = newScale.x / target.transform.localScale.x; // relative scale factor
// calc final position post-scale
Vector3 FP = B + C * RS;
// finally, actually perform the scale/translation
target.transform.localScale = newScale;
target.transform.localPosition = FP;
}
Now you call it passing one of the children like e.g.
worldAnchorReference.CenterOnObject(RedSphere);
should result in what you wanted to achieve. (Hacking this in on my smartphone so no warranties but if there is trouble I can check it as soon as I'm with a PC again. ;))
Nevermind..
Had to apply the rotation & scale to the Translation before creating the TRS
D'Oh
I have a plane defined by a normal vector and another normalalised direction vector that is moving along that plane, both in 3D space.
I'm trying to figure out how to project that normal direction 3D vector onto the plane such that it ends up being a 2D vector with x/y coordinates.
It sounds like you need to find the angle between the direction vector and the plane. The size of the projection is going to scale with the cosine of that angle. Since the normal vector of the plane is perpendicular, I think you can find the sine between the normal vector and your direction vector.
The angle between the two vectors is given by the dot product of the vectors over the magnitudes multiplied together. That gives us our theta. Take the sin of theta, and we have the scaling factor (I'll call it s)
Next, you need to define unit size vectors on the plane to project onto. It's probably easiest to do this by setting one of the unit vectors in the direction of the projection to move forward...
If you set the unit vector in the direction of the projection, then you know the length of the projection in that unit space by using the scaling factor and multiplying by the length of the vector.
After that, with the unit vector, multiply in the length and find your vector relative to your normally defined xyz axis.
I hope this helps.
Try something like this. I wrote a paper on this exact method a while ago and can provide you with a copy if you would like.
PointF Transform32(Point3 P)
{
float pX = (float)(((V.J * sxy) - V.I * cxy) * zoom);
float pY = (float)(((V.K * cz) - (V.I * sxy * sz) - (V.J * sz * cxy)));
return new PointF(Origin.X + pX, Origin.Y - pY);
}
cxy is the cosine of the x-y camera angle, measured in radians from the positive x-axis on the xy plane.
sxy is the sine of the x-y camera angle.
cz is the cosine of the z camera angle, measured in radians from the x-y plane (so the angle is zero if the camera rests on that plane).
sz is the sine of the z camera angle.
Alternatively:
Vector3 V = new Vector3(P.X, P.Y, P.Z);
Vector3 R = Operator.Project(V, View);
Vector3 Q = V - R;
Vector3 A = Operator.Cross(View, zA);
Vector3 B = Operator.Cross(A, View);
int pY = (int)(Operator.Dot(Q, B) / B.GetMagnitude());
int pX = (int)(Operator.Dot(Q, A) / A.GetMagnitude());
pY and pX should be your coordinates. Here, vector V is the position vector of the point in question, R is the projection of that vector onto your viewing vector, Q is the component of V orthogonal to the viewing Vector, A is an artificial X-axis formed by the cross-product of the viewing vector with the vector (0,0,1), and B is an artificial Y-axis formed by the cross product of A and (0,0,1).
It sounds like what you're looking for is something like a simple rendering engine, similar to this, which used the above formulae:
Hope this helps.