Rowie
Rowie

Reputation: 313

unity3d Calculate Accuracy of 2 colliders hitting

I'm using unity3d 2022.3.10f and I am trying to work out how accurate someone hits an object with the controller in a VR game I am doing for the oculus.

To simplify this so I can test repeatedly with various scenarios, I have a static object (sphere) that has a collider on it. I have a 2nd object (another sphere) that also has a collider on it.

They are directly inline with each other e.g. Sphere 1: is at 0,1,1 Sphere 2: is at 0,1,0

So they are 1 distance on the z axis.

I then impart a velocity to sphere 2 to send it exactly towards Sphere 1 (0,0,1). When the colliders trigger - I want a % rating of how accurate they collided - basically projecting the center of sphere 2 on its path to see how close it would have come to hitting the center of sphere 1.

In my above example I would expect a % of 100% as they were on exact collision course.

If I then change the velocity imparted on Sphere 2 to make it still collide but very slightly offline i.e. velocity of (0,0.04,1) - I would expect a lower % value e.g. 75% and so on to a % of near 0 if they just about scrape when then hit.

I have tried many different ways but unable to come up with anything.

Has anyone got code to do this they could share?

Thank you for your time. R

EDIT: The 2 sphere scenario is just for testing as a rig so I can accurately change slightly the angles/speed etc to see how the percentage hit calcs are working.

I do have a script attached to each hand controller that gives me the Average Velocity (int) and Average Direction (Vector3) for each controller all the time i.e. it is stored currently in a singleton value for each controller and updated per frame so I can easily access it.

So if this can be used to calculate the accuracy of the collider hit maybe?

Here is the Velocity and Direction script:

public class AverageDirectionCalculator : MonoBehaviour
{
    private Vector3[] recentPositions = new Vector3[10];
    private int currentIndex = 0;

    void Update()
    {
        // Store the current position in the array
        recentPositions[currentIndex] = transform.position;

        // Increment the index and wrap around if necessary
        currentIndex = (currentIndex + 1) % recentPositions.Length;
    }

    public Vector3 GetAverageDirection()
    {
        Vector3 averageDirection = Vector3.zero;

        // Calculate the average direction based on the stored positions
        for (int i = 0; i < recentPositions.Length - 1; i++)
        {
            averageDirection += (recentPositions[i + 1] - recentPositions[i]).normalized;
        }

        // Normalize the result to get the average direction
        averageDirection.Normalize();

        return averageDirection;
    }

    public int GetVelocity()
    {
        // Calculate the velocity based on the magnitude of the most recent position change
        int previousIndex = (currentIndex - 1 + recentPositions.Length) % recentPositions.Length;
        float velocity = (recentPositions[currentIndex] - recentPositions[previousIndex]).magnitude / Time.deltaTime;

        float maxPossibleVelocity = 100f;
        float scaledVelocity = (velocity / maxPossibleVelocity) * 100f;

        // Cast the scaled velocity to an integer
        int scaledVelocityInt = Mathf.RoundToInt(scaledVelocity);

        return scaledVelocityInt;
    }
}

EDIT: ANSWER

I think I have worked out a workable solution with the help of derHugo help and advice.

Step 1: I get the Vector3 of the controller from my AverageDirection script at the point of impact so I know the rough trajectory the controller was moving in on impact

Step 2: I work out a step movement - in my case 1/250'th of the Vector3 - this will give me a slice to move along the trajectory

Step 3: Loop - move along the trajectory path in increments of the step (1/250th) and calculate the distance from the new Vector3 to the target position

Step 4: Keep doing this until I find the smallest distance

This then gives me how close along that trajectory the controller would have hit the target.

Seems to work. Below is the process (in test form): Note: I add a sphere to each step just to visualise the trajectory was indeed on the correct path - just for testing.

 float _distanceToTarget = 0f;
 float _minDistanceFound = 1000f;
 Vector3 _stepAmount = Vector3.zero;
 Vector3 _refPoint = Vector3.zero;


private void OnTriggerEnter(Collider collider)
{    


 _distanceToTarget = Vector3.Distance(collider.transform.position, transform.position);
    _minDistanceFound = _distanceToTarget;
    _stepAmount = controllerDirection / StepDiv;
    _refPoint = transform.position;



var target = collider.transform.position;

while (_distanceToTarget <= _minDistanceFound)
{
    _refPoint = _refPoint + _stepAmount;
    _distanceToTarget = Vector3.Distance(target, _refPoint);
    if (_distanceToTarget < _minDistanceFound)
    {
        _minDistanceFound = _distanceToTarget;
    }

GameObject sphere = GameObject.CreatePrimitive(PrimitiveType.Sphere);
sphere.transform.position = _refPoint;
sphere.transform.localScale = new Vector3(0.06f, 0.06f, 0.06f);


}

}

There is bound to be a math way of doing this rather than small step slices moving forward, but I don't know how so this seems to workout.

Thank you hope this helps someone else if needed.

Upvotes: 1

Views: 67

Answers (1)

derHugo
derHugo

Reputation: 90813

You could probably use angles for that.

If your sphere is at a certain distance and has a certain radius and your projectile also has a projectileRadius you can basically calculate a range of angle around the direction

Shoot origin -> target center

within which you will just miss the target.

This you can basically do using a right scalene triangle math like e.g.

// distance between shoot origin and target center
// = adjacent of the triangle
var distance = Vector3.Distance(shootOrigin.position, target.position);
// maximum distance between projectile and target so you just miss it - just sum both radius
// = opposite of the triangle
var maxDistance = projectileRadius + targetRadius;
// maximum angle using arcsin
// = angle of the triangle
var maxAngle = Mathf.Asin(maxDistance / distance) * Mathf.Rad2Deg;

So you can simply check the angle between

// the actual angle between the two directions when hitting the target
var hitAngle = Vector3.Angle(target.position - shootOrigin.position, projectile.position - shootOrigin.position);

And now all that's left is basically mapping this angle onto the percentage using the before calculated maxAngle

// clamp the derail to maximum 1 so accuracy will be directly inverted 
// 0 miss, 1 full hit
var accuracy = 1f - Mathf.Min(hitAngle / maxAngle, 1f);

Upvotes: 1

Related Questions