// Undo objects to previous state
    private void Undo()
    {
        if (undoCount <= 0)
        {
            return;
        }

        undoCount--;
        stepIndex--;
        if (stepIndex < 0)
        {
            stepIndex = STORAGE_SIZE - 1;
        }

        for (int i = 0; i < rigidBodies.Length; i++)
        {
            NewRigidBody rigidBody = rigidBodies [i];

            RigidbodyState state = storage [stepIndex].states [i];
            rigidBody.acceleration    = state.acceleration;
            rigidBody.velocity        = state.velocity;
            rigidBody.position        = state.position;
            rigidBody.angularVelocity = state.angularVelocity;
            rigidBody.orientation     = state.orientation;

            rigidBody.transform.rotation = Quaternion.Euler(state.orientation);
            rigidBody.transform.position = rigidBody.position;
        }
    }
    // Store current state of objects
    private void StoreState()
    {
        RigidbodyState[] objectStates = new RigidbodyState[rigidBodies.Length];
        for (int i = 0; i < rigidBodies.Length; i++)
        {
            NewRigidBody rigidBody = rigidBodies[i];

            RigidbodyState state = new RigidbodyState();
            state.acceleration    = rigidBody.acceleration;
            state.velocity        = rigidBody.velocity;
            state.position        = rigidBody.position;
            state.angularVelocity = rigidBody.angularVelocity;
            state.orientation     = rigidBody.transform.rotation.eulerAngles;

            objectStates[i] = state;
        }
        storage[stepIndex].states = objectStates;

        undoCount++;
        if (undoCount >= STORAGE_SIZE)
        {
            undoCount = STORAGE_SIZE - 1;
        }

        stepIndex++;
        if (stepIndex >= STORAGE_SIZE)
        {
            stepIndex = 0;
        }
    }
    void PositionCorrection(NewRigidBody a, NewRigidBody b, float penetrationDepth, Vector3 collisionNormal)
    {
        // reference on how to correct position once an impulse is applied
        // https://gamedevelopment.tutsplus.com/tutorials/how-to-create-a-custom-2d-physics-engine-the-basics-and-impulse-resolution--gamedev-6331

        const float percent = 0.8f;
        const float thresholdToPreventJitter = 0.01f;
        Vector3     correction = Mathf.Max(penetrationDepth - thresholdToPreventJitter, 0.0f) / (a._inverseMass + b._inverseMass) * percent * collisionNormal;

        // Apply
        if (a._isKinematic == false)
        {
            a.position -= a._inverseMass * correction;
        }

        if (b._isKinematic == false)
        {
            b.position += b._inverseMass * correction;
        }
    }
    // Reset objects to the initial state
    public void Reset()
    {
        for (int i = 0; i < rigidBodies.Length; i++)
        {
            NewRigidBody rigidBody = rigidBodies [i];

            RigidbodyState state = _initialStates[i];
            rigidBody.acceleration    = state.acceleration;
            rigidBody.velocity        = state.velocity;
            rigidBody.position        = state.position;
            rigidBody.angularVelocity = state.angularVelocity;
            rigidBody.orientation     = state.orientation;

            rigidBody.transform.rotation = Quaternion.Euler(state.orientation);
            rigidBody.transform.position = rigidBody.position;

            rigidBody.transform.GetComponent <MeshRenderer> ().material = rigidBody._originalMaterial;
        }

        firstPlay = false;
        _initialConditions.SetActive(true);
        pause = true;
    }
    void Update()
    {
        // Initial refresh
        if (_start == false)
        {
            _massInput.text = _selectedObject.mass.ToString();

            _positionX.text = _selectedObject.position.x.ToString();
            _positionY.text = _selectedObject.position.y.ToString();
            _positionZ.text = _selectedObject.position.z.ToString();

            _orienX.text = _selectedObject.orientation.x.ToString();
            _orienY.text = _selectedObject.orientation.y.ToString();
            _orienZ.text = _selectedObject.orientation.z.ToString();

            _isKinematic.isOn = _selectedObject._isKinematic;
            _start            = true;
        }

        // Prevent ray from going through UI
        if (EventSystem.current.IsPointerOverGameObject())
        {
            return;
        }

        // Select object
        if (Input.GetKeyDown(KeyCode.Mouse0))
        {
            RaycastHit hit;
            Ray        ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            if (Physics.Raycast(ray, out hit, 100.0f))
            {
                _selectedObject = hit.transform.gameObject.GetComponent <NewRigidBody> ();

                _massInput.text = _selectedObject.mass.ToString();

                _positionX.text = _selectedObject.position.x.ToString();
                _positionY.text = _selectedObject.position.y.ToString();
                _positionZ.text = _selectedObject.position.z.ToString();

                _orienX.text = _selectedObject.orientation.x.ToString();
                _orienY.text = _selectedObject.orientation.y.ToString();
                _orienZ.text = _selectedObject.orientation.z.ToString();

                _isKinematic.isOn = _selectedObject._isKinematic;

                RefreshUI();
            }
        }

        // Apply torque
        if (Input.GetKey(KeyCode.Mouse1))
        {
            RaycastHit hit;
            Ray        ray = Camera.main.ScreenPointToRay(Input.mousePosition);
            if (Physics.Raycast(ray, out hit, 100.0f))
            {
                hit.transform.gameObject.GetComponent <NewRigidBody> ().ApplyTorqueForce(hit.point, hit.normal);
            }
        }


        if (_selectedObject == null)
        {
            return;
        }

        // Refresh the selected object's UI
        RefreshUI();
    }