Dyn4j missing collisions, sometimes


#1

Hello, I realize dyn4j isn’t directly supported by jme but the dyn4j forum is dead and a few people here use dyn4j so I was hoping I might get pointed in the right direction.

I have dyn4j running physics and collision detection in my simple space asteroids game, and on occasion my projectiles won’t register collisions with the asteroids. Here’s a quick image showing several projectiles, 2 of which register a hit and 2 which do not. The actual frequency of failed hits is probably <10% but as you can see here it tends to happen in waves.

Imgur

I’ll try to get all the related code in an easy to read order:
first, the projectile movement and detection code

public void physUpdate(double tpf) {
        if(projectiles.applyChanges());
        List<ConvexCastResult> results = new ArrayList<>();
        for(Entity e : projectiles){
            //raycast the next position, if we can move there do so
            Transform trans = e.get(Transform.class);
            Projectile projectile = e.get(Projectile.class);
            Vector2 startPos = trans.getPosition();
            Vector2 vel = projectile.getVelocity().product(tpf);
            Vector2 nextPos = startPos.sum(vel);
            ed.setComponent(e.getId(), new Transform(nextPos));
            CollisionMask mask = e.get(CollisionMask.class);
            Filter filter = new CategoryFilter(mask.getGroup(), mask.getMask());
            //Collisions are still being iffy, convex and ray casting both miss collisions occaisionally
            org.dyn4j.geometry.Transform worldTrans = new org.dyn4j.geometry.Transform();
            worldTrans.setTranslation(startPos);
            Convex convex = new Circle(projectile.getSize());
            if(world.convexCast(convex, worldTrans, vel, 0, filter, true, true, true, results)){
                for(ConvexCastResult result : results){
                    EntityId id = (EntityId)result.getBody().getUserData();
                    long power = e.get(Attack.class).getAttack();
                    ed.setComponents(ed.createEntity(), new Damage(id, power), e.get(Parent.class));
                }
                ed.setComponents(ed.createEntity(),
                        new SoundEffect(e.get(ImpactEffect.class).getSoundEffect()),
                        new Decay(1)
                );
                ed.removeEntity(e.getId());
            }
        }
    }

Collision groups

public static final long PLAYERGROUP = 1;
    public static final long ASTEROIDGROUP = 1 << 1;
    public static final long PROJECTILEGROUP = 1 << 2;
    public static final long PICKUPGROUP = 1 << 3;

spawning of projectiles

@Override
    public void fire(Entity parent, EntityData ed) {
        hasFired = true;
        Transform pTrans = parent.get(Transform.class);
        Vector2 pos = pTrans.getPosition().sum(new Vector2(0,1).rotate(pTrans.getRotation()));
        Vector2 velocity = new Vector2(0,10).rotate(pTrans.getRotation());
        EntityId id = ed.createEntity();
        ed.setComponents(id,
                new Projectile(velocity, 0.25),
                new Attack(3),
                new Transform(pos),
                new Model(Model.BLASTER),
                new Color(ColorRGBA.Red),
                new Decay(3f),
                new Parent(parent.getId()),
                new CollisionMask(CollisionMask.PROJECTILEGROUP, CollisionMask.ASTEROIDGROUP),
                new SoundEffect("Sounds/BlasterShoot.wav"),
                new ImpactEffect("Sounds/BlasterHit.wav")
        );
        projectiles[curProjectile] = id;
        curProjectile = -1;
    }

spawning of asteroids

public EntityId spawnAsteroid(int size, Vector2 position, double dir){
        EntityId id = ed.createEntity();
        Vector2 vel = new Vector2(0, 1+random.nextDouble()*4);
        vel.rotate(random.nextDouble()*Math.PI*2);
        ed.setComponents(id,
                new Model(Model.ASTEROID),
                new Color(ColorRGBA.Brown),
                new Transform(position, dir),
                new Size(size),
                new Health(size*3),
                new InitialVelocity(vel, 0),
                new Attack(1),
                new Bounded(Bounded.Behaviour.WRAP, 20),
                new Collider(1, 0, false, new Circle(size)),
                new CollisionMask(CollisionMask.ASTEROIDGROUP,
                        CollisionMask.PLAYERGROUP|CollisionMask.PROJECTILEGROUP)
        );
        LOG.log(Level.FINE, "Spawning an asteroid at {0}", position);
        return id;
    }

very last, adding of collisions objects to the world

private void addBody(Entity e){
        Body b = new Body();
        Collider col = e.get(Collider.class);
        Transform trans = e.get(Transform.class);
        double density = col.getDensity();
        CollisionMask mask = e.get(CollisionMask.class);
        CategoryFilter filter = new CategoryFilter(mask.getGroup(), mask.getMask());
        for(Convex c : col.getShapes()){
            BodyFixture bf = b.addFixture(c, density);
            bf.setFilter(filter);
        }
        b.setUserData(e.getId());
        b.setMass(col.getMassType());
        b.setLinearDamping(col.getDamping());
        b.setAngularDamping(col.getDamping());
        b.getTransform().setTranslation(trans.getPosition());
        b.getTransform().setRotation(trans.getRotation());
        InitialVelocity vel = ed.getComponent(e.getId(), InitialVelocity.class);
        if(vel != null){
            b.setLinearVelocity(vel.getLinVel());
            b.setAngularVelocity(vel.getAngVel());
        }
        bodyMap.put(e.getId(), b);
        world.addBody(b);
    }

I have been fighting these disappearing collisions for a while now. Before I was using raycasts, but I switched to convex casting to make sure it wasn’t the rays. I imagine it might have something to do with the collision masking but as far as I can tell the mask is mirrored correctly so either body can initiate against the other. Has anyone else had these sort of missed collisions before?


#2

Register a collision listener instead of trying to detect the collisions yourself? I have only tried the way its documented in the advanced-section on dyn4j, haven’t had your issue so far.


#3

I want to avoid adding a bunch more bodies to the physics world if possible. Though if no other solutions crop up I’ll likely have to do this.


#4

I implemented the projectiles as kinematic bodies and a collision listener and it does appear to catch 100% of collisions now, while performing 30% slower with just 3 projectiles… I’ll likely use this for now but not sure how this will hold up when I get around to adding machine guns and power ups.


#5

How are you judging performance, though? 30% frame drop for example is unreliable. Dropping down from 1000 fps to 700 fps is not the same as 700 to 490 fps. It is a 30% drop, but 30% of one second is not the same as 30% of 2 seconds. In that sense it’s exponential. Significance in time per frame decreases more and more as the rate increases. It’s likely not going to reduce in a linear fashion as you add objects, it will most likely reduce less and less as you add them.

I’d worry more when you’re nearing the minimum rate you expect to achieve.

If you are basing your performance on fps alone, that is.


#6

indeed, cant just say 30% :smiley:

otherwise i would have 1 fps when counting each system fps percent drop :smiley:

when you do simple loop over tousands/milions records each frame your fps can drop from 3000 to 1000, but this dont mean you cant run it more time. second use in each frame would be 700 fps for example, not 0.

Anyway this example is also good to explain that game should be ECS/event based, not loop.


#7

Well at 1000fps each frame takes up 1000th of a second. If I lose one 1000th through work I lose one frame. If I’m running at 300fps I can lose one 300th of a second to lose one frame.

Every time I lose a frame I increase the time required to lose another frame.

So at 4000fps on a virtually empty scene, literally anything I do will make me lose a ton of frames. Adding 5 cubes might even drop me to 3000fps - a whole thousand fps, but that doesn’t mean I’ll lose another 1000fps if I add another 5 cubes. You could use that as a performance monitor, but I’d have to get a piece of paper to find an actual equation. It’s certainly not a 25% performance drop at any rate.


#8

Ain’t ECS design == shit ton of loops? Loop though different Systems which in turn loop through Entities (based on Components).

And event based is still also a loop. Loop through events.

Whatever is the design, the loop should not be in the render thread, right?


#9

depends how ECS is implemented/used

if you use it properly, it trully loop only through NPC(or some active entites), so even if you got 100000 entities, it dont loop them all(but lets say 100 instead of this - dpends on game ofc). (you can also decide easly which one should be looped or not, other entities(or rather systems) do event actions only truly)

about how to search nearby entity/etc, its about implementation to not loop them all…, i dont use Zay-ES so idk how its done there. but it should be much faster than looping all.


#10

An ECS does loop, but it’s a “smart” loop. Instead of iterating all entities it keeps a record of “changed” components and only loops over them.

So in a PVP environment for example, if you have 128 players and only 3 people were hurt in that frame, only 3 health components would be iterated and actioned by the health system.

Locations, directions, etc are the killer, but iterating over 1000 things isn’t time consuming per-se - it’s what you do with each value of the iterator, so to that end it’s the developer causing the issue, not the system.


#11

yes, indeed. trully only AI(when doing visible action)/physics(if ES even need it…)/active state entites should be updated each frame, other iterations are done via Events(hard to explain now, but they are executed optimized way - its more like cache system that know what to change), so this iterations occur only once per some time. (only when required).

in short: every iteration is done ONLY if required, so it save a lot of time.

edit:

also fact, iterating 1000 is not a problem, but iterating 100000 each frame is a problem. depends what game require. also its not 100x times slower, it dont calculate this way :slight_smile:


#12

There are ways around that, but this is going way off-topic…


#13

Yeah but in all this is about how you implement stuff. Our game for example the 128 players would be processed twice probably. Or even more. First the health system to apply regeneration since they all have Health component. Then by AI system since they all have AIComponent. Maybe they are also walking at the same time and get visited by SteeringSystem… It is all in the implementation. This is all game logic side. Ticks.

On the UI side I was able to use the changes. For example each frame just applies changed positions.

Maybe I went overboard with the ECS in the logic side then. But yeah, pretty much you can implement all kinds of designs in many ways.