@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.