Dragging an object using perspective camera - unity3d

I'm trying to write a function so when I hold the mouse down I can drag the game object and then I latch it into target.
I'm using a perspective,vertical camera with physical Camera checked and with focal length 35. Also I don't know if this is important but I am dragging the object in the Y and Z axis.
The code I'm using drags the objects too close to the camera. How can I fix this?
private void OnMouseDrag()
{
if (IsLatched)
{
print($"is latched:{IsLatched}");
return;
}
float distance = -Camera.main.transform.position.z + this.transform.position.z;
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
Vector3 rayPoint = ray.GetPoint(distance);
this.transform.position = rayPoint;
print($"{name} transform.position:{transform.position}");
this.gameObject.GetComponent<Rigidbody>().isKinematic = true;
isHeld = true;
}

You are calculating the distance by subtracting the z coordinates, then taking a point along the click-ray with that distance. That will not be a point on the same z coordinate. If you want to keep one component constant, I would rather intersect the ray with an XY plane.
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
float Zplane = this.transform.position.z; // example. use any Z from anywhere here.
// find distance along ray.
float distance = (Zplane-ray.origin.z)/ray.direction.z ;
// that is our point
Vector3 point = ray.origin + ray.direction*distance;
// Z will be equal to Zplane, unless considering rounding errors.
// but can remove that error anyway.
point.z = Zplane;
this.transform.position = point;
Could this help? Would work similar with any other plane.

Related

Get closest point between GameObject (inside the cube collider) and 3D cube collider

How do I calculate the distance of a game object (inside a cube collider) from the cube collider surface? The existing calculations were made from the cube surface outwards so I got 0 when I used the collider.closestpoint or collider.closestpointonbounds.
The simplest (but computationally not the cheapest) would be to not rely on your current collider for the distance, but to add a set of small colliders around the edge of the object (so 6 colliders, one per face of the cube). Using Collider.ClosestPoint() on all 6 faces and calculating the distance like that would give you the results you need.
First convert a point to local space.
var localPoint = transform.InverseTransformPoint(worldPoint);
var extents = collider.size * 0.5f;
var closestPoint = localPoint;
Compute the distance to each face.
var disx = extents.x - Mathf.Abs(localPoint.x);
var disy = extents.y - Mathf.Abs(localPoint.y);
var disz = extents.z - Mathf.Abs(localPoint.z);
Find the closest face (smallest distance) and move the closest point along this axis.
if(disx < disy)
{
if (disx < disz)
closestPoint.x = extents.x * Mathf.Sign(localPoint.x); //disx
else
closestPoint.z = extents.z * Mathf.Sign(localPoint.z); //disz
}
else
{
//......
}
Plus the offset of the collider, convert to world space.
closestPoint += collider.center;
transform.TransformPoint(closestPoint);
I don't know how efficient this is, but here is how I solved it:
public static Vector3 ClosetPointOnBounds(Vector3 point, Bounds bounds)
{
Plane top = new Plane(Vector3.up, bounds.max);
Plane bottom = new Plane(Vector3.down, bounds.min);
Plane front = new Plane(Vector3.forward, bounds.max);
Plane back = new Plane(Vector3.back, bounds.min);
Plane right = new Plane(Vector3.right, bounds.max);
Plane left = new Plane(Vector3.left, bounds.min);
Vector3 topclose = top.ClosestPointOnPlane(point);
Vector3 botclose = bottom.ClosestPointOnPlane(point);
Vector3 frontclose = front.ClosestPointOnPlane(point);
Vector3 backclose = back.ClosestPointOnPlane(point);
Vector3 rightclose = right.ClosestPointOnPlane(point);
Vector3 leftclose = left.ClosestPointOnPlane(point);
Vector3 closest = point;
float bestdist = float.MaxValue;
foreach (Vector3 p in new Vector3[] {
topclose, botclose, frontclose, backclose, leftclose, rightclose
})
{
float dist = Vector3.Distance(p, point);
if (dist < bestdist)
{
bestdist = dist;
closest = p;
}
}
return closest;
}
(note: this assumes and axis-aligned box, which is all I needed at the time. If you want to rotate it you will have to do more work to transform the point.)
You can Calculate by Vector3.Distance
some example
float minDistance =2;
float Distance = Vector3.Distance(other.position, transform.position);
if(Distance < minDistance)
{
//some code stuffs
}
else if(Distance > minDistance){
//some code stuffs
}
Useful information about Vector3.Distance and getting Distance from object
source: https://docs.unity3d.com/ScriptReference/30_search.html?q=Distance

Getting compass-like behavior from quaternion

Suppose you have a camera projection matrix, i.e. camera translation vector + rotation quaternion, like every typical camera, it is able to move and rotate in any direction. And independent of it's rotation whether it is looking forward, upward or downward I need to show a compass-like gauge pointing where the camera is targeted at.
The problem is that when the camera is pointed downwards the rotation of camera around it's optical center defines the value of the compass, but when the camera points forward, the rotation of camera around it's center no longer affects the value of compass, in this case the direction of camera defines the value of compass.
It get's more ugly when the camera is tilted downwards only 45 degrees, in this case it is not even clear whether the rotation around camera center affects rotation of compass.
So is there an elegant way of getting the compass value based on arbitrary camera projection matrix / quaternion?
Thank you in advance!
If you want just an arrow pointing at the target its:
Transform camera = Camera.main.transform;
Transform target = Target.transform;
Vector3 relativePosition = target.position - camera.position;
Vector3 targetRelative = Vector3.ProjectOnPlane(relativePosition, camera.forward);
float angle = Angle360(camera.up, targetRelative, camera.forward);
Compass.transform.rotation = Quaternion.Euler(0, 0, angle);
The angle function is:
float Angle360(Vector3 from, Vector3 to, Vector3 normal)
{
float dot = Vector3.Dot(from, to);
float det = Vector3.Dot(normal, Vector3.Cross(from, to));
return Mathf.Atan2(det, dot)*Mathf.Rad2Deg;
}
Here is how you can get the direction of the compass in worldspace:
Project the camera direction and target position on the XZ plane
Transform camera = Camera.main.transform;
Transform target = Target.transform;
Vector3 cameraWorldDirXZ = Vector3.ProjectOnPlane(camera.forward, Vector3.up).normalized;
Vector3 targetWorldDirXZ = Vector3.ProjectOnPlane(target.position, Vector3.up).normalized;
The angle between the cameraWorldDirXZ and targetWorldDirXZ is the angle of your compass needle.
But i don't think this will behave like you think it will. This gives you the angle that you need to rotate the camera.forward vector around the y axis to face the target. If you rotate around camera.forward you don't change either the camera.forward vector or the y axis so the compass wont change.
You might want to try a compass in local space. For that you project onto the camera XZ plane:
Vector3 cameraLocalDirXZ = camera.forward;
Vector3 targetLocalDirXZ = Vector3.ProjectOnPlane(target.position, camera.up).normalized;
Again the angle between the cameraLocalDirXZ and targetLocalDirXZ is the angle of your compass needle. This gives you the angle you need to rotate camera.forward around camera.up to face the target. Note that when you rotate around camera.forward it will change camera.up so it will change the compass direction.
If anyone stumbles upon this problem, the solution (thanks to #Pluto) is very simple, multiply your camera quaternion over three axis vectors (0,0,1), (0,1,0), (1,0,0), you will get three vectors defining coordinate system of your camera, project those three vectors onto your plane, find centroid of your three projected points and voila you have compass direction.
Here's the piece of code for that:
var rotation = /* Your quaternion */;
var cameraOrtX = rotation * new Vector3 (1, 0, 0);
var cameraOrtY = rotation * new Vector3 (0, 1, 0);
var cameraOrtZ = rotation * new Vector3 (0, 0, 1);
var cameraOrtPX = Vector3.ProjectOnPlane(cameraOrtX, new Vector3(0, 1, 0));
var cameraOrtPY = Vector3.ProjectOnPlane(cameraOrtY, new Vector3(0, 1, 0));
var cameraOrtPZ = Vector3.ProjectOnPlane(cameraOrtZ, new Vector3(0, 1, 0));
var centroid = (cameraOrtPX + cameraOrtPY + cameraOrtPZ) / 3.0f;

How to determine thickness vs width? (Using Raycasting)

Visual aids
Thickness vs width: here
Please view the short gif.
Thickness here is different from width as there are multiple walls as there are outer and inner cylinders. Thickness is the measurement of the distance between the outer/inner wall of any side of the cylinder where as thickness is the distance from one end to the other encompassing the hollow space between.
Quick synopsis on the gifs provided
-On every click the origin point (blue) and destination point (orange) orbs are created to denote where the user clicks and the interpreted end point used to calculate the distance (displayed on the GUI).
The origin defines where the user clicks on the surface of an objects collider and the destination defines the point, perpendicular with the world Y axis of the origin, where a second ray cast towards the first ray, hits the other side of the collider.
Current:
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit hit;
if (Physics.Raycast(ray, out hit))
{
//obtain the vector where the ray hit the collider.
hitPoint = hit.point; //origin point
//offset the ray, keeping it along the XZ plane of the hit
Vector3 offsetDirection = -1 * hit.normal;
//offset a long way, minimum thickness of the object
ray.origin = hit.point + offsetDirection * 100;
//point the ray back at the first hit point
ray.direction = (hit.point - ray.origin).normalized;
//raycast all, because there might be other objects in the way
RaycastHit[] hits = Physics.RaycastAll(ray);
foreach (RaycastHit h in hits)
{
if (h.collider == hit.collider)
{
hitBack = h.point; //destination point
}
}
}
Currently, width is the functionality in place. I want to calculate thickness without having to go inside of an object (as seen in the gif).
Amazing reference
http://answers.unity3d.com/questions/386698/detecting-how-many-times-a-raycast-collides-with-a.html
This guy basically had the same question as me and has a solution that could possibly work. I'm not sure how Linecasting works vs Raycasting.
Keep:
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
RaycastHit hit;
if (Physics.Raycast(ray, out hit))
{
//obtain the vector where the ray hit the collider.
hitPoint = hit.point; //origin point
//offset the ray, keeping it along the XZ plane of the hit
Vector3 offsetDirection = -1 * hit.normal;
//offset a long way, minimum thickness of the object
ray.origin = hit.point + offsetDirection * 100;
//point the ray back at the first hit point
ray.direction = (hit.point - ray.origin).normalized;
Replace:
//raycast all, because there might be other objects in the way
RaycastHit[] hits = Physics.RaycastAll(ray);
foreach (RaycastHit h in hits)
{
if (h.collider == hit.collider)
{
hitBack = h.point; //destination point
}
}
With (credits to MirrorMirror's insightful post, and #ryemoss for his instrumental advice and assistance):
int counter = 0;
bool calculating = false; //set this to true on click
Vector3 Point, PreviousPoint, Goal, Direction;
Point = ray.origin;
Goal = hit.point;
Direction = ray.direction;
PreviousPoint = Vector3.zero;
while (calculating == true)
{
counter++;
RaycastHit hit2;
if (Physics.Linecast(Point, Goal, out hit2))
{
if(counter > 100)
{
hitBack = hitPoint;
counter = 0;
calculating = false;
break;
}
PreviousPoint = hit2.point;
Point = hit2.point + (Direction / 10000f);
}
else
{
if (PreviousPoint == Vector3.zero)
hitBack = hitPoint;
else
hitBack = PreviousPoint;
calculating = false;
counter = 0;
}
}
Linecast vs Raycast
With a raycast you set the start point, the direction, and the distance to check in that direction, with a linecast you simply set start and end points and it checks between those 2 points.
So, if you know the end destination specifically, use linecast, if you want to check in a specific direction but have no specific end point, use raycast.
Solution
First, use the initial raycast to obtain the first point, hit.point. Then, set the ray.origin to a point in world space outside the collider (the collider of the object we first collided with to obtain hit.point), and set the ray.direction to face the ray back at the first point, hit.point.
Finally, use a while loop to create a new linecast, at ray.origins new position (updated each time through the while loop until a linecast reaches hit.point), each time a collision with the object occurs until a linecast reaches hit.point. Once hit.point has been reached, it means every surface of the object was hit and on each hit, a new line was created until a line reached the first initial point, hit.point. To calculate thickness, take the distance between the first hit, hit.point, and the hit previous to the reverse linecast hitting hit.point, PreviousPoint.
UPDATE
1-Revise the code to properly handle 1-sided objects (ex: Planes).
2-Added counter to prevent special cases in which calculation not possible.
3-Improve readability.

Rotation of object is affecting my FixedJoint2D anchoring point

I have a problem with anchoring a Fixedpoint2D on a 2D object. The fixed point is used to make a circle "sticky" and I have no problem doing so IF the object that will stick to it is at a rotation of (0,0,0)
HOWEVER, if I rotate the object that will stick to it on the z axis, the anchoring point changes to a rotated point on the edge of the circle at the moment of contact. I want to find out what is causing this since I do not use the objects rotation in any of my calculations for the anchoring point.
Can anybody help me? I am relatively new to unity.
void OnCollisionEnter2D(Collision2D c)
{
if (hit != true)
{
Debug.Log("STICK");
FixedJoint2D joint = gameObject.AddComponent<FixedJoint2D>();
joint.connectedBody = c.rigidbody;
//calculate new anchor point
float newX = gameObject.transform.position.x - c.gameObject.transform.position.x;
float newY = gameObject.transform.position.y - c.gameObject.transform.position.y;
Vector3 v = new Vector3();
v.x = newX;
v.y = newY;
v.z = 0;
joint.connectedAnchor = v;
}
}
FixedJoint2D component is on the Large circle,
Transform rotation of the small object LEFT = (0,0,0), RIGHT = (0,0,90)

Determining if quarternion rotation is clockwise or counter clockwise

I am using the following code to handle rotating my player model to the position of my mouse.
void Update() {
// Generate a plane that intersects the transform's position with an upwards normal.
Plane playerPlane = new Plane(Vector3.up, transform.position);
// Generate a ray from the cursor position
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
// Determine the point where the cursor ray intersects the plane.
// This will be the point that the object must look towards to be looking at the mouse.
// Raycasting to a Plane object only gives us a distance, so we'll have to take the distance,
// then find the point along that ray that meets that distance. This will be the point
// to look at.
float hitdist = 0f;
// If the ray is parallel to the plane, Raycast will return false.
if (playerPlane.Raycast(ray, out hitdist)) {
// Get the point along the ray that hits the calculated distance.
var targetPoint = ray.GetPoint(hitdist);
// Determine the target rotation. This is the rotation if the transform looks at the target point.
Quaternion targetRotation = Quaternion.LookRotation(targetPoint - transform.position);
// Smoothly rotate towards the target point.
transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, speed * Time.deltaTime); // WITH SPEED
//transform.rotation = Quaternion.Slerp(transform.rotation, targetRotation, 1); // WITHOUT SPEED!!!
}
I would like to be able to determine if the rotation is clockwise or counter-clockwise for animation purposes. What would be the best way of handling this? I'm fairly unfamiliar with quaternions so I'm not really sure how to approach this.
Angles between quaternions are unsigned. You will always get the shortest distance, and there's no way of defining "counter-clockwise" or "clockwise" unless you actively specify an axis (a point of view).
What you CAN do, however, is to take the axis that you're interested in (I assume it's the normal to your base plane.. perhaps the vertical of your world?) and take the flat 2D components of your quaternions, map them there and compute a simple 2D angle between those.
Quaternion A; //first Quaternion - this is your desired rotation
Quaternion B; //second Quaternion - this is your current rotation
// define an axis, usually just up
Vector3 axis = new Vector3(0.0f, 1.0f, 0.0f);
// mock rotate the axis with each quaternion
Vector3 vecA = A * axis;
Vector3 vecB = B * axis;
// now we need to compute the actual 2D rotation projections on the base plane
float angleA = Mathf.Atan2(vecA.x, vecA.z) * Mathf.Rad2Deg;
float angleB = Mathf.Atan2(vecB.x, vecB.z) * Mathf.Rad2Deg;
// get the signed difference in these angles
var angleDiff = Mathf.DeltaAngle( angleA, angleB );
This should be it. I never had to do it myself and the code above is not tested. Similar to: http://answers.unity3d.com/questions/26783/how-to-get-the-signed-angle-between-two-quaternion.html
This should work even if A or B are not Quaternions, but one of them is an euler-angle rotation.
Two dimensional quaternions (complex numbers) have a signed angle. But, the more correct way to think about complex numbers is with an unsigned angle which is relative to either the XY oriented plane or the YX oriented plane. I.E. a combination of an unsigned angle an an oriented plane of rotation.
In 2D there are only two oriented planes of rotation so the idea of a "signed angle" is really just a trick to get both the unsigned angle and the oriented plane of rotation packed into a single number.
For a quaternion the "signed angle" trick cannot be used because in 3D you have an infinite number of oriented planes you can rotate in, so a single signed angle cannot encode all the rotation information like it can in the 2D case.
The only way for a signed angle to make sense in 3D is with reference to a particular oriented plane, such as the XY oriented plane.
-- UPDATE --
This is pretty easy to solve as a method on a quaternion class. If all you want to know is "is this counter clockwise", then since we know the rotation angle is from 0 to 180, a positive dot product between the quat's axis of rotation and the surface normal should indicate that we're rotating counter clockwise from the perspective of that surface. And a negative dot product indicates the opposite. Ignoring the zero case, this should do the trick with much less work:
public bool IsCounterClockwise( in Vector3 normal ) => I*normal.X + J*normal.Y + K*normal.Z >= 0;