Closed 8Observer8 closed 1 year ago
Fox example, @box2d/core doesn't have this issue. I wrote my simple version of Pong game: https://8observer8.github.io/webgl10-js/2d-games/pong-2d-noobtuts-port-box2dcore-webgl-js/ very quickly - in 2-3 days from scratch in my spare time. But I would spend a lot of time to get around that issue.
Perhaps I understood the problem and solved it. These local coordinate axes are very large. This means that the engine does not work with its size, so it starts to work incorrectly. For example, the Box2D documentation states that object sizes should be between 0.1 and 10.
I rewrote my example in Pixi.js. Picked up the normal size of the coordinate axes. I tried to adjust the racket close to the ball, but I don’t see this problem:
This code prints normal now when the collision occurs:
const eventQueue = new RAPIER.EventQueue(true);
function render() {
requestAnimationFrame(render);
world.step(eventQueue);
eventQueue.drainCollisionEvents((handle1, handle2, started) => {
/* Handle the collision event. */
// console.log(handle1);
world.narrowPhase.contactPair(handle1, handle2, (manifold, flipped) => {
console.log(manifold.normal());
});
});
debugDrawer.drawLines();
renderer.render(stage);
debugDrawer.clear();
}
The normal is needed in rapier
, because it registers even such collisions when the ball flies very close, but does not bounce off the racket. But, for example, in Box2D-WASM such collisions are ignored. I don't like it in rapier
.
No, the problem was not completely solved by setting pixelPerMeter from 30 to 3 (and the ball's velocity to (10, 0)) in the first example in pure WebGL 1.0. The ball bounces off the racket at the beginning and doesn't bounce later: https://plnkr.co/edit/LmVUDVwK7Ss7C6wn?preview
In your first post you don’t explain what behavior you are expecting from the ball, so I’m not sure what you are looking for here exactly. The "ball"’s behavior looks fine to me. It’s moving toward the very edge of the racket. That’s at place where rounding errors will result in different results based on how the "ball" is placed.
Your comparison gif with Box2D isn’t very meaning full as the "ball" in your Box2D example isn’t anywhere close to the racket’s edge.
The normal is needed in rapier, because it registers even such collisions when the ball flies very close, but does not bounce off the racket. But, for example, in Box2D-WASM such collisions are ignored. I don't like it in rapier.
Clearly there is a contact near the edge of the two cubes. So Rapier is right to report the contact since that’s what geometry dictates.
In addition, keep in mind that physics engines behave differently. IIRC Box2D involves a solid collision margin around each shape. For cubes I think the cube is shrunk inwards (instead of expanded outwards) by the collision margin, resulting in small rounded corners. So the behavior near these corners can be very different.
Finally, I recommend to use a pixelPerMeter
of 1 here. That way your ball is of size 1 which reduces rounding errors.
Clearly there is a contact near the edge of the two cubes. So Rapier is right to report the contact since that's what geometry dictates.
The most important thing in games is collision detection. Collision detection exists, for example, to determine if a bullet hit an enemy or a character. If it hits, then we reduce the number of lives, start the animation, etc. If this is a game of Pong or Arkanoid, then the collision detection function helps to understand whether the ball flew past the racket, or there was a collision and it bounced, then you need to change the angle of the ball rebound. In the Arkanoid game, we can easily understand whether the ball hit the block or flew past and did not bounce. If the ball hits the block, then it bounced, and the block can be removed.
You have prioritized the definition of such collisions (more specifically touches without collisions), which makes it incredibly difficult to write the simplest games from which novice game developers begin to get acquainted with the physics engine: Arkanoid and Pong games. You should have added a flag to determine if there was a real collision or if it was just a touch. Defining this with normals makes writing simple games impossible for someone new to your engine.
I follow two Unity tutorials: https://noobtuts.com/unity/2d-pong-game and https://noobtuts.com/unity/2d-arkanoid-game I rewrite them to pure WebGL. Unity uses Box2D for 2D games. Unity has the OnCollisionEnter2D method that works as expected in a Pong or Arkanoid game:
using UnityEngine;
using System.Collections;
public class Ball : MonoBehaviour {
public float speed = 30;
void Start() {
// Initial Velocity
GetComponent<Rigidbody2D>().velocity = Vector2.right * speed;
}
void OnCollisionEnter2D(Collision2D col) {
// Note: 'col' holds the collision information. If the
// Ball collided with a racket, then:
// col.gameObject is the racket
// col.transform.position is the racket's position
// col.collider is the racket's collider
}
}
Maybe I'm wrong and all other popular physics engines behave the same way. I will try to reproduce this situation on other physics engines: @box2d/core, box2d-wasm, OimoPhysics, Ammo.js, and Cannon ES.
Finally, I recommend to use a pixelPerMeter of 1 here. That way your ball is of size 1 which reduces rounding errors.
In this case, the axes are very small:
Solving the problem with the size of the world, to solve the problem with the incorrect behavior of the engine. It is very important. So Box2D says about it into intro: https://box2d.org/documentation/
Box2D works with floating point numbers and tolerances have to be used to make Box2D perform well. These tolerances have been tuned to work well with meters-kilogram-second (MKS) units. In particular, Box2D has been tuned to work well with moving shapes between 0.1 and 10 meters. So this means objects between soup cans and buses in size should work well. Static shapes may be up to 50 meters long without trouble.
Being a 2D physics engine, it is tempting to use pixels as your units. Unfortunately this will lead to a poor simulation and possibly weird behavior. An object of length 200 pixels would be seen by Box2D as the size of a 45 story building.
Caution: Box2D is tuned for MKS units. Keep the size of moving objects roughly between 0.1 and 10 meters. You'll need to use some scaling system when you render your environment and actors. The Box2D testbed does this by using an OpenGL viewport transform. DO NOT USE PIXELS.
It's even highlighted:
But the rapier documentation doesn't include it in the intro.
So, Solving the problem with the size of the world, to solve the problem with the incorrect behavior of the engine.
In the following example:
you need to replace these lines of code:
const pixelsPerMeter = 30;
ballRBDesc.setLinvel(1, 0);
to these:
const pixelsPerMeter = 1;
ballRBDesc.setLinvel(30, 0);
But, as you can see in the gif animation above, the local coordinate axes become very small and almost invisible.
GitHub repository. This repository contains my step-by-step instruction in README that shows how to build it with Rollup for debug and release modes. The release mode uses uglify-js
. I use importmap
to make bundles with Rollup. This allows me to simply copy files to Playgrounds and save space on my laptop because I don’t have to install NPM packages for every example.
This is not a bug. The expected result is correct and the issue should be closed. If the user doesn't want to use collision normals because "its too hard", then there should be a ticket to show an example of how to do that, instead.
The expected result is not true in terms of game development. If the ball touches a block in an Arkanoid game, then the block must be removed from the game and the ball must bounce. I think this is how it is done in all game engines and physics engines. Touch should be in the lowest priority - then enter touch materials. I just wanted to point out a big problem for newbies. But to solve this problem with separating touch and collision, you just need to enter, for example, a flag indicating whether the interaction was implemented: the ball hit the block or not. Otherwise, it will be difficult to popularize the engine. It will have a very high entry threshold for early physics games like Pong and Arkanoid. Then @box2D/core and OimoPhysics will be the most beginner friendly. For example, it was much easier for me to take @box2D/core or box2d-wasm than to try to describe a complex algorithm for analysis of "normals". I wrote my simple version of Pong game (https://8observer8.github.io/webgl10-js/2d-games/pong-2d-noobtuts-port-box2dcore-webgl-js/) very quickly - in 2-3 days from scratch in my spare time in @box2d/core, pure WebGL 1.0, glMatrix, and FreeTexturePacker. If making examples for those who are new to game development, start with those games and make the examples around those games as simple as possible.
Making an engine popular should be of no concern to you, although everyone appreciates the motive. I find Rapier is easy to use, much easier than some other engines. If you feel it doesn't fit your game, feel free to use another engine. There are plenty to pick from.
Thank you very much for your patience. I'm sorry I wasted your time. Good luck. I close the topic.
I leave an example. Maybe someone will come in handy:
Your comparison gif with Box2D isn’t very meaning full as the "ball" in your Box2D example isn’t anywhere close to the racket’s edge.
@LeXXik @sebcrozet I want to apologize to you guys for wasting so much of your time. You were absolutely right. Box2D behaves exactly the same way:
Not exactly the same. The BeginContact() method in Box2D is only called once per contact, while in rapier2d.js the contact handler is called on every frame for the duration of the contact. But this is a completely different topic, which is not related to this topic.
You can see the problem on the sandboxes above and on the gif-animation: