Could use some guidance on gravity implementation edge cases

Hi All,

I’m have most of my gravity code in place for my “game”, (not using a physics engine) - but I’m hitting a snag when trying to efficiently implement fall-detection.

You’d think “just add negative Y velocity!” but if I just apply negative Y velocity to every moveWithCollisions, the player mesh can barely climb a slope, so Y velocity has to be 0 unless I detect some action that means I need to add negative Y.

For example, when I press to jump, I apply positive Y velocity sending the mesh upward and set a “falling” flag to TRUE which hits subsequent special handling to take place in the main render function where I apply negative Y velocity, compounding, until I detect the player is on the ground (currently via ray-picking - I highly dislike this approach).

The performance of ray-picking for obtaining elevation is not the main problem though - the main problem here is when I simply walk off a cliff, there is no hint that I need to start applying negative Y velocity (ie. enter fall-handling state).

Where am I mislead?

Thanks

1 Like

Hi JD. This has been an issue many times before, and there’s many posts both here and on the old forum… on this topic. Here’s one from the new forum.

First, a little history. The BJS built-in collisions system was originally designed for “first person shooter” games… using the FreeCamera. The FreeCamera has .applyGravity and ._needMoveForGravity = true; (start falling on scene READY)… and it has .ellipsoid and .ellipsoidOffset, and it’s “scrub-off angle” (ramp-climbing/wall-scrub-off-angle) is affected by user-settable Engine.CollisionsEpsilon.

MoveWithCollisions was a later add-on feature… attempting to deal with collisions that AREN’T a freeCamera colliding with an obstacle. (ie. mesh-to-mesh instead of cam-to-mesh). SO, for now, I will assume that your player is NOT a FreeCamera. Let’s pretend it’s a box. :slight_smile:

When using moveWithCollisions, setting .checkCollisions = true on grounds, heightMaps, or nearly ANYTHING that can collide with BOTTOM of box.ellipsoid… is probably a bad idea. (possible continuous collisions from “scrubbing against ground” as sideways/lateral moveWithCollisions (MWC) happen - especially problematic for non-level terrains.)

Instead, you would use the “air hockey puck” -approach… where you try to keep boxPlayer above the ground and above jumped-up-onto mesh… a tiny amount. (the air-cushion). But this is a problem for ramps, because… your down-shooting ray… shoots straight down the -y axis of playerBox. The playerBox.ellipsoid lower-area SIDES will strike the ramp… as if it were an obstacle instead of a floor.

Therefore, even up-ramps must be treated as if they are a floor or jump-up-onto mesh… and have .checkCollisions = false. Since the down-shooting ray happens directly below playerBox.origin (center)… fat playerBoxes will be seen protruding THRU the up-ramp… because the down-ray won’t see the changing hit.distance values until playerBox CENTER… reaches ramp inclination. It gets to a situation where you really can’t use moveWithCollisions AT ALL, anymore, and need to start doing custom onCollide testing of the player moves… yourself. The same kind of issues arise when dealing with very hilly heightMap terrains. Down-shooting rays… continuously checking for hits and distance to those hits… just can’t do a good job on ramps and steep hills.

What’s the solution? You got me. I once pondered 8 down-shooting picking-rays… positioned “around” the circumference of playerBox.ellipsoid. When playerBox approaches a ramp or steep hill, any forward-moving, backward-moving, or sideways-moving (strafe)… will cause one or more of the 8 down-rays… to see a hit on the ramp/hill… and its hit.distance will suddenly change. By comparing the changed-ray-distances (start of hill/ramp) with the un-changed ray distances (flat ground)… you can calculate a ramp-hill angle. From those calculations, you can determine what playerBox altitude (y-position) to set your playerBox above the ground/ramp. In essence, ground-following WITHOUT collision, so no gravity consids needed, and no Engine.CollisionsEpsilon tweaking.

Erf, huh? Could it BE any more complicated? Eight down-shooting rays… all doing hit-checks and distance-to-hit measurements… every move-step. OUCH! It almost makes you want to say goodbye to .moveWithCollisions, and just “fake” a third-person follow-cam system… by parenting playerBox to the front side of freeCamera… and getting back to using freeCamera’s collisions to get the job done.

But we land right back into the “can’t climb a ramp” problem because of freeCamera’s .applyGravity. Even THAT system seems to need the 8 down-rays (actually 9 down-rays… one straight down from player-center, and 8 surrounders) ground-contour-monitoring system… so you can determine WHEN to reduce/increase scene.gravity.y values.

Also, the freeCamera’s round ellipsoid shape… can cause “climb over the obstacle” problems on low-altitude obstacles, and dive-under on tall obstacles. Also, depending upon scene.gravity and Engine.CollisionsEpsilon settings, the camera can START to climb-over an obstacle or climb up a ramp, but its gravity keeps pulling it backwards/down… making the camera jitter during climb attempts. (see playground below)

The BJS built-in collision system is perfect for SOME applications, but for ramps and heightMap terrains… it can be a serious hassle, as you have discovered. You almost HAVE TO… SOMEHOW… determine the angle of the slope beneath the player… and quickly adjust scene.gravity.y and maybe also Engine.CollisionsEpsilon… on the fly… after each move is completed (to avoid sliding and allow further climbing).

I have never tried a 9-down-ray “terrain contour monitoring system”. If a single down-ray and hit.distance checker is performance-heavy, I would imagine NINE rays would be 9 times worse. And there will be some gruesome calculations after you have “queried” the 9 down-ray hit-distances… to set proper player altitude. (perhaps no .checkCollisions on ANY mesh that player could stand-upon, so YOU set player altitude every move-step). And we haven’t even thought about player jumping, yet. erf.

You’ll still want .checkCollisions = true on real obstacles… un-climb-ables. No passing thru trees or walls. :slight_smile: I wonder what might happen with three (invisible?) stacked boxes for player, and thus… three stacked collider ellipsoids (all parented together). Center ellipsoid is active and moved-with-collisions… for sideways collisions. Then another for feet, and another for head… which you DON’T moveWithCollisions, yet they come along for the ride, via parented to center (invisible?) box. What added powers would THAT system… provide? Hmm. Still, we need MESH.CollisionsEpsilon… a separate setting for each ellipsoid. THAT would be cool.

I dunno, JD. Stay tuned for other comments… if some are available. I believe @splash27 stayed-with built-in collision system, and @Givo went with physics engine, but even with the physics engine… Givo needed special handling to keep player from sliding down ramps and steep terrain hills. I don’t know if either of these users were ever 100% happy with the systems they tried. Maybe they will visit and comment.

https://www.babylonjs-playground.com/#1GL6RR#8 There’s a NON-moveWithCollisions ramp-climb test… using the FreeCamera’s arrow-keys to move. Try holding-down the up-arrow. Line 51 - even with Y-gravity at 1/3 normal, we can’t climb the ramp. Camera tries to climb, gravity pulls it back to ground… causing jitter. We start to ponder WHAT IS AN OBSTACLE and WHAT IS GROUND, eh? nod. HeightMaps are even worse, because putting an ellipse-shaped collider on a heightMap with mountains… is a giant waste of effort. (bad fit)

Is our player wearing spiked mountain-climbing boots? Is there a way to kill gravity.y upon player move complete? Will player moving EVER finish… if player is moving DOWN a hill/ramp?

We sure could use a getAngleOfMeshFaceBeneathPlayer(), eh? hmm. Such a method MIGHT need 9 down-shooting rays to accomplish, and COULD get confused if player is sitting in a “valley” where 3-6 mountains begin in various directions. It might be quite difficult to calculate angleOfMeshFaceUnderPlayer(), in that case… but player sitting in a valley where 3-6 mountains intersect… is likely a common occurrence in hilly country. Using physics engines is really no help at all, here… unless you attempt anti-slide things like Givo tried. (I think he “froze” [repeatedly set] player.impostor.linearVelocity() at 0,0,0 when the move’s linearVelocity neared 0,0,0.)

Gruesome challenge… been happening for years… never really solved. Not sure if it CAN be solved… without a very intelligent terrain-angles monitoring system (what is terrain shaped-like, under and around player).

I wish I had better news for you, JD. If you beat this long-hassle issue… you will be a superstar. :slight_smile: Be sure to test various values in Engine.CollisionsEpsilon… which COULD affect back-sliding. Unfortunately, it also affects the angle to determine scrub-off angles on the SIDES of the ellipsoid collider, too… not just the top and bottom. This means… if you push the player against a wall at some angle, that value will determine WHAT ANGLE is needed… to stop the player “dead stop”… versus allow the player to slide-along the wall (scrubbing). I call it “scrub-off angle”. It also affects top and bottom of ellipsoid scrub-off angle… thus it affects climb-over issues with low-altitude obstacles, and dive-under issues with tall obstacles.

Perhaps we need Engine.CollisionsEpsilon to be split in two… VerticalCollisionsEpsilon (for top/bottom of ellipsoid) and HorizontalCollisionsEpsilon (for sides of ellipsoid).

Sorry for so much talking, gang. I’ve talked about this issue SO MANY TIMES… that I got it all memorized. heh. It would be real nice to solve this repeating problem forever, eh? We need a high-performance .getAngleOfMeshfaceBeneath(player), eh? That would at least be a START… for designing smarter anti-slide/easy-climb player-moving systems, eh? nod.

I believe ray pickInfo object… returns the FaceID number for where upon the “hit” object… the ray intersected. But that is not the ANGLE of that face. AND… we really need the angles of the faces in 8 more directions. Even then… a telephone pole or fence post obstacle… rising from a ramp… could cause all sorts of problems.

And ideally, no matter how steep the ramps or surrounding mountains, even though we are using “air hockey puck” for player altitude and no playerbottom checkCollsions… the mesh should never go partially inside the hill or ramp. If player is a box, and it has spherical .ellipsoid collider, the box corners and edges COULD go inside the steep surrounding mountains or steep surrounding ramps. Only physics engine colliding knows about the corners of the box… due to box Impostors. We have no box colliders for BJS built-in collisions system. Sigh.

I fear that the only real solve… is very advanced use of a physics engine, but moving/turning a mesh with physics applyForce, applyImpulse, or setLinearVelocity/angularVelocity… is challenging, direction-setting-wise and friction/mass-wise (move speed). player.physicsImpostor needs fixedRotation on X and Z axis… to keep from tipping over when colliding with a low obstacle like a squirrel… but it still needs full Y-rotation abilities… for player to turn in any direction. erf. Yet it also probably needs X and Z rotation… to climb/descend ramps/mountains (especially true for vehicles. Humans stay upright when climbing ramps).

God, I’m STILL talking, aren’t I? When will I stop? heh.

I MUST SAY… JD… “edge cases” is pretty funny in your post title… considering you are talking about walking off-of cliffs. I got a good chuckle from THAT one… nice work. :slight_smile:

2 Likes

Thanks for the nitty-gritty details. It’s definitely a lot to think about. I’ll meditate on it and decide where/what I can compromise on for a good-enough solution since there doesn’t seem to be a silver bullet. Once I, or If I, end up with a warm and fuzzy solution, I’ll be sure to share what my approach is.

One approach I tried (and now may circle back to vet some more since, again, there is no perfect answer here) is to apply a very slight negative Y velocity to any moving entity that isn’t currently known to be “falling” (presumably standing on level ground, or ramps that are not extreme) then, after moveWithCollisions, I check previous.Y vs current.Y and if the mesh actually moved downward it may be time to start the falling check each frame on that entity (the falling check is just a down-firing ray with increasing -Y velocity until getElevation(mesh) is near-zero. The snag here is that even if an entity appears to have its feet entirely on the ground, if I ray-cast downward to check its distance from the floor (ie. my getElevation(mesh) function) - I virtually never get 0. It’s always somewhere between 0.01 and 0.04 or something to that effect. So, with that in mind, applying a slightly negative Y velocity to an entity moving across level ground, SOMETIMES results in fall-detection happening. Ok, so just apply more negative Y and make the “did I move downward” threshold to be greater than 0.04, but the increased -Y now makes hill climbing harder. If I could reliably get an actual ZERO from a getElevation function, then my “am I falling” check would be much more reliable.

As for the pun - a happy accident lol.

1 Like

Here’s how I do it in my SIMPLE physics-engine:
Éach frame:

  1. Set grounded and colliding variables to false
  2. Do intersection tests with the environment
    2b) If it intersects with an object, resolve collision -> if the negated MTV is largest on the Y-axis, I know it is standing on/above an object, and I set the colliding variable to true(Just setting the colliding to true without considering the Y-/Up-axis makes wall-crawling and -scaling possible, which is pretty cool as well)
  3. Check the height against the heightmap using built-in getHeightAtCoordinates() -> if the height is less than, go to the height of that function + half-height of the object and set grounded variable to true
  4. if both grounded and collided variables are false, apply gravity.

Some other random points I gathered playing with my engine are:

I started using simple AABB intersections using SAT, and moved on to using OOBBs by finding the original example I believe Babylon.JS’ OOBB intersection code is based on. The original example provided a way of obtaining the MTV and penetration depth - again using SAT.

I found and ported this example for triangle handling: Intersection AABB - Plane ,Triangle - AABB, sphere - AABB, sphere - sphere, sphere - triangle, in three.js · GitHub

At some point I found this example of GJK-EPA using Babylon.JS: collision-gjk-epa/collision-gjk-epa.js at master · wanadev/collision-gjk-epa · GitHub
If you have Many checks each frame, you should optimize for garbage collection, as it creates a lot of new vectors each time. This algorithm works against convex-convex as well as convex-triangle and even triangle-triangle. - Triangles are convex, though, so of course.

Using a triangle-soup is not recommended if you have big triangle-meshes. Instead you should use some sort of spatial partitioning, like the following:
2D or 1D grid(I got this one from timetocode):spatial rid w/ raycast as a search option (can fill the grid with objects or arrays instead of a specific value) · GitHub
BVH(there are a couple of forks of this particular one): GitHub - Pixpipe/bvh-tree: A Bounding Volume Hierarchy implementation using javascript with some intersection methods

Here’s an example of using the BVH-Tree as well as the GJK-EPA algo together(Use WASD to move around - the visualization is bugged, but you get the idea of how the tree works):https://playground.babylonjs.com/#C6XVD5#4

Here are a couple of optimized versions of the above fork of the original BVH-Tree:


You can use it as a pretty damn good broadphase, and then then do a shape-based narrowphase, as long as your geometry is static and doesn’t change. You can use flags to remove checks of bounding boxes, so removing bodies is possible, and something I do. However, if you want to dynamically add bodies, you’ll have to recreate the BVH, or use some sort of magic I haven’t thought of.

Here’s an example of using the above forked and optimized version of the BVH-Tree and AABBs:
400 moving boxes(use WASD) versus 30.000 static(invisible) boxes:
https://playground.babylonjs.com/#ISQCI6
On my rig the physics run at average of 2ms/frame(check console).
If I space out the 400 dynamics to fit the entire space of the statics, I still hit it at average 5ms/frame.
I have a project of around 30.000 static objects all checked in the main-thread, as I use a worker to do culling of reusable/pooled meshes, and it runs pretty smoothly.
400 dynamic boxes versus 100 static spheres(triangles in this case as a test only, as spheres are convex and would yield much better performance):
https://playground.babylonjs.com/#ISQCI6#3

One issue I’m having with the EPA algo is that I haven’t been able to implement a way to properly calculate contact points to update to a more “dynamic” engine. I’ve looked at some clipping algorithms, but haven’t really gotten it to work.

Here’s another unknown physics/graphics engine I found: GitHub - d07RiV/js3d: WebGL2 graphics and 3D physics in Javascript
It hasn’t been updates since, but uses both SAT and GJK, and has algorithms for contacts generation, resolving collisions, forces etc.

I might have forgotten something, but if you at some point want to play around with GJK-EPA or want to implement triangle-meshes as static geometry, this should get you started :stuck_out_tongue:

1 Like

That went a bit over my head, but I appreciate the info. lol.

2 Likes

Yeah, I just woke up at the time, and might have been rambling a little bit :stuck_out_tongue:

1 Like

Hi again guys.

Ok, those tests were done… after assorted Y-unchanged moveWithCollisions(), with flat ground… with ground.checkCollisions = true, right? JD, did you ALSO test with ground.checkCollisions = false; ??

I’m wondering if the player-to-floor colliding… is the reason for the inconsistent down-ray distances/altitudes (returned values).

On another subject, do you know about ground.getHeightAtCoordinates(x, z)? Handy for bumpy terrains… esp IF/AFTER you turn-off ground.checkCollisions, and build your own player standing, ascending-walk/jump, descending-walk/jump… management system.

You probably want to keep the OTHER .checkCollisions active (and use moveWithCollisions)… for those down-hill jumps where player hits a telephone pole mid-jump-flight. (ouch)

And this all makes a person ponder… How do I accomplish parabolic jump-arc… DURING a moveWithCollisions? Yep, that’s right. A hacked-up version of moveWithCollisions()… called jumpWithCollisions(). heh. (yikes) (Wingy - too much coffee) :slight_smile:

Ahh… good times in the world of “interrupt-able animations”.

Not sure what you mean by “ground”. My entire “zone” is just a collection of meshes. Are you assuming I’m using heightmaps for ground? I’m not familiar with doing that, I just know that’s a “thing”.

Even flat grounds might have .getHeightAtCoordinates(x, z) (but why use it, right?).

I think heightMaps and basic ground… are the same base-class object. HeightMaps (and sister DisplacementMaps)… are subDivided groundMesh… whose vertices can have varying elevations. Most people use a gray-scale 2d image to set elevations. Any section of that gray-scaled image that “dithers” from white to black, or vice versa == ramp.

With some 2d gray-scale paint program (likely 8-bit pallette is fine), and a little imagination, you could “paint” one hell of a ground… with many valleys and mountains and ramps.

[https://www.babylonjs-playground.com/textures/worldHeightMap.jpg]
makes…
https://www.babylonjs-playground.com/#12CSAJ#1
That “40” out near the end of line 21… is max mountain height.

When doing gravity tests… sometimes an easy chunk of rolling terrain… is handy. Maybe not. :slight_smile:

Sometimes heightMaps get “bugs”. https://www.babylonjs-playground.com/#RBXBG#6 :slight_smile: (@jerome and his world famous SPS particles, causing trouble) :slight_smile: Not only are those little particles doing heightMap following with ramp climbing/descending, but they are also rotating to the angle of the terrain beneath them, at least somewhat. (But they won’t jump or collide.)

I have no idea what your actual project is about, JD. I’m just thinking about gravity and move/jumpWithCollision testing jigs. With heightMaps… I just don’t know how well moveWithCollisions() will work… when ground.checkCollisions = true. Every bump in the terrain might be seen as an obstacle/collision, and thus be subject to Engine.CollisionsEpsilon setting and numerous other possible issues. I’ve never really tested these things, but I’m curious. I tend to think that the .ellipsoid on a heightMap terrain… would not “fit” very well… ever. :slight_smile: Thus, heightMap.checkCollisions = true… would be… um… trouble. :slight_smile:

2 Likes

Hey @Wingnut - check out this vid for a demo of the issue.

High res version might still be processing. Just uploaded it now.