schteppe / cannon.js

A lightweight 3D physics engine written in JavaScript.
http://schteppe.github.com/cannon.js
MIT License
4.69k stars 712 forks source link

Spheres falling through edge of Heightfield #301

Open dcrosby42 opened 8 years ago

dcrosby42 commented 8 years ago

Doing some simple experiments rolling a ball around on a Heightfield, my sphere consistently falls through the surface as it approaches one particular edge of the hf shape. It seems like the sphere gets within roughly 1/2 the distance of one spacing unit then drops through. The other edges of the hf behave fine. (In my personal code the failing edge seems to be on the x+ side, but in my modified fork of demos/heightfield.html, it's the y+ edge, front-facing in the demo.)

Version of canon.js: master @ 569730f94a1d9da47967a24fad0323ef7d5b4119 (v0.6.2-66-gff9364b)

I vaguely suspect a bug in Heightfield pillar construction, but I haven't seen any glaring numerical errors in the pillar vertices, though I admit to only a loose understanding of shape collision internals. I worry more that I'm using bad parameters/sizes which trigger unexpected collision behavior.

I noticed this other heightfield issue #149, but I'm not seeing any JS console errors.

Anybody else encounter this (or know how to fix/avoid)?

dcrosby42 commented 8 years ago

(Possibly related)

Using a Heightfield shaped as a slightly convex mound, dropping Spheres of radius to 0.1... and noticed a decent number of the spheres dropping straight through the surface. I haven't pin-pointed the locations of the individual failures, but I wonder if they're dangerously close to the pillar vertices inside the hf Shape?

These spheres do not drop through if the Heightfield is perfectly flat, eg, height=1 for all values in matrix.

(testing on Chrome ~52 and Firefox ~48 on OS X)

that-ben commented 7 years ago

Been experiencing this issue for almost a year. To my understanding, this is a library bug and I never really found a proper workaround, except continuously ray tracing the object and forcing its Y position above the plane when it goes under the heightfield, which is obviously only a temporary quick fix until the library picks up the slack.

msadev commented 7 years ago

You should do this instead of raytracing.

getTerrainHeightAt = function(xPos, yPos, heightmap, tilesize){

    var x = Math.floor(xPos / tilesize),
    y = Math.floor(yPos / tilesize),

    xPlusOne = x + 1,
    yPlusOne = y + 1,

    triY0 = heightmap[x, y],
    triY1 = heightmap[xPlusOne, y],
    triY2 = heightmap[x, yPlusOne],
    triY3 = heightmap[xPlusOne, yPlusOne],

    height = 0.0,
    sqX = (xPos / tilesize) - x,
    sqY = (yPos / tilesize) - y;

    if ((sqX + sqY) < 1) {
        height = triY0;
        height += (triY1 - triY0) * sqX;
        height += (triY2 - triY0) * sqY;
    } else {
        height = triY3;
        height += (triY1 - triY3) * (1.0 - sqY);
        height += (triY2 - triY3) * (1.0 - sqX);
    }
    return height;
}

With something like if(body.position.z < height) body.position.z = height;

Credits go to this C version

that-ben commented 7 years ago

Looks legit, but do you have performance improvement stats against direct raytracing? I see the solution you posted also accounts for smoothing slopes which is good.

msadev commented 7 years ago

I use it to place a lot of objects on my heightfield too, and there's no comparison in terms of performance.

that-ben commented 7 years ago

No yes OK, I understand, but I meant what's the performance gain in regards to raytracing from camera to heightfield a couple times per second. I was not refering to generating a terrain and raytracing 10000 times per heightfield chunk; I wouldn't use raytracing to place vegetation on the terrain, since I generate it in the same loop as the heightfield vertices themselves and therefore, there is absolutely NO calculation required, obviously, as opposed to the camera's location, which is dynamic.

But either way, I guess you're right, even if it saves 0.001% it's still better than nothing as an improvement.

msadev commented 7 years ago

I'm sure it's way more than 0.001% because you dont need a built mesh to intersect with, and you dont need any matrix operations to do the calculation.

Unfortunatly, I don't have time to create a fiddle to compare right now... I'll try to create one during next days.

Of course, I still use raycasting for a lot of things like bullets or contextual actions.

there is absolutely NO calculation required

How do you get the exact height between 3 vertices ?

that-ben commented 7 years ago

In my app, the vegetation is always on a vertex, therefore, there's no average to calculate. By 0.001% (exageration of course) I was refering to the fps gain, not the actual gain per call, since we do not need to raytrace on every frame. I say raytracing maybe twice a second is enough in most typical cases, but it may vary obviously.

Also, the real question here is: if you can successfully reposition the camera when it falls through the floor by using a simple Math function, how come the physics engine fails at it? I don't understand that, which is also the OP question I think.

msadev commented 7 years ago

If

we do not need to raytrace on every frame

Id say the fps gain is about zero.

Also, the real question here is: if you can successfully reposition the camera when it falls through the floor by using a simple Math function, how come the physics engine fails at it? I don't understand that, which is also the OP question I think.

I guess the engine use a different approach to handle contacts, which is partially broken on this specific CANNON.Heightfield

that-ben commented 7 years ago

I've had more time last week to fiddle around with this problem and I have found a perfectly working solution to this problem a lot of people are talking about. The library does something weird when creating pillars and for some reason @schteppe might be able to explain, on the X+ edge of the heightfield, the very last column (so say in a 10x10 matrix, 10x0, 10x1, 10x2, etc...) will be missing. It only does that for the X+ edge. X-, Z-, Z+ edges all are perfectly aligned and all pillars are there.

The solution I found that works PERFECTLY is to STITCH the last X+ column with whatever heightfield you have immediately following... or if you only have 1 heightfield, then just extend all along the X+ axis with the same value a second time. So basically if you have a matrix of heightfields, then the X+ column of the left heightfield has to be the same values of the X- column of the right heightfield. This makes a seam and I know there's a little bit of "Z-fighting" but it does not seem to reduce performance for me and everything works perfectly. The camera never ever bounces or falls through the floor anymore. This was super annoying and it took a while to find out, because even the body2mesh() function would draw it as the physics body was supposed to be BUT IT'S REALLY NOT, so it's clear to me that there's a bug. Also if it was not a bug, it would do that on the other axis (Z+), which it does not.

If you take a look at the shape array property of the body using the browser's console, you'll discover that the array is 1 index short, so in my example, for a 10x10 heightfield, the array only has 0 to 8 indices, clearly showing it's missing the last column, therefore pillars are missing all along that axis, I guess.

I have made a visual graph to explain how to solve this issue. I hope it can help future people coming here for the same reason as everybody else who has players falling off the edges of heightfields for no apparent reason.

stitch