0
votes

I am making a scientific visualization app of the Galaxy. Among other things, it displays where certain deep sky objects (stars, star clusters, nebulae, etc) are located in and around the Galaxy.

There are 6 or 7 classes of object types (stars, star clusters, nebulae, globular clusters, etc). Each object within a class looks the same (i.e. using the same image).

I've tried creating GameObjects for each deep sky object, but the system can get bogged down with many objects (~10,000). So instead I create a particle system for each class of deep sky object, setting the specific image to display for each class.

Each particle (i.e. deep sky object) is created at the appropriate location and then I do a SetParticles() to add them to that class's particle system. This works really well and I can have 100,000 objects (particles) with decent performance.

However, I need to allow the user to click/tap on an object to select it. I have not found any examples of how to do hit testing on individual particles in the particle system. Is this possible in Unity?

Thanks, Bill

2

2 Answers

0
votes

You'll have to do the raycasting yourself. Just implement a custom raycasting algorithm using a simple line rectangle intersection. Simply assume a small rectangle at each particle's position. Since you do not rely on Unity's built in methods you can do this async. For performance optimization you can also cluster the possible targets at simulation start, allowing the elimination of whole clusters when their bounding box is not hit by your ray.

Note: Imho you should choose a completely different approach for your data rendering. Take a look at unity's entity component system. This allows for large amounts of data, but comes with some disadvantages (e.g. when using Unity's physics engine) (which will not be of relevance for your case I suppose).

0
votes

I ended up rolling my own solution.

In Update(), upon detecting a click, I iterate through all the particles. For each particle, I calculate its size on the screen based on the particle's size and its distance from the camera.

Then I take the particle's position and translate that into screen coordinates. I use the screen size to generate a bounding rectangle and then test to see if the mouse point is inside it.

As I iterate through the particles I keep track of which is the closest hit. At the end, that is my answer.

    if (Input.GetMouseButtonDown(0))
    {
        Particle? closestHitParticle = null;
        var closestHitDist = float.MaxValue;
        
        foreach (var particle in gcParticles)
        {
            var pos = particle.position;
            var size = particle.GetCurrentSize(gcParticleSystem);
            var distance = Vector3.Distance(pos, camera.transform.position);
            var screenSize = Utility.angularSizeOnScreen(size, distance, camera);
            var screenPos = camera.WorldToScreenPoint(pos);
            var screenRect = new Rect(screenPos.x - screenSize / 2, screenPos.y - screenSize / 2, screenSize, screenSize);
            if (screenRect.Contains(Input.mousePosition) && distance < closestHitDist)
            {
                closestHitParticle = particle;
                closestHitDist = distance;
            }
        }

        if (closestHitDist < float.MaxValue)
        {
            Debug.Log($"Hit particle at {closestHitParticle?.position}");
           
        }

Here is the angularSizeOnScreen method:

public static float angularSizeOnScreen (float diam, float dist, Camera cam) 
{
    var aSize = (diam / dist) * Mathf.Rad2Deg;
    var pSize = ((aSize * Screen.height) / cam.fieldOfView);
    return pSize;
}