From looking at the BoidSystem demo, I thought I finally had an idea of how the IJobNativeMultiHashMapMergedSharedKeyIndices works, so I tried it out. And it turns out, I have no idea what it does…
Why is there an ExecuteFirst(int index) and an ExecuteNext(int cellIndex, int index)?
Can anyone give a simple example that demonstrates how it works? I have not been able to find anything at all, except the BoidSystem example.
But I still can’t find a way to solve my problem. What I’m trying to do is, in simple pseudocode:
foreach entity in allEntities
{
entity.nodeInOctree = CalculateNode(entity)
}
foreach entity in allEntities
{
foreach otherEntity in entitiesInSameNode //I can't find a way to do this iteration
{
//check for collisions
}
}
I can get a hash of the entity position, like in the BoidSystem and add each entity’s index to the HashMap with that as key.
But I can’t find a way to iterate over the entities that share the same node/cell, without having some kind of dynamically constructed list.
If only I could do something like:
foreach entity in allEntities
{
HashMapValues entitiesInCell = _myHashMap[entity.nodeInOctree ]
foreach otherEntity in entitiesInCell
{
//check for collisions
}
}
The trick was to have the NativeMultiHashMap<int, int> as [ReadOnly], so it’s readable from all threads. Then iterate over all entities and and look up all other entities in the same cell and do collision checks on them.
This is a reduced version of my code and there are even a lot of things I would like to do, but have not figured out how to do properly yet.
[BurstCompile]
struct CellIndexingJob : IJobParallelFor
{
[ReadOnly] public ComponentDataArray<Position> positions;
[ReadOnly] public NativeArray<EntityCollider> readColliders;
[ReadOnly] public float cellSize;
public NativeMultiHashMap<int, int>.Concurrent hashMap;
public void Execute(int index)
{
EntityCollider collider = readColliders[index];
float3 position = positions[index].Value;
int finalNodeIndex = GridHash.Hash(math.round(position / cellSize), cellSize);
hashMap.Add(finalNodeIndex, index);
}
}
[BurstCompile]
struct CollissionCheckJob : IJobParallelFor
{
[ReadOnly] public ComponentDataArray<Position> positions;
[ReadOnly] public NativeArray<EntityCollider> readColliders; //Extra array of EntityColliders only for lookup, as the other is only for writing.
[ReadOnly] public NativeMultiHashMap<int, int> collisionCellMap;
public ComponentDataArray<EntityCollider> colliders;
[ReadOnly] public float cellSize;
public void Execute(int index)
{
EntityCollider collider = colliders[index];
float3 myPosition = positions[index].Value;
float size = collider.size;
//GridHash is found in the ECS samples
int cellIndex = GridHash.Hash(myPosition, cellSize);
int otherItem;
var iterator = new NativeMultiHashMapIterator<int>();
int collisionCount = 0;
if (collisionCellMap.TryGetFirstValue(cellIndex, out otherItem, out iterator))
{
//The TryGetFirstValue gives us the iterator AND the first item, so we have to make the check here too.
collisionCount += CalculateCollision(index, otherItem, myPosition, size);
while (collisionCellMap.TryGetNextValue(out otherItem, ref iterator))
{
//Now calculate collision between self (index) and otherItem
collisionCount += CalculateCollision(index, otherItem, myPosition, size);
}
}
//And set the result. Here we just track how many collisions there are, but we will later calculate the new velocity after all the contacts.
colliders[index] = new EntityCollider
{
Collides = collisionCount,
size = colliders[index].size
};
}
private int CalculateCollision(int index, int item, float3 position, float size)
{
//Don't compare with self.
if (index == item)
return 0;
//Just a distance check. More elaborate collision calculations can be implemented later.
float distance = math.lengthSquared(position - positions[item].Value);
bool hit = math.lessThan(distance, (size + readColliders[item].size) * 0.5f);
return math.select(0, 1, hit);
}
}
Aaaand I realize that it does not work that well. The way it works right now, collisions between cells is not implemented. (In my own code, I check for all cells that an Entity touches, but that only detects the collision for that object, not the one it touches)
I have tried several ways to implement something better, but I always run into a wall where I need to add something to a container concurrently from multiple threads. The NativeQueue supports this, but it has to be dequeued on the main thread, creating a bottleneck.