'How to get Direction after cueBall collide with any ball in unity3d? Like in 8 ball pool

enter image description here

Hi! i am making something like 8 ball pool in 3d with unity 3d C#. A is que ball and I know Dir1. I want to calculate Dir2.I am using Raycast to i can get point of contact.



Solution 1:[1]

If you really want to go manual

Instead of a Raycast you would rather use a Physics.SphereCast (according to your balls shape) or alternatively you could also use Rigidbody.SweepTest in order to get hit information for if you object would move.

Both provide you with the information

  • whether something is hit or not (e.g. check via tag if the first thing hit is a wall or another ball)

  • both give you a RaycastHit which contains detailed information such as

    • point the exact contact point
    • distance how far the ball traveled before hitting something (e.g. for calculating the already applied damping/table friction)
    • normal of the surface you hit so you can calculate your two balls new direction from that
    • and of course most importantly which object was hit so you can start a new calculation for that ball as well

The rest is The Math and Physics of Billard or Physics of Billard etc and you will find that a complete answer is way to complex for this page ;)

There are way to many things to consider when calculating physics manually and first of all you will need to decide how realistic you want to go actually. There are spins, frictions, jumps and you know in the real world there doesn't exist any fully elastic collision at all ... So boy if you are going to pre-calculate all this by hand you can as well just write your own physics engine ;)


Use the existing physics without calculating yourself at all

Now as a complete alternative approach to all that, which doesn't require you to calculate anything at all:

You could simulate the whole physics!

You could

  • Store all balls current positions (in order to restore them later)
  • Use Physics.Simulate in a loop, as condition checking if any sphere has moved between the two calls -> If not (or after certain preview time) then break.
  • Every step track the positions of all balls
  • After breaking of the loop reset all positions and velocities

=> You already get all the tracked points for each balls LineRenderer ;)

You could even use the positions in order to move the balls yourself instead of use the physics again to show the actual movement ;)

For large systems this would of course cause immense lag (depends also on your target preview time range). But I'd say for only a limited amount of balls this should be fine.

Here a little code I cluched together in a few minutes (way not perfect of course)

// little helper component for storing some references and identify clearly as a ball 
// (rather then allowing to reference just any GameObject)
public class Ball : MonoBehaviour
{
    [SerializeField]
    private Rigidbody _rigidbody;
    public Rigidbody Rigidbody => _rigidbody;

    [SerializeField]
    private LineRenderer _line;
    public LineRenderer Line => _line;

    private void Awake()
    {
        if (!_rigidbody) _rigidbody = GetComponent<Rigidbody>();
        if (_line) _line = GetComponentInChildren<LineRenderer>(true);
    }
}

and

public class Example : MonoBehaviour
{
    // A simple data container for transform values 
    private class TransformData
    {
        public Vector3 Position;
        public Quaternion Rotation;

        private readonly Rigidbody _rigidbody;

        public TransformData(Rigidbody rigidbody)
        {
            _rigidbody = rigidbody;
            Update();
        }

        public void Update()
        {
            Position = _rigidbody.position;
            Rotation = _rigidbody.rotation;
        }
    }
    
    // all balls
    public Ball[] balls;

    // the white ball in particular 
    public Ball whiteBall;

    // direction to shoot in
    public Vector3 direction;
    // force to apply 
    public float force;

    // How far to predict into the future
    public float maxPreviewTime = 10;

    // shall this be done every frame (careful with performance!)
    public bool continousUpdates;

    // stores all initial transform values before starting the prediction in order to restore the state later
    private readonly Dictionary<Ball, TransformData> initialPositions = new Dictionary<Ball, TransformData>();
    // stores all the predicted positions in order - but for now without the information about the exact frame
    // for simplicity and performance we only store WHERE the balls move, not exactly WHEN (could change that though if you wish)
    private readonly Dictionary<Ball, List<Vector3>> simulatedPositions = new Dictionary<Ball, List<Vector3>>();

    private void Awake()
    {
        // Initialize empty data sets for the existing balls
        foreach (var ball in balls)
        {
            initialPositions.Add(ball, new TransformData(ball.Rigidbody));
            simulatedPositions.Add(ball, new List<Vector3>());
        }
    }

    private void Update()
    {
        // you could call this every frame e.g. in order to see prediction lines while the player changes force and direction
        // have performance in mind though!
        if (continousUpdates)
        {
            UpdateLines();
        }

        // for demo we shoot on space key
        if (Input.GetKeyDown(KeyCode.Space))
        {
            ShootWhiteBall();
        }
    }

    private void ShootWhiteBall()
    {
        whiteBall.Rigidbody.AddForce(direction.normalized * force, ForceMode.Impulse);
    }

    [ContextMenu("Update Preview Lines")]
    public void UpdateLines()
    {
        // update all balls initial transform values to the current ones
        foreach (var transformData in initialPositions.Values)
        {
            transformData.Update();
        }

        // clear all prediction values
        foreach (var list in this.simulatedPositions.Values)
        {
            list.Clear();
        }

        // disable autosimulation - the API is a bit unclear whether this is required when manually calling "Physics.Simulate"
        // so just to be sure
        Physics.autoSimulation = false;

        // we will track if any transform values changed during the prediction
        // if not we break out of the loop immediately to avoid unnecessary overhead
        var somethingChanged = true;
        // as second break condition we use time - we don't want o get stuck in prediction forever
        var simulatedTime = 0f;

        // Do the same thing as you would later for shooting the white ball
        // preferably even actually use the exact same method to avoid double maintenance 
        ShootWhiteBall();

        while (somethingChanged && simulatedTime < maxPreviewTime)
        {
            // Simulate a physics step   
            Physics.Simulate(Time.fixedDeltaTime);

            // always assume there was no change (-> would break out of prediction loop)
            somethingChanged = false;
            
            foreach (var kvp in simulatedPositions)
            {
                var ball = kvp.Key;
                var positions = kvp.Value;
                var currentPosition = ball.Rigidbody.position;

                // either this is the first frame or the current position is different from the previous one
                var hasChanged = positions.Count == 0 || currentPosition != positions[positions.Count - 1];

                if (hasChanged)
                {
                    positions.Add(currentPosition);
                }

                // it is enough for only one ball to be moving to keep running the prediction loop
                somethingChanged = somethingChanged || hasChanged;
            }

            // increase the counter by one physics step
            simulatedTime += Time.fixedDeltaTime;
        }

        // Reset all balls to the initial state
        foreach (var kvp in initialPositions)
        {
            kvp.Key.Rigidbody.velocity = Vector3.zero;
            kvp.Key.Rigidbody.angularVelocity = Vector3.zero;
            kvp.Key.Rigidbody.position = kvp.Value.Position;
            kvp.Key.Rigidbody.rotation = kvp.Value.Rotation;
        }

        // apply the line renderers
        foreach (var kvp in simulatedPositions)
        {
            var ball = kvp.Key;
            var positions = kvp.Value;

            ball.Line.positionCount = positions.Count;
            ball.Line.SetPositions(positions.ToArray());
        }

        // re-enable the physics
        Physics.autoSimulation = true;
    }
}

enter image description here

as you can see it is not exactly 100% accurate, tbh no sure why but it is probably something that can be tweaked out.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1