1
\$\begingroup\$

I have a physical simulation on the streets of a city. The simulation is 2D but in a few points it could develop on multiple "layers": for instance bridges or underground tunnels. I may have up to 3 or very rarely 4 overlapping layers.

I think that using a 3D engine is overkill for this and could complicate things unnecessarily. A 2D engine like Box2D or similar should suffice. However Box2D and the other engines I checked out use a 16-bit mask for collision filtering.

If I divide those 16 bits in 4 submasks, one per each possible layer, I'm left with only 4 bits for filtering, which is not enough for my needs: I need more than 4 to implement other features.

Can you think of any solutions? Can I use those 16 bits in a smarter way? Should I use a 3D engine just for this? Can I tweak the filtering mechanism? Other solutions I could try?

\$\endgroup\$

2 Answers 2

1
\$\begingroup\$

If I'm not mistaken, Box2D also has the ability to store and simulate multiple independent Worlds.

So you could have one world for above-ground and a separate world for below ground, and transition objects from one world to the other at tunnel entrances/exits. This effectively means creating a new physics body for them in the new world, and destroying/recycling/disabling their old physics body. It's not as cheap as twiddling their collision filtering bits, so you'd want to choose where you split worlds carefully to minimize the traffic flux across the threshold.

You could also use one world with collision filtering for most traffic with up to 2 overlapping roads, and use the second world only for a tight vicinity around the rarer 3-4-way overlaps, and only for the least-travelled members of each cluster, to minimize the overhead this world-hopping introduces.

\$\endgroup\$
2
  • \$\begingroup\$ Nice idea. Has this been attempted already? When an object is on the entrance/exit, it would need to have a body in both worlds and the forces of the bodies in the worlds would need to be synchronized. It seems like it could create problems in some cases (imagine if at the same time an object on the entrance threshold collides with two objects one on the ground floor and one on the bridge)... But maybe these cases are so uncommon that I can ignore them. Will try to test your solution and report my results. \$\endgroup\$ Commented Sep 6, 2021 at 0:41
  • \$\begingroup\$ I know Portal had to solve similar problems for bodies straddling both sides of the eponymous portals, so solutions exist for that case — though I could not offer you details, myself. Though you may have an easier time than they did, since in a traffic simulation, you're usually avoiding collisions, so bodies are mostly not exérting forces on one another, just sensing each others' presence, which just requires copying/repeated queries between worlds, no resolution of contradicting impulses/constraints between the two. \$\endgroup\$
    – DMGregory
    Commented Sep 6, 2021 at 0:49
1
\$\begingroup\$

When I first used box2d I quickly reached the 16 bits limit :-)

Then I found a solution: you don't need to use all 16 bits, you need to categorize your objects.

For example, you have some grounds and solid platforms, you put them in the SOLID category.

You have many enemies, you put them in the ENEMY category.

...

An enemy is an enemy regardless of its abilities like size, speed... You will define those abilities upon creating them. For example:

if g_currentlevel == 1 then
    Nme_Flyer.new(xworld, {
        posx=xobject.x, posy=xobject.y,
        texpath="gfx/playable/Bob.png",
        animspeed=14, movespeed=8*0.6, jumpspeed=8*0.5, maxnumjump=2,
        density=1, restitution=0.5, friction=0,
        BIT=G_BITENEMY, COLBIT=nmecollisions, NAME=G_ENEMY,
        lives=1, nrg=1,
    })
elseif g_currentlevel == 2 then
    Nme_Flyer.new(xworld, {
        ...
        density=2, restitution=1, friction=0,
        BIT=G_BITENEMY, COLBIT=nmecollisions, NAME=G_ENEMY,
        lives=1, nrg=4,
    })
end

My complete set up looks something like this (please note I am using Gideros framework, code is in Lua):

-- here we store all possible objects name -- NO LIMIT
G_GROUND = 2^0
G_MVPLATFORM = 2^1
G_PTPLATFORM = 2^2
G_PLAYER = 2^3
G_PLAYER_BULLET = 2^4
G_ENEMY = 2^5
G_ENEMY_BULLET = 2^6
...
G_LADDER = 2^56 -- ;-)

-- here we define some category BITS (that is those objects can collide) -- 2^15 = MAX
G_BITSOLID = 2^0
G_BITPTPF = 2^1
G_BITPLAYER = 2^2
G_BITPLAYERBULLET = 2^3
G_BITENEMY = 2^4
G_BITENEMYBULLET = 2^5
G_BITSENSOR = 2^6

-- here is another trick (what can collide with what)
solidcollisions = G_BITPLAYER + G_BITPLAYERBULLET + G_BITENEMY + G_BITENEMYBULLET
playercollisions = G_BITSOLID + G_BITPTPF + G_BITENEMY + G_BITENEMYBULLET + G_BITSENSOR
playerbulletcollisions = G_BITSOLID + G_BITENEMY + G_BITENEMYBULLET
nmecollisions = G_BITSOLID + G_BITPTPF + G_BITPLAYER + G_BITPLAYERBULLET + G_BITENEMY
nmebulletcollisions = G_BITSOLID + G_BITPLAYER + G_BITPLAYERBULLET

This way you won't run out of bits :-)

EDIT: added the onBeginContact listener

function LevelX:onBeginContact(e)
    local fixtureA, fixtureB = e.fixtureA, e.fixtureB
    local bodyA, bodyB = fixtureA:getBody(), fixtureB:getBody()
    -- PLAYER1
    if (bodyA.name == G_PLAYER and bodyB.name == G_GROUND) or (bodyA.name == G_GROUND and bodyB.name == G_PLAYER) then
        if bodyA.name == G_PLAYER then bodyA.numfloorcontacts += 1
        else bodyB.numfloorcontacts += 1
        end
    end
    if (bodyA.name == G_PLAYER and bodyB.name == G_LADDER) or (bodyA.name == G_LADDER and bodyB.name == G_PLAYER) then
        if bodyA.name == G_PLAYER then bodyA.numladdercontacts += 1
        else bodyB.numladdercontacts += 1
        end
    end
    if (bodyA.name == G_PLAYER and bodyB.name == G_PTPLATFORM) or (bodyA.name == G_PTPLATFORM and bodyB.name == G_PLAYER) then
        if bodyA.name == G_PLAYER then bodyA.numptpfcontacts += 1
        else bodyB.numptpfcontacts += 1
        end
    end
...
\$\endgroup\$
5
  • \$\begingroup\$ I would be doing that, but I only have 4 bits available, not 16, because the 16 bits are divided in 4 for different layers... The 4 bits from 0 to 3 are used for the ground floor, the 4 bits from 4 to 7 are for a bridge and so on. This is necessary to support 4 levels of stacked bridges. Only 4 bits for each layer can be used to categorize objects, but I need more than 4 categories. \$\endgroup\$ Commented Sep 6, 2021 at 0:36
  • \$\begingroup\$ no, you don't need 4 bits for the ground floor, just 1. What you need is give each floor a different name so that you can treat them differently. Same for bridge, ... \$\endgroup\$
    – mourad
    Commented Sep 6, 2021 at 4:12
  • \$\begingroup\$ to add to my previous comment: ...just 1 (SOLID for example). You then put all your grounds under this category. What you need is give each floor a different name so that you can treat them differently, in pure box2d you would use UserData to specify each type (ground1, ground2, ...), in my case I use NAME. Same for bridges, ... \$\endgroup\$
    – mourad
    Commented Sep 6, 2021 at 4:20
  • \$\begingroup\$ I don't understand... What do you mean by name? The entities under the bridge are still going to collide with the bridge and with the entities on the bridge even if they have a different name, aren't they? \$\endgroup\$ Commented Sep 7, 2021 at 11:52
  • \$\begingroup\$ quote: "If I divide those 16 bits in 4 submasks", please don't do that! Here are some links iforce2d.net/b2dtut/user-data, group index iforce2d.net/b2dtut/collision-filtering maybe that's what you are looking for? Good luck! \$\endgroup\$
    – mourad
    Commented Sep 8, 2021 at 5:05

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .