-
“Or are you saying that shapes at arbitray start index are not working?”
Exactly
-
“I also don’t know what the “SetShapes” is, presumably that’s a typo and this isn’t real code and is referring to “SetCustomShapes”.”
You’re right, misspelled. I’ve corrected an OP.
-
"You’ve not shown your job so in the absense of that, the question for me becomes, why add a gap in your vertex data i.e. why have a fixed size? "
Currently, I’m developing the marching squares algorithm in Unity. Due to the algorithm, each square is up to 6 vertices. In the job, I generate polygon vertices for PhysicsShape2D. Basically, it is a bit similar to mesh generation.
Job:
[BurstCompile]
public struct MarchingSquareColliderGenerationJob : IJobParallelFor
{
[NativeDisableParallelForRestriction] [ReadOnly] public NativeArray<MarchingSquare> Grid;
[ReadOnly] public NativeArray<float> ScalarField;
[ReadOnly] public MeshGenerationJobData Data;
[WriteOnly] public NativeCounter.Concurrent PhysicsShapeCounter;
[WriteOnly] public NativeStream.Writer PhysicsPoints;
[BurstCompile]
public void Execute(int i)
{
SquareIndex index = new SquareIndex(i, Data.Bounds.Start, Data.Bounds.Size);
MarchingSquareInputStruct input = GetInput(index.GlobalIndex.x, index.GlobalIndex.y);
if (input.IsFullQuad == false)
{
AddMarchingSquarePart(in input, in index);
}
}
private MarchingSquareInputStruct GetInput(int i, int j)
{
return new MarchingSquareInputStruct(Data.IsoValue,
ScalarField[(j + 1) * Data.Density.x + i + 1],
ScalarField[j * Data.Density.x + i + 1],
ScalarField[j * Data.Density.x + i],
ScalarField[(j + 1) * Data.Density.x + i]);
}
private void AddMarchingSquarePart(in MarchingSquareInputStruct input, in SquareIndex squareIndex)
{
MarchingSquare square = Grid[squareIndex.ChunkY * Data.Bounds.Size.x + squareIndex.ChunkX];
int configurationIndex = input.GetConfiguration();
square.Interpolate(input);
NativeMarchingSquareMeshConfiguration configuration = Data.Configurations[configurationIndex];
Span<Vector2> vertices = stackalloc Vector2[configuration.VerticesCount];
Span<Vector2> uv = stackalloc Vector2[configuration.VerticesCount];
Data.SquareFunctionsList.UpdateVertices(configurationIndex, ref vertices, ref uv, in square, squareIndex.GlobalIndex, Data.Density - Vector2Int.one);
AddCollider(in vertices, squareIndex.OneDimensionalIndex);
}
private void AddCollider(in Span<Vector2> vertices, int index)
{
PhysicsPointsCalculator calculator = new PhysicsPointsCalculator(stackalloc Vector2[vertices.Length]);
calculator.Evaluate(in vertices);
if (calculator.Count >= 2)
{
PhysicsShapeCounter.Increment();
PhysicsPoints.BeginForEachIndex(index);
for (int i = 0; i < calculator.Count; ++i)
{
PhysicsPoints.Write(calculator.Output[i]);
}
PhysicsPoints.EndForEachIndex();
}
}
}
Updating collider outside the job:
private void UpdateCollider(int physicsShapesCount, ref NativeStream stream)
{
NativeStream.Reader reader = stream.AsReader();
NativeArray<PhysicsShape2D> shapes = new NativeArray<PhysicsShape2D>(physicsShapesCount, Allocator.Temp);
int vertexStartIndex = 0;
for (int i = 0, shapeIndex = 0; i < reader.ForEachCount; ++i)
{
int count = reader.BeginForEachIndex(i);
if (count >= 2)
{
shapes[shapeIndex++] = new PhysicsShape2D()
{
shapeType = count == 2 ? PhysicsShapeType2D.Edges : PhysicsShapeType2D.Polygon,
vertexCount = count,
vertexStartIndex = vertexStartIndex
};
vertexStartIndex += count;
}
}
NativeArray<Vector2> vertices = stream.ToNativeArray<Vector2>(Allocator.Temp);
Data.Collider.SetShapes(shapes, vertices);
vertices.Dispose();
shapes.Dispose();
}
From this point, I have only 2 options (Of course, to my knowledge):
-
To use a NativeStream and MarchingSquare index as .BeginForEachIndex(index). (as I did in the example above. Lines 50-58)
Write vertices to stream and convert to NativeArray outside Job. It works perfectly
-
Or we use the feature that each marching square is up to 6 vertices. Preallocate an Allocator.Persistent array with size MarchingSquareCount * 6 and write to it this way:
for (int i = 0; i < marchingSquareVertexCount; ++i)
{
vertices[index * 6 + i] = marchingSquareVertices[i];
}
In this case, though, I’d have “gaps” in vertex data, but there is no memory-bound for me. Apparently, I cannot run performance tests between NativeStream and NativeArray usage in this case, but at least there wouldn’t be an overhead of converting stream to array. So, I’m pretty sure the second option is faster. How much - don’t know
-
“Could you not use a NativeList with the TempJob allocator and just add your shapes in your job? You can then use NativeList.AsArray when passing to the custom collider.”
To my knowledge - I can’t. In my case it’s crucial to know vertexStartIndex (= List count). But when the Job system is used, a race condition arises (Please check out the attached Screenshot)
-
“Note that there’s been plenty of projects doing this on the job system so your statement that “Not much benefit in using CustomCollider2D with NativeArray and Job system” seems a rather inappropriate.”
I didn’t mean to be rude. Sorry. I’ve been waiting for this feature for quite some time, and I must say, it’s truly impressive work
However, the line you provided, physicsShape.m_VertexStartIndex == expectedVertexStartIndex
seems to be quite costly for me, hehe.