I have a start and end, i need to place an adjustable amount of rays on the line from start to end. But I can't figure out how to correctle place them. I almost finished, but rays is not placed at the end.
Green (2, 0, 2) for start and Red (2, 0, -2) for end with distance between 4
This is my result with 2 rays, the center ray need to be at the end (red ray), and next rays add should be at the center of start and end rays
What happens if i adding third ray, the end ray position isn't in use
And 5 rays. The first and the last rays need to be at the start and the end. Other 3 rays should to be between
This is my code:
public class VehicleAroundCast : MonoBehaviour
{
[SerializeField] private int sideRaysAmount;
[SerializeField] private Vector3 offset;
private void Update()
{
Vector3 startRayPos = transform.position + Vector3.left * offset.x + Vector3.forward * offset.z;
Vector3 endRayPos = transform.position + Vector3.left * offset.x + Vector3.back * offset.z;
float dist = Vector3.Distance(startRayPos, endRayPos);
Debug.DrawRay(startRayPos, Vector3.down, Color.green);
Debug.DrawRay(endRayPos, Vector3.down, Color.red);
for (int i = 0; i < sideRaysAmount; i++)
{
float step = dist / sideRaysAmount * i;
Vector3 position = (transform.position + Vector3.left * offset.x + Vector3.forward * offset.z) + Vector3.back * step;
Debug.DrawRay(position, Vector3.down);
}
}
}
Following up from the clarification through the comment I'd do a couple of things a bit different. I'll just edit your code so that it should work as you'd need it to and explain it in code comments.
public class VehicleAroundCast : MonoBehaviour
{
[SerializeField] [Min(0)] private int intermediateRayCount;
// i renamed this field so the name is more descriptive, intermediate meaning between start and end
// to make sure that no number below 0 can be assigned i added the [Min(0)] attribute, a number
// below 0 would break the logic and wouldn't make sense
[SerializeField] private Vector3 offset;
private void Update ()
{
// we dont want to clog up our Update() method with tons of lines of code
// this serves better maintainability as well
CastRays();
}
private void CastRays ()
{
Vector3 startRayPos = transform.position + Vector3.left * offset.x + Vector3.forward * offset.z;
Vector3 endRayPos = transform.position + Vector3.left * offset.x + Vector3.back * offset.z;
// if we dont have any intermediate rays we can save on the calculation that is necessary when we
// have intermediates, we just cast the the two rays and end the method by returning
if (intermediateRayCount == 0)
{
Debug.DrawRay(startRayPos, Vector3.down, Color.green);
Debug.DrawRay(endRayPos, Vector3.down, Color.red);
return;
}
// instead of using Vector3.Distance() we'll calculate the directional vector manually this way
// we can get not only the distance (directional vectors magnitude) but the direction to move
// along to space the rays as well
// subtarting the start position from the end position gives us the directional vector
Vector3 direction = endRayPos - startRayPos;
// a directional vectors magnitude gives the distance from start to end
float distance = direction.magnitude;
// after the distnace has been established we normalize the direction so that it's length is 1
direction.Normalize();
// we have at least two rays (start and end) so our total is the rays between start and end plus two
int totalRayCount = intermediateRayCount + 2;
// the space between the individual rays, we have to subtract one from the totalRayCount in order to
// place the the last ray at the end position (yes this could be optimized but i want to write out
// the logic fully so that it's clear what happens)
float spacing = distance / (totalRayCount - 1);
for (int i = 0; i < totalRayCount; i++)
{
// we can simply get the ray position by adding the direction multiplied by the spacing multiplied
// by the number of itterations we've gone through to the start position
// since the direction is normalized we can multiply it by the distance between rays to give it
// the length between two rays, if we then multiply it by the number of rays the current ray is,
// we get the distance as well as direction the current ray has to be placed away from the start
// position
Vector3 rayPosition = startRayPos + direction * spacing * i;
// i added some color so you can see start and end
if (i == 0)
Debug.DrawRay(startRayPos, Vector3.down, Color.green);
else if (i == totalRayCount - 1)
Debug.DrawRay(endRayPos, Vector3.down, Color.red);
else
Debug.DrawRay(rayPosition, Vector3.down, Color.white);
}
}
}
It's a simple logic mistake.
You wanted sideRaysAmount to be the amount of rays. But you are using it as amount of steps in between rays.
There is always exactly one step less than rays.
=> For the calculation of step you simply want to use
float step = dist / (sideRaysAmount - 1) * i;
so e.g.
var startRayPos = transform.position + Vector3.left * offset.x + Vector3.forward * offset.z;
var endRayPos = transform.position + Vector3.left * offset.x + Vector3.back * offset.z;
var dist = Vector3.Distance(startRayPos, endRayPos);
var stepSize = sideRaysAmount == 1 ? 0 : dist / (sideRaysAmount - 1f);
Debug.DrawRay(startRayPos, Vector3.down, Color.green);
Debug.DrawRay(endRayPos, Vector3.down, Color.red);
for (var i = 0; i < sideRaysAmount; i++)
{
var step = i * stepSize;
var position = transform.position + Vector3.left * offset.x + Vector3.forward * offset.z + Vector3.back * step;
Debug.DrawRay(position, Vector3.down);
}
Related
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
How to make a bullet fly in an arc when shooting and hit the player.
As in this picture . I tried to use formulas from physics, the body is thrown at an angle to the horizon, that's what came of it . But the bullet flies away into the void
velocity = Mathf.Round(Vector3.Distance(lastpos, transform.position) / Time.deltaTime);
lastpos = transform.position;
Vector3 direction = PlayeCar.position - Vector3.zero;
float angle = Mathf.Atan2(direction.y, direction.x); // радианы
float x = velocity * Mathf.Cos(angle) + gravity * (time* time) / 2;
float y = velocity * Mathf.Sin(angle);
transform.position = new Vector2(x, y)
;
Sample orientative method could be (The script would be attached to the gun that shots, so when the code referes to transform, refers to the gun's transfom):
public void LaunchTarget()
{
Vector3 initialSpeed = transform.forward; //shot direction, gun's forward
GameObject go = GameObject.Instantiate(m_bullet);//reference to bullet prefab
//in case you need to randomize the shot speed
initialSpeed = transform.forward * Random.Range(m_minLaunchSpeed, m_maxLaunchSpeed);
//set the initial position of the bullet in the gun
go.transform.position = transform.position;
// shoot (give speed to the bullets rigidbody)
go.GetComponent<Rigidbody>().velocity = initialSpeed;
//initially disabled soas to see the bullet when shot
go.SetActive(true);
}
Hope that helps
I did it !!!! at the time of the bullet’s flight, I turn it toward the player in an intermittent manner. And after a while I delete it
void Start()
{
rigidbodyBullet = GetComponent<Rigidbody2D>();
Player = GameObject.Find("carPlayer").GetComponent<Transform>();
_distance = Vector3.Distance(Player.position, transform.position);
Invoke("DestroyArcBullet", 1.5f);
limitDistance = _distance / 1.3f;
}
void Update()
{
transform.position += transform.up * Time.deltaTime * 5f;
_distance = Vector3.Distance(Player.position, transform.position);
if (_distance < limitDistance)
{
var turn = Quaternion.Lerp(transform.rotation,
Quaternion.LookRotation(Vector3.forward, Player.position - transform.position), Time.deltaTime * 2);
rigidbodyBullet.MoveRotation(turn.eulerAngles.z);
}
if (this.transform.position.y + this.transform.position.y <= -10 || this.transform.position.y + this.transform.position.y >= 10
|| this.transform.position.x + this.transform.position.x <= -10 || this.transform.position.x + this.transform.position.x >= 10)
{
Destroy(gameObject);
}
}
I am making a game where bullets can start orbiting a player. The orbits are circular, and not elliptical. Bullets have a drag factor of 0.
At first, I simply calculated the next position in the orbit, and divided the delta position by fixedDeltaTime to obtain its new velocity.
Unfortunately, it meant that fast bullets instead follow polygonal paths, and often miss a target.
I wanted to improve the precision by giving them an inward force, and a velocity nearly tangential to the orbit, so that they follow parabola segments around the player instead of straight lines.
The parabola segments are defined by their start and end point, and their vertices, which must lay on the orbit arc, with a velocity equal to the velocity I was previous using ((endpos - startpos) / fixedDeltaTime).
To calculate the parabola, I calculate the midway points on the arc and segment, and their difference is proportional to the force applied.
so we use the following names
fdt = fixedDeltaTime
t = fdt / 2
f = the force applied during the incoming physics frame
v0 = the velocity at the start of the frame
v1 = the velocity at the middle of the frame (at the vertex of the parabola)
dp1 = the relative position at the middle of the frame (midpos - startpos) and vertex of the parabola
dp2 = the relative position at the end of the frame (endpos - startpos)
The force is defined by these two equations:
// at vertex, only the "lateral" velocity remains
v1 = dp2 / fdt
// the difference between dp2 / 2 and dp1 is what the force can apply over half a frame
dp2 / 2 - dp1 = f * 0.5tt
therefore
(dp2 / 2 - dp1) / (0.5 * t * t) = f
(dp2 / 2 - dp1) / (0.5 * fdt/2 * fdt/2) = f
(dp2 - dp1 * 2) * 4 / (fdt * fdt) = f
//v0 is then easily calculated
v0 = v1 - t * force
v0 = (dp2 / fdt) - force * (fdt / 2)
We then get this working code:
Vector3 startPos = _rigidbody.position;
if (firstFrame == false && Vector3.Distance(predictedNextFramePos, startPos) > 0.01f)
Debug.Log("WTF");
Vector3 nextPosition;
GetLocalOrbitPos(nextTime, out nextPosition);
nextPosition += _parent.GetPosition();
float fdt = Time.fixedDeltaTime;
float halfTime = (time + nextTime) / 2f;
Vector3 halfPosition;
GetLocalOrbitPos(halfTime, out halfPosition);
halfPosition += _parent.GetPosition();
Vector3 dp2 = nextPosition - startPos;
Vector3 dp1 = halfPosition - startPos;
Vector3 force = (dp2 - 2 * dp1) * 4 / (fdt * fdt);
Vector3 v0 = (dp2 / fdt) - (force * fdt / 2f);
Vector3 deltaPosPredicted = PhysicsTools.GetMovement(v0, force, fdt);
if (Vector3.Distance(deltaPosPredicted, dp2) > 0.001f)
Debug.Log("position prediction error: " + Vector3.Distance(deltaPosPredicted, dp2));
predictedNextFramePos = deltaPosPredicted + startPos;
Vector3 deltaHPosPredicted = PhysicsTools.GetMovement(v0, force, fdt / 2f);
if (Vector3.Distance(deltaHPosPredicted, dp1) > 0.001f)
Debug.Log("position prediction error: " + Vector3.Distance(deltaHPosPredicted, dp1));
//drawing the startpos, midpos, endpos triangle
Debug.DrawLine(startPos, startPos + dp2, Color.magenta, Time.fixedDeltaTime * 2);
Debug.DrawLine(startPos, startPos + dp1, Color.red, Time.fixedDeltaTime * 2);
Debug.DrawLine(startPos + dp2, startPos + dp1, Color.red, Time.fixedDeltaTime * 2);
//drawing v0 and force
Debug.DrawLine(startPos, startPos + force, Color.gray, Time.fixedDeltaTime * 2);
Debug.DrawLine(startPos, startPos + v0, Color.blue, Time.fixedDeltaTime * 2);
//drawing the parabola arc
{
Vector3 pos = startPos;
Vector3 vel = v0;
for (int i = 0; i < 10; i++)
{
Vector3 offset = PhysicsTools.GetMovementUpdateVelocity(ref vel, force, Time.fixedDeltaTime / 10f);
Debug.DrawLine(pos, pos + offset, Color.green, Time.fixedDeltaTime * 2);
pos += offset;
}
}
// Old version
// Vector3 deltaPosition = nextPosition - _rigidbody.position;
// Vector3 velocity = deltaPosition / t;
// SetPhysicsState(_rigidbody.position, velocity, time);
//Applying results
SetPhysicsState(startPos, v0, time);
_rigidbody.AddForce(force / 2f, ForceMode.Acceleration);
I am using my physics helper class
public static class PhysicsTools
{
public static Vector3 GetMovement(Vector3 velocity, Vector3 force, float time)
{
return (velocity * time) + 0.5f * force * (time * time);
}
public static Vector3 GetMovementUpdateVelocity(ref Vector3 velocity, Vector3 force, float time)
{
Vector3 ret = (velocity * time) + 0.5f * force * (time * time);
velocity += force * time;
return ret;
}
}
Everything works fine, but if, and only if, I divide the force by two when applying it. My own simulation using PhysicsTools does not require such tampering.
Here's a picture of one of my tests, with the force factor applied to both the physics engine and the PhysicsTools simulation. You can see that the simulated lines go off into the distance, but not the actual projectile, which stays in its weird pentagram, as it should.
Here we can see it working as intended (still with the applied force reduced)
My question, why would I need to divide that damned force by two?
Well here folks is what happen when you make assumptions.
I assumed that ForceMode.Continuous meant that the force would be applied continuously through the frame. That is not the case.
The unity physics engine is incapable of any kind of continuous acceleration or parabola casting. Any object moves in straight lines, and AddForce simply modifies the velocity right then and there.
It turns out that simply dividing the force by two was enough to reset the starting velocity to my previous linear solution to the problem, and that the only reason that objects seemed to react outside of the polygon was that my bullet collider was much wider than I thought it was.
Please read this post for more information: https://answers.unity.com/questions/696068/difference-between-forcemodeforceaccelerationimpul.html
The only solution to the problem is to increase the physics framerate, or to use your own raycasting solution, which comes with a slew of other problems.
This camera script is intended to rotate and look at the player while it is moving and snapping to the player slowly while it isn't. (Player passes over a Vector3 beforeMoving before it is starting movement). My issue is I want to "feed" the deltaPosition slowly so it isn't a sudden snap but rather a slow and smooth transition, also stop adding if arrived.
private void LateUpdate()
{
if (player.isMoving)
{
desiredPosition = player.beforeMoving + offset;
}
else
{
Vector3 deltaPosition = player.transform.position - player.beforeMoving;
desiredPosition += deltaPosition * Time.deltaTime;
}
Quaternion camTurnAngle =
Quaternion.AngleAxis(input * rotationSpeed, Vector3.up);
desiredPosition = camTurnAngle * desiredPosition;
transform.position = Vector3.Slerp(transform.position, desiredPosition, smoothFactor);
transform.LookAt(player.transform);
}
Edit: I thought I would share the final code.
private void LateUpdate()
{
Quaternion rotation = Quaternion.Euler(GetDegree(), input.x * rotationSpeed, 0f);
if (player.isMoving)
{
desiredPosition = player.beforeMoving + offset;
CalculatePanTime();
}
else if (!player.isMoving)
{
desiredPosition = player.transform.position + offset;
}
transform.position = Vector3.Slerp(transform.position, rotation * desiredPosition, GetSpeed());
transform.LookAt(player.transform);
}
private void CalculatePanTime()
{
stoppedTime = Time.time;
playerDelta = Vector3.Distance(player.transform.position, player.beforeMoving);
timeToPan = (playerDelta / snappingSpeed) * Time.deltaTime;
}
private float GetSpeed()
{
if (Time.time < stoppedTime + timeToPan)
{
controlsDisabled = true; return snappingSpeed;
}
else
{
controlsDisabled = false; return smoothSpeed;
}
}
You are telling us what you want the code to do, that is good. You are also posting the code you implemented to achieve your goal, that is also good. Can you also tell us what is not working as you want it to work as a result of that code?
From what I understand it is the "Vector3 deltaPosition = player.transform.position - player.beforeMoving;
desiredPosition += deltaPosition * Time.deltaTime; " that is not behaving as you expect it to
maybe try something like this:
private void LateUpdate()
{
// snap the rotation center slowly to the player's position if not moving, or player's position before he started moving
desiredPosition = Vector3.Lerp(desiredPosition, player.beforeMoving, 0.1f);
// rotate around rotation center
Quaternion camTurnAngle = Quaternion.AngleAxis(rotationSpeed * Time.time, Vector3.up);
desiredPosition += camTurnAngle * offset;
// set the position to rotate around rotation center, and look towards player
transform.position = Vector3.Lerp(transform.position, desiredPosition, smoothFactor);
transform.LookAt(player.transform);
}
the problem with your code is that you are overshooting. If you want to implement something that decides how fast the camera snaps, or how much time it should take to snap, try introducing a float player.timeStopMoving = Time.time that you can use to correctly compute the position correction while he is not moving.
if(player.isMoving)
{
desiredPosition = player.beforeMoving;
}
else
{
const float timeNeededToSnap = 2f;
// or float timeNeededToSnap = (player.transform.position - player.beforeMoving).magnitude; // (which you could compute only once in player script, when he stops moving, and then reuse the value instead of performing a ".magnitude" every frame)
if(Time.time < player.timeStopMoving + timeNeededToSnap)
{
desiredPosition = Vector3.Lerp(desiredPosition, player.transform.position, (Time.time - player.timeStopMoving) / timeNeededToSnap);
}
else
{
// an other problem here is: if the player starts moving AGAIN before your desiredPosition got the value, do you want desired position to glich to the new player.beforeMoving?...
desiredPosition = player.transform.position;
}
}
EDIT:
To make he lerp less linear, you can use:
if(Time.time < player.timeStopMoving + timeNeededToSnap)
{
var t = Mathf.Cos(Maths.Pi * 0.5f * (Time.time - player.timeStopMoving) / timeNeededToSnap); // as timeDelta/totalTime goes from 0->1, multiply it by Pi/2 and the Cos will also go from 0->1 but with a smoothing speed
desiredPosition = Vector3.Lerp(desiredPosition, player.transform.position, t);
}
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.