Physics.BakeMesh question

I submitted #1197040 awhile ago, but I have no idea how to track this as the link in the original email requires some kind of login and there’s no hint as to how to register or sign in or anything…

Edit: Ah, there was a second email much later: https://issuetracker.unity3d.com/issues/rebuilding-a-mesh-using-the-job-system-to-bake-the-collider-then-setting-the-mesh-results-in-a-double-collider-bake

Does it contain a fix?

I’m really sorry, but have to inform everybody on this thread that the issue has not yet been addressed, unfortunately. anthony

Thanks for the Update!

So what is the current Workaround?
I tried creating new meshes each time but i still wasn’t able to trigger the threaded meshbaking in a way that the mesh didn’t need also trigger baking in the main loop.

What is the time estimate for the fix? Proc gen stuff uses 2019.3 features heavily (like setting NativeCollections to Mesh), falling back to earlier versions is not an option. GC traps are everywhere.

It doesn’t work for me. I see a Mesh.Bake PhysX collisionData on the main thread anyway every time I instantiate a prefab containing meshcollider that references a previously baked mesh using Physics.Bake Mesh. Is there any way to determine was the bake successful or not? May be it is because I am using PhysicsScenes?

We get a lot of this type of errors (in the player 2019.3.0f6 Win64 Mono):

Couldn't create a Convex Mesh from source mesh "p_BigRock_01_b_N" within the maximum polygons limit (256). The partial hull will be used. Consider simplifying your mesh.

After a few (not always the same amount) of reloading the scene, we start get these:

[Physics.PhysX] Foundation: Invalid registration detected.
(Filename: C:\buildslave\physx\build\physx/source/foundation/src/PsFoundation.cpp Line: 201)

[Physics.PhysX] Foundation: Invalid deregistration detected.
(Filename: C:\buildslave\physx\build\physx/source/foundation/src/PsFoundation.cpp Line: 216)

And after that we get a crash on exit. Can these be related? (The errors and the crash)

I don’t think it is. TerrainCollider internally uses Heightfield, while MeshCollider uses either TriangleMesh or ConvexMesh (depending on convex setting). I’d prefer Unity to decouple TerrainCollider from TerrainData and expose methods for direct update of heightmap (which PhysX supports).

Hi,

Can you guys help trying out this custom build please? https://beta.unity3d.com/download/5ef4e2647b21/public_download.html

It’s based on some 20.1 alpha, and we can have this change in all Unity versions 19.3 to trunk provided that we see it’s improving on the issues discussed above.

The main changes are:

  • When the MeshCollider is activated (or .sharedMesh is set), we check for actual default flags present before making the decision to bake another physx mesh instance. Priorly, we compared to an int directly, and that apparently is not great as you can get input flags == -1 in some cases (Editor’s “Everything” sets that for instance). Didn’t work well with deprecated flags (=convex inflation) either.

  • Addressed a problem in Physics.BakeMesh() where it checked if a Mesh had already been baked and early out if yes. This broke the pipeline where you wanted to keep modifying the same mesh and bake it over and over again. New logics:

  • Physics.BakeMesh() will always bake a new instance of physx mesh and store it on the Mesh

  • If this Mesh has already been baked (Mesh holds a pointer to the binary blob for mem management purposes), decrease the ref count of this physx mesh

  • If ref count == 0, we know nothing is using it any more, and will release memory (happens off the main thread)

  • If ref count > 0, we know something is using this instance still, it will get released when the last MeshCollider using it is deactivated (or .sharedMesh is set, replacing it with another one, possibly baked with BakeMesh on another thread).

I expect that the intended workflow is:

  • Modify mesh
  • Bake it with Physics.BakeMesh() on any thread
  • Call MeshCollider.sharedMesh with the mesh on the main thread

Note that (3) is consistent with the classic pipeline of editing Mesh geometry and then calling MeshCollider.sharedMesh to have it applied. Also avoids destroying PhysX and Unity shapes on another thread.

Please do let me know what you think.

Thanks for all your help.

Anthony

Did not work for me. I wasn’t able to avoid rebaking on assignment in the main thread.
Do cooking options break it? Can we in any way verify if the bake was performed on the thread?
Anyway, we should wait for a second person to test this, my code got messy because i had only an hour to test this, so it might just be me.

EDIT: Was my fault, the custom build works!
https://forum.unity.com/threads/physics-bakemesh-question.741257/#post-5545537

cooking options break it, I believe intentionally

In 2019.3 it was designed to work with default cooking options only (and then it actually broke if you set “Everything” which is logical to be default, but it wasn’t because of an issue in comparison). If you specify non-default cooking options, the mesh will be baked for the second time.

This is what the Editor uses per defaul:
5542597--570355--upload_2020-3-2_20-22-11.png

I created my MeshColliderComponent via script without touching the default cooking options. And after that failed, i tried to set it manually to this state. Same behavior. Has anyone else tried so far?

I’ve just created this simple project to set common grounds what we’re speaking about.

There’s just one scene with a script that creates everything procedurally.

It adds renderer, filter, MeshColllider, spawn a single Mesh instance and will generate new vertices every Update(). It’s baked every Update(), but then used only in FixedUpdate() to demonstrate that there are no bakes happening inside FixedUpdate.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CreateTriangle : MonoBehaviour
{
    private Mesh mesh;
    private MeshCollider collider;
    private MeshFilter filter;
    private MeshRenderer renderer;

    public float elevation = 0;
    public float size = 1;
    public float speed = 0.2f;
    public float spawnDelay = 2f;
    public GameObject testProto;

    public IEnumerator Start()
    {
        mesh = new Mesh();
        collider = gameObject.AddComponent<MeshCollider>();
        filter = gameObject.AddComponent<MeshFilter>();
        renderer = gameObject.AddComponent<MeshRenderer>();
        filter.sharedMesh = mesh;

        while (true)
        {
            // this is to drop something from atop and make sure collision is working as the thing bounces off the triangle
            var g = GameObject.Instantiate(testProto, Vector3.up * (elevation + 10), Quaternion.identity);
            yield return new WaitForSeconds(spawnDelay);
        }
    }

    private void Generate()
    {
        mesh.vertices = new Vector3[] {
            new Vector3(0, elevation, size),
            new Vector3(size, elevation, 0),
            new Vector3(0, elevation, 0),
        };
        mesh.triangles = new int[] {0, 1, 2};
        mesh.RecalculateNormals();
    }

    public void Update()
    {
        elevation += Time.deltaTime * speed;
        Generate();
        Physics.BakeMesh(mesh.GetInstanceID(), false);
    }

    public void FixedUpdate()
    {
        // there will be no bake here because we baked in Update() before
        collider.sharedMesh = mesh;
    }
}

5544793–570697–try-bake-mesh.zip (29.7 KB)

Thank you for looking into this! I checked your script, it seems that Physics.Bake() will still only happen once for the lifetime of the mesh.
EDIT: Oops, I used the old Editor on my first tests, in 2020.1.0a24.3012 it works as expected! (in the current version 2019.3.03f it doesn’t)

Thank You! Can we expect this in the next stable release? I need to test this on an Android device :smile:

Script description

I adapted your script to include Threading (see below). Just paste it, needed objects are created procedurally. I also used a larger mesh so that the baking should take some milliseconds. So you can see in the console-messages if assignments cause baking:

Controls:
Press 1: Update the mesh and Bake it in Update, the assignment in FixedUpdate should not take long
Press 2: Update the mesh without baking it, the assignment in FixedUpdate should take long
Press 3: Update the mesh and bake it in a second thread, the assignment in the callback should not take long
Space: Drop a test collider

Here is the script:
Script

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

using System.Threading;
using BakeThreads;

public class CreateTriangle : MonoBehaviour
{
    private Mesh mesh;
    private MeshCollider collider;
    private MeshFilter filter;
    private MeshRenderer renderer;
    private GameObject    lagIndicator    = null;
    private    GameObject    dropCloneSource    = null;
    private    float        spawnDelay        = 2.8f;
    private    GameObject    curClone        = null;

    private BakeThreadHandle bakeHandle = null;

    //counter for better console messages
    public static int jobCounter;

    public void Start()//public IEnumerator Start()
    {
        //Target mesh
            mesh = new Mesh();
            collider = gameObject.AddComponent<MeshCollider>();
            filter = gameObject.AddComponent<MeshFilter>();
            renderer = gameObject.AddComponent<MeshRenderer>();
            filter.sharedMesh = mesh;
        //LagIndicator
            lagIndicator = GameObject.CreatePrimitive(PrimitiveType.Cube);
            lagIndicator.transform.position = new Vector3(-5F, 0F, 0F);

        //LagIndicator
            dropCloneSource = GameObject.CreatePrimitive(PrimitiveType.Cube);
            dropCloneSource.transform.position = new Vector3(-5F, 5F, 0F);
            dropCloneSource.AddComponent<Rigidbody>();
 
        //borrow material from primitve
            renderer.sharedMaterial = lagIndicator.GetComponent<MeshRenderer>().sharedMaterial;

        //init our bakehandle for threading (see bottom of this script)
            bakeHandle = new BakeThreadHandle( OnFinishedThreadedMeshBake );
    }
    private static void Generate( Mesh targetMesh, float timeMod )
    {
 
        //generate array (COULD BE THREADED)

            //https://www.patrykgalach.com/2019/07/29/procedural-terrain-pt1-plane/
            int width = 180;
            int depth = 180;
            float scale = 0.05F;
            // Defining vertices.
            Vector3[] vertices = new Vector3[(width + 1) * (depth + 1)];
            int i = 0;
            float y = Random.Range(-15F,15F);
            for (int d = 0; d <= depth; d++){
            for (int w = 0; w <= width; w++){
                vertices[i] = ( new Vector3(w, y, d) - new Vector3(width / 2f, 0, depth / 2f) )*scale;
                i++;
            }}
            // Defining triangles.
            int[] triangles = new int[width * depth * 2 * 3]; // 2 - polygon per quad, 3 - corners per polygon
            for (int d = 0; d < depth; d++){
            for (int w = 0; w < width; w++){
                // quad triangles index.
                int ti = (d * (width) + w) * 6; // 6 - polygons per quad * corners per polygon
                // First tringle
                triangles[ti] = (d * (width + 1)) + w;
                triangles[ti + 1] = ((d + 1) * (width + 1)) + w;
                triangles[ti + 2] = ((d + 1) * (width + 1)) + w + 1;
                // Second triangle
                triangles[ti + 3] = (d * (width + 1)) + w;
                triangles[ti + 4] = ((d + 1) * (width + 1)) + w + 1;
                triangles[ti + 5] = (d * (width + 1)) + w + 1;
            }}

        //assign to mesh (CANNOT BE THREADED)
            targetMesh.vertices = vertices;
            targetMesh.triangles = triangles;
            targetMesh.RecalculateNormals();
    }
    public void Update()
    {

        lagIndicator.transform.Rotate(0F,0F,60F*Time.unscaledDeltaTime);

        //BAKE in Update
        if(Input.GetKeyDown(KeyCode.Alpha1)){    jobCounter++;
            Debug.Log("═════Key 1═════════\nBAKING IN UPDATE, assignment should NOT take long [ job:"+jobCounter.ToString().PadLeft(4,'.')+" ]");

            Generate( mesh, Time.time );//Generating vertex arrays etc. could be threaded too, but assigning .vertices|.triangles etc. to mesh, cannot be threaded
 
            float start = Time.realtimeSinceStartup;
            BakeMeshCollider( mesh );
            print("[ job:"+jobCounter.ToString().PadLeft(4,'.')+" ] Meshcollider Baking in Main thread took:\n"+BakeThreadQueue.GetMS(Time.realtimeSinceStartup -start)+"" );

            assignmentPending = true;
        }

        //Don't Bake
        if(Input.GetKeyDown(KeyCode.Alpha2)){    jobCounter++;
            Debug.Log("═════Key 2═════════\nDON'T BAKE assignment should take long [ job:"+jobCounter.ToString().PadLeft(4,'.')+" ]");

            Generate( mesh, Time.time );//Generating vertex arrays etc. could be threaded too, but assigning .vertices|.triangles etc. to mesh, cannot be threaded
 
            assignmentPending = true;
        }

        //Bake in Thread
        if(Input.GetKeyDown(KeyCode.Alpha3)){    jobCounter++;

            Debug.Log("═════Key 3═════════\nBAKING IN THREAD, assignment should NOT take long [ job:"+jobCounter.ToString().PadLeft(4,'.')+" ]");
 
            if( bakeHandle.occupied ){
                print("One Thread is already active");//for simplicity, only  threadhandle allowed in this example
            }else{

                Generate( mesh, Time.time );//Generate could be threaded too

                int meshID = mesh.GetInstanceID();//caching because 'targetMesh.GetInstanceID()' cant be called in worker thread!

                //prepare & start thread (long version)
                //ThreadStart threadstart = delegate {  MeshUpdate_Threadable( meshID, bakeHandle ); };
                //Thread t = new Thread( threadstart );
                //t.Start();

                //prepare & start thread (one line version)
                new Thread( new ThreadStart( delegate { BakeMeshCollider_Threaded( meshID, bakeHandle );})).Start();
                bakeHandle.startTime = Time.realtimeSinceStartup;
                bakeHandle.occupied = true;
            }
        }

        //Collison Test Object
        if(Input.GetKeyDown(KeyCode.Space)){
            if(curClone!= null){ Destroy(curClone); }
            curClone = GameObject.Instantiate(dropCloneSource, Vector3.up *10, Quaternion.identity);
        }

        BakeThreadQueue.CheckFinishedThreads();
    }
    private bool assignmentPending = false;
    public void FixedUpdate()
    {
        if( assignmentPending ){

            float start = Time.realtimeSinceStartup;
            // there will be no bake here because we baked in Update() before
            collider.sharedMesh = mesh;
            assignmentPending = false;
            print("[ job:"+jobCounter.ToString().PadLeft(4,'.')+" ] Meshcollider assignment in FixedUpdate took:\n"+BakeThreadQueue.GetMS(Time.realtimeSinceStartup -start)+"" );
        }
    }
 
    //NO THREADING
    private static void BakeMeshCollider( Mesh targetMesh  )
    {
        Physics.BakeMesh( targetMesh.GetInstanceID(), false);
    }


    //THREADING
    private static void BakeMeshCollider_Threaded( int targetMeshInstanceID, BakeThreadHandle bakeHandle  )
    {
        Physics.BakeMesh( targetMeshInstanceID, false);
 
        //report that the bake has finished
        lock( BakeThreadQueue.bakeFinishedQueue ){    //threads need to lock things that may be accessed by multiple threads at once
            BakeThreadQueue.bakeFinishedQueue.Enqueue( bakeHandle );
        }
    }

    //used as callback on our handle
    private void OnFinishedThreadedMeshBake(){
        float start = Time.realtimeSinceStartup;
        collider.sharedMesh = mesh;
        print("[ job:"+jobCounter.ToString().PadLeft(4,'.')+" ] Meshcollider assignment after threaded baking took:\n"+BakeThreadQueue.GetMS(Time.realtimeSinceStartup -start)+"" );
    }
}





 
namespace BakeThreads{

    public class BakeThreadHandle
    {
        public    readonly System.Action    callback;
        public    bool    occupied;
        public    float    startTime;
        public BakeThreadHandle( System.Action callback){
            this.callback = callback;
        }
    }

    public static class BakeThreadQueue
    {
        public static Queue<BakeThreadHandle> bakeFinishedQueue = new Queue<BakeThreadHandle>();
        public static bool    allowMultipleDequeues = false;

        //check every Update if thread is ready
        public static void CheckFinishedThreads(){

            for(int i=0; i<bakeFinishedQueue.Count; i++){
 
                BakeThreadHandle handle    = bakeFinishedQueue.Dequeue();        //get finished task handle
                Debug.Log(  "[ job:"+CreateTriangle.jobCounter.ToString().PadLeft(4,'.')+" ] Finished baking in another Thread!" +
                            "BakeThread took about \n"+GetMS(Time.realtimeSinceStartup -handle.startTime)+" to complete" );    //measure time it took
 
                //do what must be done after baking has finsihed
                handle.callback();
                handle.occupied = false;
 
                //early exit if we only want one dequeue per frame, e.g. if the callback is a large non threadable function and performing multiple might cause lag
                if(!allowMultipleDequeues){ break; }
            }
        }

        public    static string GetMS( float seconds, string msDecimalPlaces = "F6" ){
            return ""+(1000F *seconds).ToString( msDecimalPlaces ) +"ms";
        }
    }
}

Thanks for your input Marrt. I’ll let it sit here for a few days just in case. Afterwards there’s going to be a PR to 20.2, 20.1 and 19.3. I’ll keep you posted here on the thread. Sorry it’s taken a while. Anthony.

@Peter77 – speaking of the profiler entries, there are two:

“Mesh.Bake PhysX CollisionData” – indicated when baking a non-scaled mesh
“Mesh.Bake Scaled Mesh PhysX CollisionData” – indicated when baking a scaled mesh

Clearly, it would be nice to have an API that tells you whether the mesh will be baked or not, but it’s not there at the moment.

Currently, we run baking when either one is true: cooking options are not default, scaling is non-uniform, shared mesh is dirty.

Hope that helps.

i tested on version 2020.1.0a25.3171 and Physics.Bake() is only working once, then Mesh.Bake PhysX moves to the mesh assignment.
when we can get the fix in a public version.
Constantly moving the heavy calculation out of the main thread would be very useful for me.

I could not edit my post…So.
Mesh.Clear() does Not work, only work around I found is to create a new Mesh every time.

Try the script in this post: https://discussions.unity.com/t/757439/35