Hey all, I’m having a bunch of problems trying to get a mesh collider to match a wave function.
As a background, I’m working on a simulated ocean / buoyancy, half as an experiment and half to make a quick game out of it. I came across a problem where the wave function I’m using (Gerstner waves) are mathematically impossible to calculate the exact Y coordinate at a given x/z location. My solution to this was to generate a custom mesh directly underneath any object that floats (for now, only a rectangle “boat”), which the buoyancy nodes raycast downwards to in order to determine if they’re above or below it, and hence, above or below the water.
In theory this works, great, and I’ve managed to get significant performance boosts by calculating it once every few physics updates, as well as parallelizing the calculation (just a Parallel.For, nothing fancy). The problem is that the mesh collider itself seems to have this default position that it always returns to, on random frames. No matter the size of the mesh, or what settings the collision mesh has applied, it always happens. In one test, I tried deleting the gameobject and recreating it from scratch every frame, and it still didn’t work. I’ll attach my code for the mesh, the wave controller, and the collider update, but I’m losing my mind trying to fix this.
TL;DR - Watch the video and please tell me what’s happening.
The video is me going frame-by-frame in the unity editor, in order to get a better picture of what’s going on. If I play the game at full speed, it looks like the mesh is OK with a few flickers, but when done frame by frame you can see the mesh “jump” to a previously stored position.
I did some testing, and found more odd behavior that might point to something. My mesh is randomly swapping to positions in between FixedUpdate calls. I noticed this because my code wasn’t reporting a new value (I’m doing all my calculations in FixedUpdate) but the mesh was absolutely not in the same place that it was.
I’m just casting the rays down, and if they hit the collider I say that the “floater” is above water, and if they don’t hit the collider, I say they’re below it. This may run into problems if my boat goes to a Y value equivalent to the length of my raycast, but I doubt I’ll hit that any time soon. I can even approximate the depth of the floater under the water by grabbing the average Y position of the whole mesh and comparing it to my current Y position.
I definitely was incorrect about the FixedUpdate() / Update() thing, but it’s still continuing to happen regardless of what solutions I try.
This is my current code for updating the meshCollider and meshFilter, run every fixedUpdate() for the boat to float on.
private void Awake()
{
meshFilter = GetComponent<MeshFilter>();
meshCollider = GetComponent<MeshCollider>();
meshCollider.cookingOptions = MeshColliderCookingOptions.CookForFasterSimulation;
}
void Start()
{
originalVertices = (Vector3[]) meshFilter.mesh.vertices.Clone();
meshCollider.sharedMesh = meshFilter.sharedMesh;
Physics.IgnoreLayerCollision(0, 8); // 8 is the layer defined for the meshes under "floater" objects.
}
void UpdateCollider(Mesh mesh)
{
//meshCollider.sharedMesh = null;
//DestroyImmediate(meshCollider);
//gameObject.AddComponent<MeshCollider>();
//meshCollider = GetComponent<MeshCollider>();
meshCollider.sharedMesh = meshFilter.sharedMesh;
Physics.IgnoreLayerCollision(0, 8);
}
void FixedUpdate()
{
Debug.Log("Old: " + meshFilter.mesh.vertices[0]);
frameCount += 1;
if (frameSkip != 0 && frameCount % frameSkip != 0) return;
var vertices = meshFilter.mesh.vertices;
var newY = currentPlane.transform.position.y;
//Debug.Log(vertices[0]);
Vector3 currentPosition = transform.position;
Parallel.For(0, vertices.Length, i =>
{
originalVertices[i].y = newY;
vertices[i] = waveController.GetWavePositionAt(originalVertices[i] + currentPosition).position - currentPosition;
// If you don't subtract - transform.position, you end up with double the distance each frame.
});
meshFilter.mesh.vertices = vertices;
meshFilter.mesh.RecalculateNormals();
meshFilter.sharedMesh = meshFilter.mesh;
Debug.Log("New:" + meshFilter.mesh.vertices[0]);
UpdateCollider(meshFilter.mesh);
if (meshFilter.mesh.vertices[0] != meshCollider.sharedMesh.vertices[0])
Debug.LogError("Noticed a discrepancy in the mesh!!");
}
I tried to illustrate the problem more clearly by disabling the wave shader. I’m starting to suspect it’s a problem with how I’m handling the vertices, but that makes no sense to me, as I’m certain my function returns the correct value at a given sample position.
fixedveneratedlarva
My only lead is that the mesh is returning to the original vertices set after the first calculation is run. Whatever the function calculates as the first correct position, each point snaps back to that “original” position at seemingly random.
What’s even more bizarre is that this only happens when the mesh is selected in the project menu. When run frame by frame in any other circumstance, this issue is invisible (but very real, based on how the boat interacts with the mesh).
I’m losing all interest in game dev trying to fix this bug, it’s been over a week now and I still cannot figure this out.
Okay, let’s lose the threads first. 100% of Unity stuff has to be on the main thread.
You’re firing off this async parallel mumbo jumbo then instantly telling the mesh about the verts (which are being changed in the other thread), and then recalculating normals on it.
At a bare minimum you would need to wait for the verts to be ready before shoveling them onto Unity by assigning them to the MeshFilter.
I’m definitely sure the async method is working. I went to some lengths to get it functional and I’m very familiar with how it works. The only effect it has on my code is boosting performance roughly 3x.
Just as proof, here’s a recording of the mesh with this code instead.
Vector3 currentPosition = transform.position;
for(int i = 0; i < vertices.Length; i++)
//Parallel.For(0, vertices.Length, i =>
{
originalVertices[i].y = newY;
vertices[i] = waveController.GetWavePositionAt(originalVertices[i] + currentPosition).position - currentPosition;
// If you don't subtract - transform.position, you end up with double the distance each frame.
}//);
It actually doesn’t return the correct value at the function. Absolutely didn’t expect this. This is just a quick update, I’m going to do some more experimentation in a second, but notice the discrepancy in the positions in this, its weird as hell.
Edit:
I just noticed that the input is actually 100% correct, and doesn’t change as I anticipated. It must be something to do with the function itself.
Alright, I figured it out, and let me just say it was obscure. I’ll explain it in detail in case someone else has this particular problem but I doubt they’ll find a solution for it here.
So, this line of code is an important part of simulating the waves, as it ties the waves’ movement to the current time in the game. Otherwise, they’d be static.
float f = wave.kValue * (Vector2.Dot(wave.direction, new Vector2(point.x, point.z)) - wave.cValue * curTime);
curTime is a variable I created that every update call, I set to the current time since level load (this enables me to make the calculations parallel). When I was writing this originally, though, I wanted to be absolutely certain that my time was equivalent to the one in the shader. After all, if it’s slightly off, the collision wouldn’t be right, so I did this.
curTime = Shader.GetGlobalVector("_Time").y;
This, however, was the mistake. As it turns out, for some reason that I’m still not at all sure of, this code occasionally just returns 0. What this does is set the wave’s position to its very first moment of instantiation, hence the really weird mesh behavior I was seeing.
You could use the same function that returns the vertice position and return the Y “height” of where the boat is.
No need for raycast, no need for collision meshes updating realtime, significant performance boost as your game scales.
You just convert your boat position or anchors(pivot) to the water’s local space and just compare the Y.
float boatVerticalH = boatLocal.y - seaLocal.y;
boatVerticalH’s absolute value is your distance to the water surface, it’s sign means if you’re above or bellow it.
You could do it for any pivot you might have on your boat.