Search Unity

Official 2D Physics in Unity 2022.1

Discussion in '2D' started by rustum, Aug 26, 2022.

  1. rustum

    rustum

    Unity Technologies

    Joined:
    Feb 14, 2015
    Posts:
    190
    2D Physics in Unity 2022.1
    Delaunay Tessellation.png

    Hi 2D folks! We have new updates for 2D Physics in Unity 2022.1!

    What’s new?
    • Delaunay tessellation
      • The PolygonCollider2D, CompositeCollider2D, and TilemapCollider2D components produce multiple polygon physics shapes to fill the area of the provided path outlines. Those polygon physics shapes can become too numerous, too thin or too small, especially on curved outlines. In some cases, some of these are filtered out by the physics engine itself as it cannot use them.
      • We’re introducing Delaunay tessellation when producing these polygon physics shapes, which produces far superior results in these cases; it not only reduces the quantity of polygon physics shapes that are too thin or small but also generally produces fewer polygons to cover the same area.
    Samples
    Check out the 2D Physics Samples github repo. We've added 2 samples to the colliders folder to show the Delaunay tessellation. These are named:
    • PolygonCollider2D_Delaunay
    • CompositeCollider2D_Delaunay
    About 2D Physics
    Read more about the current 2D Physics features here.

    What can you do?
    Try it out and let us know what you think of the additions and improvements. We want to know what works as expected, what doesn’t and what is missing. We’d love to see how you use them as well, so please show off all the cool things that you make with them!
     
    Xrayez, EvOne, msfredb7 and 2 others like this.
  2. Ruskul

    Ruskul

    Joined:
    Aug 7, 2014
    Posts:
    72
    Do you know- In the past, there seemed to be plans for presolve callbacks. Have those been implemented anywhere and I just missed them? At some point they seem to have fallen off the road map, but I didn't hear why.
     
    NotaNaN likes this.
  3. AnaWilliam850

    AnaWilliam850

    Joined:
    Dec 23, 2022
    Posts:
    36
    thanks for the update!
     
  4. DenDunno

    DenDunno

    Joined:
    Mar 18, 2020
    Posts:
    5
    Not much benefit in using CustomCollider2D with NativeArray and Job system
    The following code leads to an error: "ArgumentException: An invalid PhysicsShape at index 1 was encountered. The CustomCollider2D was not updated."

    Code (CSharp):
    1. NativeArray<PhysicsShape2D> shapes = new NativeArray<PhysicsShape2D>(2, Allocator.Temp, NativeArrayOptions.UninitializedMemory);
    2. NativeArray<Vector2> vertices = new NativeArray<Vector2>(8, Allocator.Temp);
    3.  
    4. vertices[0] = new Vector2(1, 1);
    5. vertices[1] = new Vector2(1, 2);
    6. vertices[2] = new Vector2(2, 1);
    7. vertices[3] = new Vector2(2, 2);
    8. vertices[4] = new Vector2(1, 1) * 2;
    9. vertices[5] = new Vector2(1, 2) * 2;
    10. vertices[6] = new Vector2(2, 1) * 2;
    11. vertices[7] = new Vector2(2, 2) * 2;
    12.  
    13. shapes[0] = new PhysicsShape2D()
    14. {
    15.     shapeType = PhysicsShapeType2D.Polygon,
    16.     vertexStartIndex = 0,
    17.     vertexCount = 3,
    18. };
    19. shapes[1] = new PhysicsShape2D()
    20. {
    21.     shapeType = PhysicsShapeType2D.Polygon,
    22.     vertexStartIndex = 4,
    23.     vertexCount = 3,
    24. };
    25.  
    26. _customCollider.SetCustomShapes(shape2D, vertices);
    27.  
    28. shape2D.Dispose();
    29. vertices.Dispose();

    As I understood, the problem is that there is a gap (vertices[3]) between shape[0] and shapes[1]. If shapes[1] vertexStartIndex is 3, it works fine (without an error).
    And ... this constraint really fades away the great benefit of using CustomCollider2D with Job system.

    In my project, I want to generate Edge and Polygons using PhysicsShape2D in Job system. The key point is that - I do know the up size of each shape (6).
    So, I allocate shapes and vertices (with size shapeCount * maxVertexCount)

    Code (CSharp):
    1. _shapes = new NativeArray<PhysicsShape2D>(Data.ChunkSize.x * Data.ChunkSize.y, Allocator.Persistent);
    2. _colliderVertices = new NativeArray<Vector2>(Data.ChunkSize.x * Data.ChunkSize.y * MaxVertexCount, Allocator.Persistent);

    and populate it in Job system

    Code (CSharp):
    1. for (int i = 0; i < calculator.Count; ++i)
    2. {
    3.     ColliderVertices[index * Data.PhysicsShapeOffset + i] = calculator.Output[i];
    4. }
    5.  
    6. if (calculator.Count >= 2)
    7. {
    8.     PhysicsShapeType2D type = calculator.Count == 2 ? PhysicsShapeType2D.Edges : PhysicsShapeType2D.Polygon;
    9.  
    10.     PhysicsShapes.Add(new PhysicsShape2D()
    11.     {
    12.         shapeType = type,
    13.         vertexCount = calculator.Count,
    14.         vertexStartIndex = index * Data.PhysicsShapeOffset
    15.     });
    16. }  
    And what was my surprise when I figured out that error that data should be without gaps...
    Sure, I can adjust shapes/vertices arrays outside the Job so that they won't contain gaps. It works, but it costs a great amount of performance.

    P.S Please check out the Screenshot below for a better understanding of the algorithm and problem
     

    Attached Files:

    Last edited: Oct 16, 2023
  5. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    11,459
    If you have an issue then please create your own thread. Please don't hijack existing threads.

    Nothing here imposes "gaps" so I'm not following where that's coming from. Or are you saying that shapes at arbitray start index are not working?

    When it parses the shapes, it grabs the start-index and the vertex-count and passes that to the internal shape (fixture) creation. A polygon has to be 3 to 8 vertices.

    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".
     
  6. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    11,459
    Right so re-reading your post again, yes, it's validating that the shapes are all valid (to stop crashes) and one of the checks are that the shape vertex are contiguous although technically they don't need to be I guess; as long as they are not outside the vertex array so that specific check could be removed. I'd need to careful check for any reliance upon that now but in theory, with a "bug" report it could be removed and backported.

    It's essentially this code here (the next to final line):
    Code (CSharp):
    1.     // Do some common validation.
    2.     if (physicsShape.m_Radius >= 0.0f &&
    3.         IsFinite(physicsShape.m_Radius) &&
    4.         physicsShape.m_VertexStartIndex >= 0 &&
    5.         physicsShape.m_VertexStartIndex < maxVertexCount &&
    6.         physicsShape.m_VertexStartIndex == expectedVertexStartIndex &&
    7.         physicsShape.GetNextVertexIndex() <= maxVertexCount)

    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? Could you not use a NativeList with the TempJob allocator and just add your shapes in your job? You can then use NativeList<T>.AsArray when passing to the custom collider.

    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.
     
    Sahilraje likes this.
  7. DenDunno

    DenDunno

    Joined:
    Mar 18, 2020
    Posts:
    5
    1) "Or are you saying that shapes at arbitray start index are not working?"
    Exactly

    2) "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.

    3) "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:
    Code (CSharp):
    1. [BurstCompile]
    2. public struct MarchingSquareColliderGenerationJob : IJobParallelFor
    3. {
    4.     [NativeDisableParallelForRestriction] [ReadOnly] public NativeArray<MarchingSquare> Grid;
    5.     [ReadOnly] public NativeArray<float> ScalarField;
    6.     [ReadOnly] public MeshGenerationJobData Data;
    7.  
    8.     [WriteOnly] public NativeCounter.Concurrent PhysicsShapeCounter;
    9.     [WriteOnly] public NativeStream.Writer PhysicsPoints;
    10.  
    11.     [BurstCompile]
    12.     public void Execute(int i)
    13.     {
    14.         SquareIndex index = new SquareIndex(i, Data.Bounds.Start, Data.Bounds.Size);
    15.         MarchingSquareInputStruct input = GetInput(index.GlobalIndex.x, index.GlobalIndex.y);
    16.         if (input.IsFullQuad == false)
    17.         {
    18.             AddMarchingSquarePart(in input, in index);
    19.         }
    20.     }
    21.  
    22.     private MarchingSquareInputStruct GetInput(int i, int j)
    23.     {
    24.         return new MarchingSquareInputStruct(Data.IsoValue,
    25.             ScalarField[(j + 1) * Data.Density.x + i + 1],
    26.             ScalarField[j * Data.Density.x + i + 1],
    27.             ScalarField[j * Data.Density.x + i],
    28.             ScalarField[(j + 1) * Data.Density.x + i]);
    29.     }
    30.  
    31.     private void AddMarchingSquarePart(in MarchingSquareInputStruct input, in SquareIndex squareIndex)
    32.     {
    33.         MarchingSquare square = Grid[squareIndex.ChunkY * Data.Bounds.Size.x + squareIndex.ChunkX];
    34.         int configurationIndex = input.GetConfiguration();
    35.         square.Interpolate(input);
    36.         NativeMarchingSquareMeshConfiguration configuration = Data.Configurations[configurationIndex];
    37.         Span<Vector2> vertices = stackalloc Vector2[configuration.VerticesCount];
    38.         Span<Vector2> uv = stackalloc Vector2[configuration.VerticesCount];
    39.         Data.SquareFunctionsList.UpdateVertices(configurationIndex, ref vertices, ref uv, in square, squareIndex.GlobalIndex, Data.Density - Vector2Int.one);
    40.  
    41.         AddCollider(in vertices, squareIndex.OneDimensionalIndex);
    42.     }
    43.  
    44.     private void AddCollider(in Span<Vector2> vertices, int index)
    45.     {
    46.         PhysicsPointsCalculator calculator = new PhysicsPointsCalculator(stackalloc Vector2[vertices.Length]);
    47.         calculator.Evaluate(in vertices);
    48.  
    49.         if (calculator.Count >= 2)
    50.         {
    51.             PhysicsShapeCounter.Increment();
    52.             PhysicsPoints.BeginForEachIndex(index);
    53.  
    54.             for (int i = 0; i < calculator.Count; ++i)
    55.             {
    56.                 PhysicsPoints.Write(calculator.Output[i]);
    57.             }
    58.  
    59.             PhysicsPoints.EndForEachIndex();
    60.         }
    61.     }
    62. }

    Updating collider outside the job:

    Code (CSharp):
    1. private void UpdateCollider(int physicsShapesCount, ref NativeStream stream)
    2. {
    3.     NativeStream.Reader reader = stream.AsReader();
    4.     NativeArray<PhysicsShape2D> shapes = new NativeArray<PhysicsShape2D>(physicsShapesCount, Allocator.Temp);
    5.  
    6.     int vertexStartIndex = 0;
    7.  
    8.     for (int i = 0, shapeIndex = 0; i < reader.ForEachCount; ++i)
    9.     {
    10.         int count = reader.BeginForEachIndex(i);
    11.  
    12.         if (count >= 2)
    13.         {
    14.             shapes[shapeIndex++] = new PhysicsShape2D()
    15.             {
    16.                 shapeType = count == 2 ? PhysicsShapeType2D.Edges : PhysicsShapeType2D.Polygon,
    17.                 vertexCount = count,
    18.                 vertexStartIndex = vertexStartIndex
    19.             };
    20.  
    21.             vertexStartIndex += count;
    22.         }
    23.     }
    24.  
    25.     NativeArray<Vector2> vertices = stream.ToNativeArray<Vector2>(Allocator.Temp);
    26.  
    27.     Data.Collider.SetShapes(shapes, vertices);
    28.  
    29.     vertices.Dispose();
    30.     shapes.Dispose();
    31. }
    From this point, I have only 2 options (Of course, to my knowledge):
    1. 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

    2. 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:

    Code (CSharp):
    1. for (int i = 0; i < marchingSquareVertexCount; ++i)
    2. {
    3.     vertices[index * 6 + i] = marchingSquareVertices[i];
    4. }
    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 :)

    4) "Could you not use a NativeList with the TempJob allocator and just add your shapes in your job? You can then use NativeList<T>.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)

    5) "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,
    Code (CSharp):
    1. physicsShape.m_VertexStartIndex == expectedVertexStartIndex
    seems to be quite costly for me, hehe.
     

    Attached Files:

    Last edited: Oct 16, 2023
    Sahilraje likes this.
  8. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    11,459
    So I just took a look at this and there's no reason why it has to be consecutive vertices, I guess I was just over-eager to validate the case when a "raw" native-array of verts/shapes comes in; last thing I needed with such low-level is a hard crash. So I can see how that could be a pain if you wanted your own vertex stride in there.

    So it could easily be changed however to fix and backport this to LTS I'd need a bug report. If you could do that, referring to me in the case then it can be fast-tracked. If you could provide me the case number you get back then I can chase it coming from our QA team.

    How fast can you create a bug report? Essentially just copy/paste the post above would be more than enough and refer to this thread.
     
    Sahilraje likes this.
  9. DenDunno

    DenDunno

    Joined:
    Mar 18, 2020
    Posts:
    5
    Done
     
    Sahilraje likes this.
  10. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    11,459
    As soon as you get a case number, please post it to me here or DM.

    I'll get it changed ASAP for you.

    Thanks.
     
    Sahilraje likes this.
  11. DenDunno

    DenDunno

    Joined:
    Mar 18, 2020
    Posts:
    5
    CASE IN-57664
     
    Sahilraje and MelvMay like this.
  12. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    11,459
    Just an update: I'm waiting on our customer QA to progress the case but in the meantime I started looking at changing this. I did discover why I originally made that "contiguous vertex" constraint. It was because when adding/removing/changing shapes, manipulating the vertex data becomes much trickier when there's redundant (unaccounted for) data between the shapes.

    So I said I'd change it and I will do that but part of me is wishing I hadn't said that but a promise is a promise! :D

    I'll let you know here when it's done.
     
    funkyCoty, DragonCoder and DenDunno like this.
  13. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    11,459
  14. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    11,459
    The fix has now landed and will be in:
    • 2022.3.13f1
    • 2023.1.20f1
    • 2023.2.0b17
    • 2023.3.0a13
     
    Xrayez likes this.