How to instantiate and delete spheres based on incomming coordinates stream

Hello everyone, Iam new to Unity scripting and c# and would like to do the following:

I constantly get x y coordinates which represent laser scan data. I would like to draw spheres on each of the x y points.

Basically the laserscan data consist of up to 100 points which will repeat themselves if the laserscanner is not moved. So i will constantly get a x y data stream (if laser is not movied, then always the same points). If I move the scanner, I will get new points and some old points will not be sent because they are not visible for the laser scanner anymore.

Now for each x y point i would like to draw a sphere and at the same time, if the laser scanner moves and gives new points i want to display the new points and delete old ones which are not visible.

For now I have implemented that for every xy point to draw the spheres but struggled with the deletion algorithm. So now it draws spheres at new points but also keep the old ones on the scene I want to delete the ones which coordinates are not being sent anymore.

my script is as following:

 public GameObject point;
     public Vector3 spawnValues;
     public float spawnWait;
     public float spawnMostWait;
     public float spawnLeastWait;
     public int startWait;
     public bool stop;
     public GameObject sensorf;
     public SensorReceive sensorscript;
     public GeometryPoint pt
     private void Start()
     {
         // get x and y from sensorReceive script
         sensorf = GameObject.Find("SensorReceiver");
         sensorscript = sensorf.GetComponent<SensorReceive>();
         StartCoroutine(waitSpawner());
     }
     private void Update()
     {
         spawnWait = 0.00f;
     }

 IEnumerator waitSpawner()    
     {

         while (!stop)
         { 
             // spawn spheres at x and y position from sensorscript
             Vector3 spawnPosition = new Vector3(sensorscript.x, 1, sensorscript.y);

             Instantiate(point, spawnPosition + transform.TransformPoint(0, 0, 0), gameObject.transform.rotation);

             yield return new WaitForSeconds(spawnWait);
         }
     }        

Please help me because I struggled for 2 days and also searched for examples. Big thanks in advance! Best regards

@neymar4138, A review of waitSpawner suggests that whether you see them or not, new spheres are constantly being created in the same place if the data streams as you describe.

Since it seems obvious from your description that the software can’t sense when the scanner is moved, it must sense, instead, when data points already exist (so it doesn’t create duplicates as it appears to be now), and can age the data so as to remove items no longer coming in from the data.

You’ll need a container that can find points by x,y location (so as to recognize duplicate incoming data). When a duplicate data point is recognized, there should be a time stamp it can update, “refreshing” that point of data to indicate it is still coming in.

Next, there must be an occasional sweep of this container to identify old data. If the time stamp isn’t being refreshed, then the scanner is no longer reading those points in - those are the candidates for removal.

The classic container of choice here is the balanced height binary tree. This container is indicated because it performs well for frequent insertion and removal, provides lookup by key (the x,y pair) and dynamically expands to whatever volume is required. Unfortunately that container isn’t in the C# library, so you’d have to build one yourself to use it.

C# provides the Dictionary, the Hashtable and sorted arrays. These will work because your volume is low. Insertion and deletion performance are these containers weak points, but they are much faster for sweeping through the entire container than binary trees, and are a little more efficient on memory consumption. If the volume were in the several thousands, the deletion/insertion performance would suffer sufficiently that constructing a binary tree might be a reasonable choice (and that’s even more important as the volume increases), but for 100 or so there won’t be a problem.

I’ll choose Dictionary for an example.

Please note, this is proposed example code, untested and not checked. You’ll need to interpret, perhaps research a bit on the specifics.

There’s a potential catch. Vectors (2 and 3) use floats for location. When identifying exact matches, floats have a tendency to truncate (some say round, but it doesn’t do 4/5 rounding, it truncates). If that isn’t a problem, you may ignore this point. However, if you do want to control the precision of the incoming data, you’ll need to create an IEqualityComparer to control how keys are compared. This should suffice:

class DataPointComp : IEqualityComparer<Vector3> 
{
  private static int rounding = 4;

  // this rounds the points for comparison, accepts points as matching
  // when they are within 'rounding' decimal points of accuracy

  public bool Equals( Vector3 d1, Vector3 d2 )
   {
     if ( Mathf.Round( d1.x, rounding ) != Mathf.Round( d2.x, rounding ) ) return false;
     if ( Mathf.Round( d1.y, rounding ) != Mathf.Round( d2.y, rounding ) ) return false;
     if ( Mathf.Round( d1.z, rounding ) != Mathf.Round( d2.z, rounding ) ) return false;

     return true;;
   }
     
 // this creates hashcodes based on rounded values, so close 
 // points (within 'rounding' of precision) have the same hash code

 public int GetHashCode( DataPoint d ) 
   {
    return Math.Round( d.x, rounding ).GetHashCode() 
         ^ Math.Round( d.y, rounding ).GetHashCode() << 2 
         ^ Math.Round( d.z, rounding ).GetHashCode() >> 2;
   }

}

You’ll need a few members in your class. A container, for example (using the rounding comparer)

var dataPoints = new Dictionary< Vector3, DataPoint >( new DataPointComp() );

You’ll need a DataPoint class. This stores the time stamp, a copy of the Transform of the sphere you create for the data point (so it knows what to delete later), and the data point. Something like:

class DataPoint
{
 Vector3 position;
 Transform trans;
 float timeStamp;

 DataPoint( x, y, Transform t )
   {
    position = new Vector3( x, 1, y );
    trans = t; 
    timeStamp = Time.time;
   }
}

You need an “executive” level function to run this. I’ll assume your waitSpawner, but I can’t tell the correct choice. Something like:

IEnumerator waitSpawner()
{
 while( !stop )
   {
    Vector3 dataPoint = GetDataPoint();

    UpdateDataPoints( dataPoint );

    if ( Time.time > nextCullTime )
      {
       PruneData();
      }

   }

}

The idea here is to use a few functions to represent important steps.

This assumes a float, nextCullTime, is set to the value Time.time will reach when the next cull should run. This will sweep through the data to find old points and remove them. When that executes, calculate and set the nextCullTime for scheduling removal. You’ll choose a time that represents how often, and therefore how soon, old data should be removed.

This is an example “prune” function that does something like that:

void PruneData()
{
 // set ageCutoff as member of the class, this is how old data
 // can be before it should be pruned

 float cutoffAge = Time.time - ageCutoff; 

 List<Vector3> toBeRemoved = new List<Vector3>();


 foreach( KeyValuePair< Vector3, float > d in dataPoints )
   {
    if ( d.Value.time < cutoffAge )
      {
       DestroyDataPoint( d.Value ); // provides the transform 

       toBeRemoved.Add( d.Key );
      }
   }

 foreach( Vector3 d in toBeRemoved )
   {
    dataPoints.Remove( d );
   }

}

You’ll note that I create a list collecting what is to be removed, and remove them from the dictionary after the sweep completes. This is necessary in some containers because when something is removed while a loop is running through its contents, the loop can be confused by stuff disappearing (the current pointer can be lost). I don’t know C# Dictionary’s behavior in this regard, so I’m playing it safe by moving the removal to a second step.

This uses yet another member you’ll create and tune to your liking, the ageCutoff. This is time in seconds (a float, so it can be short, like .1 seconds) declaring what is considered old data. It can be used to schedule the nextCullTime.

Also note this calls “DestroyDataPoint”, a function I’ve not exampled. This is what removes the sphere from your gameObjects. How you do that is up to you (you’re creating them), but the d.Value passed is a DataPoint object, which contains a transform of the sphere, from which you can get gameObject and do whatever works.

I’d suggest you consider not destroying the game objects, but disabling them from rendering, hold them in a pool of unused gameObjects, and reuse them by moving them to new positions and turning them back on when SpawnNewData is called (another proposed function).

Maybe my use of GetDataPoint as a function seems overkill since it’s just one little thing, but here’s an example:

Vector3 GetDataPoint()
{
 return new Vector3( sensorscript.x, 1, sensorscript.y );
}

Here’s the meat of it all, though.

bool UpdateDataPoints( Vector3 d )
{
 DataPoint p;

 if ( dataPoints.TryGetValue( d, out p ) == true )
      {
       p.timeStamp = Time.time; // updates the time for this key

       // inform caller data isn't new (but has been refreshed by time)
       return false;
      }

 // the key doesn't exist, so create it, and inform the caller
 // the key is new data

 Transform t = SpawnNewData( d ); // should return the transform of the new sphere

 dataPoints[ d ] = new DataPoint( d.x, d.y, t ); // stamps time in the constructor

 return true;
}

The objective here is to query the dictionary dataPoints to see if a key of d is already there. If it is, get the dataPoint (in p) and update the timeStamp (so it stays fresh and prune won’t destroy it).

That would return false to indicate to the executive that this dataPoint already existed. I don’t use the return in my example waitSpawner, so this is optional.

If the data point was not found, it’s new. This calls SpawnNewData, which takes the Vector3 indicating where it should spawn. How you spawn is up to you, but you may consider a pool of reusable objects. Whatever the case, return the Transform representing the object so that can be stored in the DataPoint of the dictionary (so it can tell you the Transform and therefore the GameObject that is to be deleted later).

So, there’s the plan.

Details are still up to you.

@Jvene Many Thanks for your help Sir !! Iam very glad that you found time to think about my problem.

Your idea is very good and I nearly understand all the steps that you proposed.

Still I dont know how to work with transforms to instantiate and destroy objects. I looked at the definition of Transform but there are so many variables to set (one is the position). What do you mean with provide the transform with the function SpawnNewData(d) and DestroyDataPoint(d) and how should I do that? Because I only have the position Information. I tried to write the Functions as below but its not working yet:

public bool UpdateDataPoints(Vector3 d)
    {
        
        DataPoint p;

        if (dataPoints.TryGetValue(d, out p) == true)
        {
            p.timeStamp = Time.time; // updates the time for this key

            // inform caller data isn't new (but has been refreshed by time)
            return false;
        }

        // the key doesn't exist, so create it, and inform the caller
        // the key is new data

        Transform t = SpawnNewData(d);  // should return the transform of the new sphere

        dataPoints[d] = new DataPoint(d.x, d.y, t); // stamps time in the constructor

        //now get the position from transform and instantiate the object 'point' 
        //would it be easier to instantly get the position from d instead of going through the transform ? 
        Instantiate(point, t.position + transform.TransformPoint(0, 0, 0), gameObject.transform.rotation);

        return true;
    }

    Transform SpawnNewData(Vector3 position)
    {
       
        transform.position = position;
        return transform;
    }

For the destruction of old points I have this :

void PruneData()
        {
            // set ageCutoff as member of the class, this is how old data
            // can be before it should be pruned
            
            float cutoffAge = Time.time - ageCutoff;

            List<Vector3> toBeRemoved = new List<Vector3>();


            foreach (KeyValuePair<Vector3, DataPoint> d in dataPoints)
            {
                if (d.Value.timeStamp < cutoffAge)
                {
                    DestroyDataPoint(d.Value); // provides the transform 

                    toBeRemoved.Add(d.Key);
                }
            }

            foreach (Vector3 d in toBeRemoved)
            {
                dataPoints.Remove(d);
            }

        }


void DestroyDataPoint(DataPoint objectInformation)
        {
            Destroy(objectInformation.gameObject);
        }

Is this the right way to work with transforms to instantiate and destroy objects? Because before that I only did instantiation with simple

Instantiate(point, spawnPosition , gameObject.transform.rotation);

but I think with transform its the better way because you can somehow work with the gameObject.
If I start unity then nothing happens. So no spheres are spawned.

Another question is where I should put the dictionary within my code ?

var dataPoints = new Dictionary< Vector3, DataPoint >( new DataPointComp() );

Because I have to make this global so that the functions can access it, but var s cant be public. So I fist declare the Dictionary globally at the top of my class and within the start() function make it var (see below overall code at the beginning). But Iam not sure if this works. So our overall code looks like this: The main class ‘SpawnPoints’ which I will attach to unity (see picture)

using System;
using UnityEngine;
using System.Collections;
using System.Threading;
using System.Linq;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using System.IO;

namespace RosSharp.RosBridgeClient
{

    public class SpawnPoints : MonoBehaviour
    {


        public GameObject point;
        public Vector3 spawnValues;
        public float spawnWait;
        public float spawnMostWait;
        public float spawnLeastWait;
        public int startWait;
        public bool stop;
        public GameObject sensorf;
        public SensorReceive sensorscript;
        public double x, y, z;


        public float ageCutoff = 4;
        public float nextCullTime = 2;
        

        //Position Arrays 
        public Vector3[] spawnPositions;
        public Vector3[] sPos;
        public Vector3[] newPositions;

        public Dictionary<Vector3, DataPoint> dataPoints;
        public GeometryPoint pt;
        



        private void Start()
        {



            sensorf = GameObject.Find("SensorReceiver");
            sensorscript = sensorf.GetComponent<SensorReceive>();

            //right to put it here?
            var dataPoints = new Dictionary<Vector3, DataPoint>();


            StartCoroutine(waitSpawner());

        }

        private void Update()
        {
            spawnWait = 0.0001f;


        }

        IEnumerator waitSpawner()
        {
            while (!stop)
            {

                Vector3 dataPoint = GetDataPoint();

                UpdateDataPoints(dataPoint);

                if (Time.time > nextCullTime)
                {
                    PruneData();
                }
                yield return new WaitForSeconds(spawnWait);
            }
        }


        Vector3 GetDataPoint()
        {
            return new Vector3(sensorscript.x, 1, sensorscript.y);
        }



        public bool UpdateDataPoints(Vector3 d)
        {
            
            DataPoint p;

            if (dataPoints.TryGetValue(d, out p) == true)
            {
                p.timeStamp = Time.time; // updates the time for this key

                // inform caller data isn't new (but has been refreshed by time)
                return false;
            }

            // the key doesn't exist, so create it, and inform the caller
            // the key is new data

            Transform t = SpawnNewData(d);  // should return the transform of the new sphere

            dataPoints[d] = new DataPoint(d.x, d.y, t); // stamps time in the constructor

            //now get the position from transform and instantiate the object 'point' 
            //would it be easier to instantly get the position from d instead of going through the transform ? 
            Instantiate(point, t.position + transform.TransformPoint(0, 0, 0), gameObject.transform.rotation);

            return true;
        }

        Transform SpawnNewData(Vector3 position)
        {
           
            transform.position = position;
            return transform;
        }

        void DestroyDataPoint(DataPoint objectInformation)
        {
            Destroy(objectInformation.gameObject);
        }


        void PruneData()
        {
            // set ageCutoff as member of the class, this is how old data
            // can be before it should be pruned
            
            float cutoffAge = Time.time - ageCutoff;

            List<Vector3> toBeRemoved = new List<Vector3>();


            foreach (KeyValuePair<Vector3, DataPoint> d in dataPoints)
            {
                if (d.Value.timeStamp < cutoffAge)
                {
                    DestroyDataPoint(d.Value); // provides the transform 

                    toBeRemoved.Add(d.Key);
                }
            }

            foreach (Vector3 d in toBeRemoved)
            {
                dataPoints.Remove(d);
            }

        }

        
    }
}

If I try to run my unity scene nothing is spawned cause there is an NullReferenceException: Object reference not set to an instance of an object at line 99 where UpdateDataPoints(vector3 d) is called.

Also i would like to ask if you could provide examples on how i should implement the algorithm where we dont destroy the spheres but asign them to some other places. That seems to be a very nice idea!
But Iam still to beginner to figure out how to achieve that.

I hope for your answer and thank you very much!

Best regards