Lerp point between position A,B while controlling the min and max Y-value - c#

I have an object A that I can move freely, and and object B that also moves freely but always along the ground.
I wrote some code to make a point always interpolate at 50% distance between A,B. I modified the code so that if object A goes below listenerMinY on the Y-axis, it will move closer to point A.
This all works great. However one more thing I want to add is that if object A goes above listenerMaxY on the Y-axis, the point cannot go any higher, in other words I want to Clamp the point to a position on the Y-axis. And I do not know how to do that using this Lerp.
As you can see I am currently trying to clamp the Y-position to listenerMaxY which does limit it's movement on the Y-axis, but then it does not interpolate correctly and leaves the "line" between A,B.
if (Physics.Raycast(transform.position, Vector3.down, out hit, Mathf.Infinity, groundLayer))
{
downwardDistance = hit.distance;
}
//listenerMinY = 20, listenerMaxY = 60
float percentageDistance = downwardDistance / listenerMinY;
Vector3 interpolation = Vector3.Lerp(transform.position, centerGround.position, Mathf.Clamp(percentageDistance, 0, .5f));
point.position = new Vector3(interpolation.x, Mathf.Clamp(interpolation.y, 0, listenerMaxY), interpolation.z);
point.rotation = transform.rotation;
Full code (attach to camera or any Object A) for testing:
public class CameraRay : MonoBehaviour
{
[SerializeField] LayerMask groundLayer;
[SerializeField] Transform centerGround;
[SerializeField] Transform listener;
[SerializeField] int listenerMinY;
[SerializeField] int listenerMaxY;
private void Update()
{
RaycastHit hit;
float forwardDistance = 0;
float downwardDistance = 0;
// Control center ground pos
if (Physics.Raycast(transform.position, transform.forward, out hit, Mathf.Infinity, groundLayer))
{
forwardDistance = hit.distance;
Debug.DrawRay(transform.position, transform.forward * forwardDistance, Color.blue);
centerGround.position = hit.point;
}
// Check distance Y from ground, green = <20, red = >listener Y limit, else yellow
if (Physics.Raycast(transform.position, Vector3.down, out hit, Mathf.Infinity, groundLayer))
{
downwardDistance = hit.distance;
if (downwardDistance < listenerMinY)
Debug.DrawRay(transform.position, Vector3.down * downwardDistance, Color.green);
else if(downwardDistance < listenerMaxY)
Debug.DrawRay(transform.position, Vector3.down * downwardDistance, Color.yellow);
else
Debug.DrawRay(transform.position, Vector3.down * downwardDistance, Color.red);
}
float percentageDistance = downwardDistance / listenerMinY;
Vector3 interpolation = Vector3.Lerp(transform.position, centerGround.position, Mathf.Clamp(percentageDistance, 0, .5f));
listener.position = new Vector3(interpolation.x, Mathf.Clamp(interpolation.y, 0, listenerMaxY), interpolation.z);
listener.rotation = transform.rotation;
}
}

So if I understand you correctly you want to stay on the vector A -> B or in your specific case centerGround.position -> transform.position but whatever happens the Y component of he resulting vector should not be bigger than listenerMaxY.
So you can probably calculate backwards what factor you would need to maximum reach that Y level like e.g.
// returns the factor that would be needed to be passed into Lerp in order to result in listenerMaxY
// the result is clamped between 0 and 1
var maxFactor = Mathf.InverseLerp(transform.position.y, centerGround.position.y, listenerMaxY);
// your original factor based on the distance
var percentageDistance = downwardDistance / listenerMinY;
// Now take the minimum of the two factors
var factor = Mathf.Min(percentageDistance, maxFactor);
// and use that as the factor of Lerp
var interpolation = Vector3.Lerp(transform.position, centerGround.position, factor);
// The result is now always a position on the line between
// transform.position and centerGround.position but with y <= listenerMaxY
// (EXCEPT if both positions are already higher than that anyway of course)
listener.position = interpolation;
See Mathf.InverseLerp => basically as the name says this works exactly the other way round: Give it a start and end point and the expected value and it will return the factor that would result in that last value when passed into Lerp

Related

How to use LookRotation to rotate an object based on hand positions?

I'm creating an interaction system for a VR game and I'm struggling with two hand interactions. I'm using the Quaternion.LookRotation() method to generate the rotation of the grabbed object based on the positions and rotations of the hands. The forward part is pretty simple:
Vector3 fwd = (primaryHand.position - secondaryHand.position).normalized;
The "up" part is what I have difficulty with. Initially I tried using the average up direction of both hands:
Vector3 avgUp = Quaternion.Slerp(primaryHand.rotation, secondaryHand.rotation, 0.5f) * Vector3.up;
There is an issue with this approach: the hand's up vector might get aligned with the fwd vector, which causes the object to flip over when it goes over it. Here is a simple illustration of the problem:
The light green arrows represent the up direction of the hands, while the dark green is the calculated direction used as an argument for the LookRotation() method.
The obvious solution seems to be to pick a different fixed vector instead of "up", one which won't be so easily aligned with the fwd vector. In the example it could be a vector aligned with the fingers. But keep in mind that there are no restrictions on initial hand rotation so no matter which vector you choose the hands can always happen to be rotated so that the vectors align. And even if you pick the an optimal vector dynamically (one that is perpendicular to fwd), it's still at best 90 degrees from aligning with fwd.
To solve this I tried restricting the direction to the values which don't cause problems but this caused another issues (I had difficulties with determining which values are ok and which should be discarded). I feel like I'm doing something wrong here, is there any better solution to this problem?
You can calculate the delta rotations of the hands and apply it to the "base up" of the object (the new up if we only take into account the change in position of hands...which will of course be orthogonal to the axis of the object). Then determine the change in angle that results in that up being rotated with each hand. Average those angles out, then apply those angles with the hand-hand axis using Quaternion.AngleAxis to the "base up" from earlier. Then you have your forward and up for Quaternion.LookRotation).
Below is an example of how you can use this, including VR hand noise simulation. To see the test, create a new scene in unity and attach this to the camera and it will build the scene on play start. There is a grip/release gui that will appear in Play view. You can adjust the hand rotation in Scene view
Vector3 leftHandPosCurrent;
Vector3 rightHandPosCurrent;
Vector3 worldAxisPrev;
Quaternion leftHandRotPrev;
Quaternion leftHandRotCurrent;
Quaternion rightHandRotPrev;
Quaternion rightHandRotCurrent;
bool isGripping;
bool firstFrameGripping;
Rigidbody grippedRB;
Transform leftHand;
Transform rightHand;
Quaternion targetRot;
/*
* On subsequent frames of gripping, calculate deltas in positions and
* rotations, average out the hand's effects, then apply them to the gripped
* object
*/
void HandleGrippedRot()
{
Vector3 worldAxisCurrent = rightHandPosCurrent - leftHandPosCurrent;
if (!firstFrameGripping)
{
Vector3 prevUp = targetRot * Vector3.up;
// we haven't moved the transform based on the hands yet, so
// find the new up would be ignoring hand rotations
Vector3 newUp = Quaternion.FromToRotation(worldAxisPrev,
worldAxisCurrent) * prevUp;
float leftHandAngle = GetDegRot(newUp, leftHandRotPrev,
leftHandRotCurrent, worldAxisCurrent);
float rightHandAngle = GetDegRot(newUp, rightHandRotPrev,
rightHandRotCurrent, worldAxisCurrent);
float avgAngle = (leftHandAngle + rightHandAngle) * 0.5f;
newUp = Quaternion.AngleAxis(avgAngle, worldAxisCurrent) * prevUp;
targetRot = Quaternion.LookRotation(worldAxisCurrent,
newUp);
}
else
{
firstFrameGripping = false;
}
leftHandRotPrev = leftHandRotCurrent;
rightHandRotPrev = rightHandRotCurrent;
worldAxisPrev = worldAxisCurrent;
}
/*
* Given the "up" of the object without taking hand rotations into account
* and the axis, determine how a hand's delta rotation affects that up
* around the axis and return the angle of that rotation
*/
float GetDegRot(Vector3 baseUp, Quaternion prevHandRot, Quaternion curHandRot,
Vector3 axis)
{
Vector3 adjUp = (curHandRot * Quaternion.Inverse(prevHandRot)) * baseUp;
adjUp = Vector3.ProjectOnPlane(adjUp, axis);
return Vector3.SignedAngle(baseUp, adjUp, axis);
}
void Update()
{
AddVRNoise(leftHand);
AddVRNoise(rightHand);
leftHandPosCurrent = leftHand.position;
rightHandPosCurrent = rightHand.position;
leftHandRotCurrent = leftHand.rotation;
rightHandRotCurrent = rightHand.rotation;
if (isGripping)
{
HandleGrippedRot();
}
}
void StartGrip()
{
if (isGripping) return;
isGripping = true;
firstFrameGripping = true;
// grippedTransform is set accordingly at some point
}
void EndGrip()
{
if (!isGripping) return;
isGripping = false;
}
/*
* example of using targetRot to move rb
*/
private void FixedUpdate()
{
if (!isGripping) return;
Quaternion delta = targetRot
* Quaternion.Inverse(grippedRB.transform.rotation);
delta.ToAngleAxis(out float angle, out Vector3 axis);
// convert to shortest angle form
if (angle > 180f)
{
axis = -axis; angle = 360f - angle;
}
grippedRB.angularVelocity = angle * 0.25f * axis;
}
/*
* just for testing purposes
*/
void Start()
{
leftHand = CreateHand(true);
leftHand.position = Vector3.left;
rightHand = CreateHand(false);
rightHand.position = Vector3.right;
CreateArrow();
}
/*
* just for testing purposes
*/
void AddVRNoise(Transform hand)
{
Quaternion noise = Random.rotation;
noise.ToAngleAxis(out float angle, out Vector3 axis);
angle = 100f * Time.deltaTime;
noise = Quaternion.AngleAxis(angle, axis);
Quaternion noisyRot = hand.rotation * noise;
hand.rotation = noisyRot;
}
/*
* just for testing purposes
*/
void OnGUI()
{
if (GUI.Button(new Rect(0, 0, 100, 50), "Grip"))
{
StartGrip();
}
if (GUI.Button(new Rect(100, 0, 100, 50), "Release"))
{
EndGrip();
}
}
/*
* just for testing purposes
*/
Transform CreateHand(bool isLeft)
{
string handName = isLeft ? "Left" : "Right";
GameObject hand = new GameObject($"{handName}hand");
GameObject palm = GameObject.CreatePrimitive(PrimitiveType.Cube);
palm.transform.localScale = new Vector3(0.5f, 0.2f, 1f);
palm.transform.SetParent(hand.transform);
GameObject thumb = GameObject.CreatePrimitive(PrimitiveType.Cube);
thumb.transform.localScale = new Vector3(0.2f, 0.1f, 0.1f);
thumb.transform.SetParent(hand.transform);
thumb.transform.localPosition = new Vector3(isLeft ? 0.32f : -0.32f,
0f, -.31f);
return hand.transform;
}
/*
* just for testing purposes
*/
void CreateArrow()
{
GameObject arrow = new GameObject();
GameObject body = GameObject.CreatePrimitive(PrimitiveType.Cube);
body.transform.localScale = new Vector3(1f, 1f, 5f);
body.transform.SetParent(arrow.transform);
GameObject head = GameObject.CreatePrimitive(PrimitiveType.Cube);
head.transform.SetParent(arrow.transform);
head.transform.localEulerAngles = Vector3.up * 45f;
head.transform.localPosition = new Vector3(0f, 0f, 2.5f);
grippedRB = arrow.AddComponent<Rigidbody>();
grippedRB.useGravity = false;
arrow.transform.position = 2f * Vector3.up;
}

When I make an object face a mouse cursor in Unity it eventually offsets

I made a script that makes the player point towards the mouse cursor, but recently I discovered a bug. When I move the mouse cursor too much (An example being when I spin the mouse around the object in circles, causing the object to move around.), the object ends up pointing a bit off of where the mouse should be. As in, the cursor would signal the object to look at it, and the object ends up looking the slightest bit off, making it feel quite odd after maneuvering quickly. How can I make it so the object always faces the cursor, with no offsets, even when I move the cursor as much as possible.
private void LateUpdate()
{
Vector3 lookAtPoint = Camera.main.ScreenToWorldPoint(Input.mousePosition);
Vector3 mousePoint = new Vector3(lookAtPoint.x, lookAtPoint.y, 0);
float angle = getAngle(transform.position, mousePoint);
transform.rotation = Quaternion.Lerp(transform.rotation, Quaternion.Euler(0, 0, angle), 9f * Time.deltaTime);
float getAngle(Vector3 currentLocation, Vector3 mouseLocation)
{
float x = mouseLocation.x - currentLocation.x;
float y = mouseLocation.y - currentLocation.y;
return angle = Mathf.Rad2Deg * Mathf.Atan2(y, x);
}
}
Looks like it's down to the way that you are using Quaternion.Lerp(). In particular, the last parameter - it is meant to be a value between 0f and 1f which you supply, it does not auto-increment for you.
So to fix this issue, what you want to do is save off a float somewhere. When you move the mouse (mouse position has changed since last frame) then start that value at 0f again. Then increment it at some value every frame until it is equal to or greater than 1f.
Doing this will not only fix your bug. It will, depending on how fast your increment, give you a smooth rotation effect. Below is an example.
internal class SomeClass : MonoBehaviour
{
private float lerpAmount = 0f;
private Vector3 cachedMousePosition = Vector3.zero;
private void LateUpdate()
{
var mousePosition
= Camera.main.ScreenToWorldPoint(Input.mousePosition)
* new Vector3(1, 1, 0);
bool recalculateRotation = false;
if (this.cachedMousePosition != mousePosition)
{
this.lerpAmount = 0f;
recalculateRotation = true;
}
if (this.lerpAmount < 1f)
{
this.lerpAmount = Mathf.Min(this.lerpAmount + Time.deltaTime, 1f);
recalculateRotation = true;
}
if (recalculateRotation)
{
float angle = getAngle(transform.position, mousePoint);
transform.rotation = Quaternion.Lerp(transform.rotation, Quaternion.Euler(0, 0, angle), this.lerpAmount);
}
float getAngle(Vector3 currentLocation, Vector3 mouseLocation)
{
float x = mouseLocation.x - currentLocation.x;
float y = mouseLocation.y - currentLocation.y;
return angle = Mathf.Rad2Deg * Mathf.Atan2(y, x);
}
}

Move 2D object at constant speed and turn towards touch point

I've been trying for a while to get a 2D player to work kind of like a bullet that is always moving forward (forward being in this case the local X axis for the GameObject, as that's the way that the character is facing) and only changes direction when you touch a point on the screen, in which case it should smoothly start turning towards that point.
One problem I have is that I can't manage to keep the character moving smoothly at a constant speed in the last direction it was facing before, and the other problem that I'm finding is that the character is turning around the wrong axis and instead of rotating based on the Z axis, it's always rotating on the Y axis, which makes the sprite become invisible to the camera.
Here's the code that I have right now:
Vector3 lastTouchPoint;
private void Start()
{
lastTouchPoint = transform.position;
}
void Update()
{
if (Input.touchCount > 0)
{
// The screen has been touched so store the touch
Touch touch = Input.GetTouch(0);
if (touch.phase == TouchPhase.Stationary || touch.phase == TouchPhase.Moved)
{
// If the finger is on the screen, move the object smoothly to the touch position
lastTouchPoint = Camera.main.ScreenToWorldPoint(new Vector3(touch.position.x, touch.position.y, 10));
}
}
transform.position = Vector3.Lerp(transform.position, lastTouchPoint, Time.deltaTime);
//Rotate towards point
Vector3 targetDir = lastTouchPoint - transform.position;
transform.LookAt(lastTouchPoint);
}
Thanks in advance!
keep the character moving smoothly at a constant speed
You probably didn't understand what Lerp actually is: This interpolates between the two positions on the given factor where 0 means fully the first position, 1 means fully the last position and e.g. 0.5f would mean in the center between both positions.
This results in faster speeds if the positions are further apart and becomes slower and slower the smaller the distance between both positions becomes. In some cases especially with a factor that small as in your case the object might even never actually reach the target position.
Using this with a dynamic factor of Time.deltaTime makes no sense as this value changes every frame and jitters somewhere around 0,017 (assumin 60 FPS).
You could rather use Vector3.MoveTowards with a fixed constant speed
// set via the Inspector
public float speedInUnitsPerSecond = 1;
...
transform.position = Vector3.MoveTowards(transform.position, lastTouchPoint, Time.deltaTime * speedInUnitsPerSecond);
if you want to keep moving but stop once the touched position is reached.
If you rather wanted to continue moving in the according direction no matter what you could rather store the direction instead of a position and use a straight forward Transform.Translate
// set via the Inspector
public float speedInUnitsPerSecond = 1;
private Vector2 lastDirection;
privtae void Update()
{
...
// If the finger is on the screen, move the object smoothly to the touch position
var touchPosition = Camera.main.ScreenToWorldPoint(new Vector3(touch.position.x, touch.position.y, 10));
lastDirection = (touchPosition - transform.position).normalized;
...
// move with constant speed in the last direction
transform.Translate(lastDirection * Time.deltaTime * speedInUnitsPerSecond);
...
}
the character is turning around the wrong axis and instead of rotating based on the Z axis, it's always rotating on the Y axis
Note that Transform.LookAt has an optional second parameterworldUp which by default is Vector3.up so a rotation around the global Y axis!
Since you rather want a rotation around the Z axis you should pass
transform.LookAt(lastTouchPoint, Vector3.forward);
I don't know your setup ofcourse but also note that
LookAt
Rotates the transform so the forward vector points at worldPosition.
As you describe it it is also possible that you don't want the objects forward vector to point towards the target position but actually rather the objects right (X) vector!
You can do this by rather simply directly setting the transform.right like e.g.
transform.right = (lastTouchPoint - transform.position).normalized;
or
transform.right = lastDirection;
Btw it would actually be enough to set this rotation only once, namely the moment it changes so in
if (touch.phase == TouchPhase.Stationary || touch.phase == TouchPhase.Moved)
{
// If the finger is on the screen, move the object smoothly to the touch position
lastTouchPoint = Camera.main.ScreenToWorldPoint(new Vector3(touch.position.x, touch.position.y, 10));
transform.right = (lastTouchPoint - transform.position).normalized;
}
or
if (touch.phase == TouchPhase.Stationary || touch.phase == TouchPhase.Moved)
{
// If the finger is on the screen, move the object smoothly to the touch position
var touchPosition = Camera.main.ScreenToWorldPoint(new Vector3(touch.position.x, touch.position.y, 10));
lastDirection = (touchPosition - transform.position).normalized;
transform.right = lastDirection;
}
I ended up finding the answer to my own problem using code to rotate smoothly from another post. Here's the code:
Vector3 lastTouchPoint;
Vector3 direction;
Vector3 vectorToTarget;
//Character controller variables
public float moveSpeed = 5f;
public float angularSpeed = 3f;
private void Start()
{
lastTouchPoint = transform.position;
}
void Update()
{
if (Input.touchCount > 0)
{
// The screen has been touched so store the touch
Touch touch = Input.GetTouch(0);
if (touch.phase == TouchPhase.Began)
{
// If the finger is on the screen, move the object smoothly to the touch position
lastTouchPoint = Camera.main.ScreenToWorldPoint(new Vector3(touch.position.x, touch.position.y, 10));
direction = lastTouchPoint - transform.position;
vectorToTarget = lastTouchPoint - transform.position;
}
}
transform.position += direction.normalized * moveSpeed * Time.deltaTime;
float angle = Mathf.Atan2(vectorToTarget.y, vectorToTarget.x) * Mathf.Rad2Deg;
Quaternion q = Quaternion.AngleAxis(angle, Vector3.forward);
transform.rotation = Quaternion.Slerp(transform.rotation, q, Time.deltaTime * angularSpeed);
}

Unity enemy ai always firing bullets high over the player

Got an issue where the enemy will fire at the player, but always seems to go high or to the side of the player even when the player is stationary and isn't moving. Am I doing something wrong in my code which creates this wild issue or is it just a random annoying bug?
Using the same script for the player albeit it under a different name works, which leads me to believe the issue lies within the fire point. Under the player's script I fire like so:
// Get the place the player has clicked
Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
// Holds information regarding the mouseclick
RaycastHit hitInfo;
// Now work out if we fire or not
if (Physics.Raycast(ray, out hitInfo))
{
if(hitInfo.distance < maxRange)
{
FireAtPoint(hitInfo.point);
Whereas in the enemy script it is just done through the player's position.
// Holds information regarding the mouseclick
RaycastHit hitInfo;
// Now work out if we fire or not
if (Physics.Raycast(player.transform.position,transform.forward, out hitInfo))
{
Is this underlying issue in the Physics.Raycast call then?
Rest of code for reference:
//More above this but doesn't influence the firing
if (Physics.Raycast(player.transform.position,transform.position, out hitInfo))
{
if (hitInfo.distance < maxRange)
{
FireAtPoint(hitInfo.point);
}
}
private void FireAtPoint(Vector3 point)
{
// Get the velocity to fire out at
var velocity = FiringVelocity(point, angle);
Rigidbody rg = Instantiate(bulletPrefab.gameObject, firePoint.position, firePoint.rotation).GetComponent<Rigidbody>();
EnemyBulletController newProjectile = rg.GetComponent<EnemyBulletController>();
newProjectile.speed = velocity;
}
private Vector3 FiringVelocity(Vector3 destination, float angle)
{
// Get the direction of the mouse click from the player, then get the height differential.
Vector3 direction = destination - transform.position;
float height = direction.y;
height = 0;
// Get the distance in a float of the vector3
float distance = direction.magnitude;
// Turn the firing angle into radians for calculations, then work out any height differential
float AngleRadians = angle * Mathf.Deg2Rad;
direction.y = distance * Mathf.Tan(AngleRadians);
distance += height / Mathf.Tan(AngleRadians);
// Calculate the velocity magnitude
float velocity = Mathf.Sqrt(distance * Physics.gravity.magnitude / Mathf.Sin(2 * AngleRadians));
// Return the normalized vector to fire at.
return velocity * direction.normalized;
}
Picture for reference:
Your equation for computing the velocity looks doubtful. Let's re-derive it:
The equations of free-fall motion under constant gravity are:
After rearranging by substituting the first into the second, we find an expression for the firing velocity:
This is different to what you have, as you are missing the h/d term; said term also gives a constraint on the allowed values of θ:
(Basically means that if you fire directly at the target the bullet would never reach due to gravity)
There are many other problems with your code; just to list three:
Why set height to zero?
Why add a correction to distance? The correction has no physical interpretation.
The fix suggested by #BasillePerrnoud
Amended code:
private Vector3 FiringVelocity(Vector3 destination, float angle)
{
Vector3 direction = destination - transform.position;
float height = direction.y;
float distance = Mathf.Sqrt(direction.x * direction.x + direction.z * direction.z); // *horizontal* distance
float radians = angle * Mathf.Deg2Rad;
float hOverd = height / distance;
float tanAngle = Mathf.Tan(radians);
if (tanAngle <= hOverd)
// throw an exception or return an error code, because no solution exists for v
float cosAngle = Mathf.Cos(radians);
direction.Y = distance / cosAngle;
float velocity = Mathf.Sqrt((distance * Physics.gravity.magnitude) /
(2 * cosAngle * cosAngle * (tanAngle - hOverd)));
return velocity * direction.normalized;
}
I think you use Raycast wrongly. According to the doc, the second argument is the direction, not the destination:
if (Physics.Raycast(player.transform.position,transform.position, out hitInfo))
Should be
if (Physics.Raycast(transform.position, player.transform.position -
transform.position, out hitInfo))
That would explain why it is not firing at the right moment and why the direction is not accurate (since hitInfo is wrong)

Unity3D: Find Raycasthit direction

I'm trying to make a Billiard game and I wanna calculate the direction on which the Cue Ball(white ball) will be moving after it hits another ball.
As you can see I wanna calculate the angle/direction in which the RAY hits the ball and the angle/direction on which the raycast will change its direction into. I need the angle to display as a Vector3 variable so I can use it on the linerenderer(3).
I already calculated the direction that the ball that gets hit will go.
If you could help me on this that would be great!
Current code:
RaycastHit hitz;
if (Physics.SphereCast(transform.position, 0.8f, location - transform.position, out hitz, Mathf.Infinity, lmm2))
{
lineRenderer2 = hitz.collider.GetComponentInChildren<LineRenderer>();
lineRenderer2.SetVertexCount(2);
if (!Input.GetKey(KeyCode.Mouse0))
lineRenderer2.SetPosition(0, hitz.point);
if (!Input.GetKey(KeyCode.Mouse0))
{
Vector3 start = hitz.point;
Vector3 end = start + (-hitz.normal * 4);
if (lineRenderer2)
{
if (!Input.GetKey(KeyCode.Mouse0))
lineRenderer2.SetPosition(1, end);
}
if(lineRenderer3)
{
anglelel = Vector3.Angle(hitz.normal, hitz.point);
Vector3 cross = Vector3.Cross(hitz.normal, hitz.point);
if(cross.y > 0)
{
tzt = Quaternion.AngleAxis(90f, hitz.normal) *realStick.transform.forward;
}
if (cross.y < 0)
{
anglelel = -anglelel;
tzt = Quaternion.AngleAxis(270f, hitz.normal) * realStick.transform.forward;
}
Vector3 start2 = hitz.point;
Vector3 end2 = start2 + ((tzt) * 5f);
lineRenderer3.SetPosition(0, hitz.point);
lineRenderer3.SetPosition(1, end2);
}
}
}
Thank you for your time.
Edit:
This part of the code has been changed to this one, currently makign some progress but still, it's not good enough.
Before
if(lineRenderer3)
{
Vector3 start2 = hitz.point;
//THIS IS WHERE I'M CURRENTLY STUCK AT
Vector3 end2 = start2 + (hitz.point * 0.7f);
lineRenderer3.SetPosition(0, hitz.point);
lineRenderer3.SetPosition(1, end2);
}
After
if(lineRenderer3)
{
anglelel = Vector3.Angle(hitz.normal, hitz.point);
Vector3 cross = Vector3.Cross(hitz.normal, hitz.point);
if(cross.y > 0)
{
tzt = Quaternion.AngleAxis(90f, hitz.normal) *realStick.transform.forward;
}
if (cross.y < 0)
{
anglelel = -anglelel;
tzt = Quaternion.AngleAxis(270f, hitz.normal) * realStick.transform.forward;
}
Vector3 start2 = hitz.point;
Vector3 end2 = start2 + ((tzt) * 5f);
lineRenderer3.SetPosition(0, hitz.point);
lineRenderer3.SetPosition(1, end2);
}
Let's take this piece by piece. First off, this is a classic Physics 101 problem. The angle 2 billiard balls make on impact is a perfect 90 degree angle. Note how the green and blue vector in the following picture make a right angle:
Now, you should also notice that from the point of contact to the center of both balls is normal to the surface of both balls. This means that in unity, we can use the hit.normal to get the direction of travel for the ball we hit. We just need to invert it by doing: -1 * hit.normal
Now, to get the direction the cue ball travels, we just need to rotate the previous vector 90 degrees. We can do this with a quaternion. We create a 90degree rotation about the up direction (or whatever direction is normal to the pool table) by doing: Quaternion.AngleAxis(-90, Vector3.up)
We can then calculate the angle between the original vector of travel and the angle the cue ball will travel at by doing Vector3.Angle(-1 * cue.up, rotate90 * hit.normal)
Let's look at this visual example from my test scene:
I color coded the vectors in unity to match the diagram above. The only difference you may notice is the black vector, which represents our hit.normal.
Here's the code:
public class Main : MonoBehaviour {
public Transform cue,cueBallPostHit;
public int dist = 10;
public Color red,green,blue;
RaycastHit hit;
float scale,ballAngle;
Quaternion rotate90;
Vector3 cueBallHitPosition;
void Start () {
rotate90 = Quaternion.AngleAxis(-90, Vector3.up);
}
void FixedUpdate () {
if(Physics.SphereCast(cue.position, .5f, cue.up, out hit, dist))
{
// Calculate variables
cueBallHitPosition = hit.point + (.5f * hit.normal);
scale = (cue.position - hit.point).magnitude;
ballAngle = Vector3.Angle(-1 * cue.up, rotate90 * hit.normal);
print(ballAngle);
// Cue Ball Direction and normal
Debug.DrawLine(cue.position, cueBallHitPosition, red);
Debug.DrawRay(cueBallHitPosition, hit.normal, Color.black);
// Ball direction
Debug.DrawRay(hit.point + (-.5f * hit.normal), -1 * hit.normal * scale, blue);
// Cue Ball Direction
Debug.DrawRay(cueBallHitPosition, rotate90 * hit.normal * scale, green);
// Visual for where the ball will hit
cueBallPostHit.position = cueBallHitPosition;
}
else
{
Debug.DrawRay(cue.position, cue.up * dist, blue);
cueBallPostHit.position = cue.position + (2 * cue.up);
}
}
}
Hopefully that should be enough to help you get started in the right direction, but if you have questions, let me know and I'll add some more explanations.

Categories