craftyjs/Crafty

Collision does not work for rotated entities with updated origin

adbo28 opened this issue · 7 comments

I am using the following piece of code to check for collisions and to prevent them from moving, if needed.
It works well until I change the rotation origin (this.origin("center")) and rotate the this object. The erroneous behavior can be easily shown by adding the SolidHitBox component to the respective entity.

var hitDatas, hitData;
if ((hitDatas = this.hit('Wall'))) { // check for collision with walls
    hitData = hitDatas[0]; // resolving collision for just one collider
    if (hitData.type === 'SAT') { // SAT, advanced collision resolution
        // move player back by amount of overlap
        this.x -= hitData.overlap * hitData.nx;
        this.y -= hitData.overlap * hitData.ny;
    } else { // MBR, simple collision resolution
        // move player to previous position
        this.x = evt._x;
        this.y = evt._y;
    }
}

It works well until I change the rotation origin (this.origin("center")) and rotate the this object. The erroneous behavior can be easily shown by adding the SolidHitBox component to the respective entity.

Thanks for the bug report! Could you describe the error in a bit more detail? (Or provide a self-contained example?)

Hi, sure the subset of my code which displays the reported behaviour follows:

`


    <div id="game"></div>
    
    <script type="text/javascript" src="https://rawgithub.com/craftyjs/Crafty/release/dist/crafty-min.js">
    </script>
        
    <script>
        Crafty.init(500,500, document.getElementById('game'));

        Crafty.c("Rocket", {
            init: function() {
                this.addComponent("2D, DOM, Color, Motion, Collision, SolidHitBox");
                this.rotating = 0;
                this.rotation = Crafty.math.randomNumber(0, 359);
                this.x = Crafty.math.randomNumber(50, 450);
                this.y = Crafty.math.randomNumber(50, 450);
                this.w = 30;
                this.h = 30;
                this.origin("center");
            },

            moveOneStep: function(evt) {
                //rotation
                this.rotation += this.rotating;

                //check for collision after rotation
                var hitDatas, hitData;
                if ((hitDatas = this.hit('Wall'))) { // check for collision with walls
                    hitData = hitDatas[0]; // resolving collision for just one collider
                    this.rotation -= this.rotating;
                    return this;
                }

                //move
                diff_x = Math.cos(this.rotation/180*Math.PI);
                this.x += diff_x;
                diff_y = Math.sin(this.rotation/180*Math.PI);
                this.y += diff_y;

                //check for collision after move
                if ((hitDatas = this.hit('Wall'))) { // check for collision with walls
                    hitData = hitDatas[0]; // resolving collision for just one collider
                    if (hitData.type === 'SAT') { // SAT, advanced collision resolution
                        // move player back by amount of overlap
                        this.x -= diff_x;
                        this.y -= diff_y;
                    } else { // MBR, simple collision resolution
                        // move player to previous position
                        this.x = evt._x;
                        this.y = evt._y;
                    }
                }


                return this;
            }
        });

        Crafty.e('Collision, Wall, 2D, DOM, Color').attr({x: 0, y: 0, w: 20, h: 500}).color('black');
        Crafty.e('Collision, Wall, 2D, DOM, Color').attr({x: 0, y: 0, w: 500, h: 20}).color('black');
        Crafty.e('Collision, Wall, 2D, DOM, Color').attr({x: 480, y: 0, w: 20, h: 500}).color('black');
        Crafty.e('Collision, Wall, 2D, DOM, Color').attr({x: 0, y: 480, w: 500, h: 20}).color('black');

        //--- rocket1 ---
        var rocket = Crafty.e('Rocket, Keyboard')
            .color('blue');
        
        rocket.bind("KeyDown", function(e) {
            if(e.key == Crafty.keys.LEFT_ARROW) {
                rocket.rotating = +5;
            }
            if(e.key == Crafty.keys.RIGHT_ARROW) {
                rocket.rotating = -5;
            }
        });
        rocket.bind("KeyUp", function(e) {
            if(e.key == Crafty.keys.RIGHT_ARROW || e.key == Crafty.keys.LEFT_ARROW) {
                rocket.rotating = 0;
            }
        });

        rocket.bind("UpdateFrame", rocket.moveOneStep);

    </script>

</body>

`

my previous comment does not contain the inital lines, but they are quite obvious:
< html >
<head >
</head >
<body >

If I skip the line this.origin("center"); in my code, the SolidHitBox overlaps well with my square. Once this line is included, the SolidHitBox and my square divide.

Looks like something is going wrong when you set the width, origin, rotation, and hitbox in a specific order.

I'll look into this further, but setting the origin after the dimensions, but before the other properties works around the bug:

this.addComponent("2D, DOM, Color, Motion, Collision, SolidHitBox");
this.rotating = 0;
this.w = 30;
this.h = 30;
this.origin("center");
this.rotation = Crafty.math.randomNumber(0, 359);
this.x = Crafty.math.randomNumber(50, 450);
this.y = Crafty.math.randomNumber(50, 450);

e: originally suggested setting the origin first, but that produces a slightly different bug. I think the root issue is that the collision hitbox isn't recalculated correctly when the origin is applied, and it's manifesting in a couple of different ways.

thanks a lot. the workaround works fine :)

With the fix in #1202, your original example now works, so I'm going to close.

(You can find the most recent build of the develop branch here)

Thanks for the report!