Lusito / box2d.ts

Full blown Box2D Ecosystem for the web, written in TypeScript
https://lusito.github.io/box2d.ts
60 stars 6 forks source link

Rotation problem with box2d-core and WebGL #36

Closed 8Observer8 closed 4 months ago

8Observer8 commented 1 year ago

I will try to fix this issue later. At the moment I have no ideas. Can someone fix: https://plnkr.co/edit/zTthXbGVITITERkL (another sandbox: https://playcode.io/1211513)

debug-drawer-box2dcore-webgl-js-first-trying-to-fix-a-rotation-problem

Lusito commented 1 year ago

In theory, all you need to do to get the final point is store the xf from the pushTransform and in the draw function multiply it with the vector you need in final coordinates:

b2Transform.MultiplyVec2(xf, pointIn, pointOut)

At least, that's what's been done in the original: https://github.com/erincatto/box2d/blob/c6cc3646d1701ab3c0750ef397d2d68fc6dbcff2/src/dynamics/b2_world.cpp#L1095

8Observer8 commented 1 year ago

Local scale transformation to form a line or segment

I will try to understand this problem by explaining how the code works. I think the problem is in the order in which the transformations are applied. I will use the original example from the topic: https://plnkr.co/edit/zTthXbGVITITERkL

Focus on the debug-drawer.js file. This method is automatically called every frame:

    DrawSolidPolygon(vertices, vertexCount, color) {
        mat4.mul(this.projViewMatrix, this.projMatrix, this.viewMatrix);
        gl.uniform3f(this.uColorLocation, color.r, color.g, color.b);

        this.drawLine(vertices[0], vertices[1]);
        this.drawLine(vertices[1], vertices[2]);
        this.drawLine(vertices[2], vertices[3]);
        this.drawLine(vertices[3], vertices[0]);
    }

I need to draw four lines to create a rectangle. In order to draw a line, I have to transform the square which is in the VBO using matrix operations. I create a vector using start and end points in the this.drawLine method:

        this.tempVec[0] = this.toX - this.fromX;
        this.tempVec[1] = this.toY - this.fromY;

I can find the length of this vector using the glMatrix library:

this.length = vec3.length(this.tempVec);

I have a predefined this.lineWidth = 4; variangle and I can create a scale vector:

        this.scale[0] = this.length;
        this.scale[1] = this.lineWidth;
        this.scale[2] = 1;

I use this vector to scale the square to make a line:

mat4.fromRotationTranslationScale(this.modelMatrix, this.rotation, this.position, this.scale); // Create a model matrix that keep: scale, rotation, and position
mat4.mul(this.mvpMatrix, this.projViewMatrix, this.modelMatrix); // Multiply the projection view matrix (orthographic projection and camera) by the model matrix to create the model view projection matrix
gl.uniformMatrix4fv(this.uMvpMatrixLocation, false, this.mvpMatrix); // Move calculated matrix to vertex shader
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); // Draw a line (converted square to line)

...

I will continue my explanation later...

8Observer8 commented 1 year ago

Local position transformation to set a center of line or segment

this.centerX and this.centerY are calculated like this inside of this.drawLine:

    drawLine(pointA, pointB) {
        this.fromX = pointA.x * this.pixelsPerMeter;
        this.fromY = pointA.y * this.pixelsPerMeter;
        this.toX = pointB.x * this.pixelsPerMeter;
        this.toY = pointB.y * this.pixelsPerMeter;
        if (this.fromX > this.toX) {
            this.centerX = this.toX + Math.abs(this.fromX - this.toX) / 2;
        }
        else {
            this.centerX = this.fromX + Math.abs(this.toX - this.fromX) / 2;
        }
        if (this.fromY > this.toY) {
            this.centerY = this.toY + Math.abs(this.fromY - this.toY) / 2;
        }
        else {
            this.centerY = this.fromY + Math.abs(this.toY - this.fromY) / 2;
        }
8Observer8 commented 1 year ago

Local rotation transformation for a line or segment

This code uses a begin and end of a line to calculate a local rotation of a line:

quat.rotationTo(this.rotation, this.unitX, this.tempVec);

Amount of transformations

Local transformations to form a line and to set position and rotation of a line:

Two global transformations:

    PushTransform(xf) {
        // Translation
        this.xf[0] = xf.p.x * this.pixelsPerMeter;
        this.xf[1] = xf.p.y * this.pixelsPerMeter;
        // Rotation
        this.radianOffset = xf.q.s;
    }

We have five transformations.

8Observer8 commented 1 year ago

Original example from the topic on two sandboxes:

8Observer8 commented 1 year ago

Now you can see this order of transformations in the example above:

        vec3.add(this.position, this.position, this.xf);
        quat.rotateZ(this.rotation, this.rotation, this.radianOffset);
        mat4.fromRotationTranslationScale(this.modelMatrix, this.rotation,
            this.position, this.scale);
        mat4.mul(this.mvpMatrix, this.projViewMatrix, this.modelMatrix);
        gl.uniformMatrix4fv(this.uMvpMatrixLocation, false, this.mvpMatrix);
        gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);

Before of this code I create a quaternion (this.rotation) to rotate a horizontal line to 0 or to 90. I add this.radianOffset to this.rotation here:

quat.rotateZ(this.rotation, this.rotation, this.radianOffset);

The final transformation (this.mvpMatrix or this.modelMatrix) is applied in order: 1) scale 2) rotation 3) translation. I should apply this.radianOffset after: 1) scale, 2) rotation to 0 or to 90 and 3) translation this.position. I replaced this line mat4.fromRotationTranslationScale(this.modelMatrix, this.rotation, this.position, this.scale); to

        // Like above
        vec3.add(this.position, this.position, this.xf);
        quat.rotateZ(this.rotation, this.rotation, this.radianOffset);
        mat4.identity(this.modelMatrix);
        mat4.translate(this.modelMatrix, this.modelMatrix, this.position);
        mat4.fromQuat(this.quatMat, this.rotation);
        mat4.mul(this.modelMatrix, this.modelMatrix, this.quatMat);
        mat4.scale(this.modelMatrix, this.modelMatrix, this.scale);
        mat4.mul(this.mvpMatrix, this.projViewMatrix, this.modelMatrix);
        gl.uniformMatrix4fv(this.uMvpMatrixLocation, false, this.mvpMatrix);
        gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);

Like in the documentation which describes how can I replace mat4.fromRotationTranslationScale:

image

After this I have the same example but I can apply transformations separately:

box2d-core-wrong-transformations

8Observer8 commented 1 year ago

You can comment this piece of code:

        // Like above
        vec3.add(this.position, this.position, this.xf);
        quat.rotateZ(this.rotation, this.rotation, this.radianOffset);
        mat4.identity(this.modelMatrix);
        mat4.translate(this.modelMatrix, this.modelMatrix, this.position);
        mat4.fromQuat(this.quatMat, this.rotation);
        mat4.mul(this.modelMatrix, this.modelMatrix, this.quatMat);
        mat4.scale(this.modelMatrix, this.modelMatrix, this.scale);
        mat4.mul(this.mvpMatrix, this.projViewMatrix, this.modelMatrix);
        gl.uniformMatrix4fv(this.uMvpMatrixLocation, false, this.mvpMatrix);
        gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);

And add this code:

        mat4.identity(this.modelMatrix);
        mat4.translate(this.modelMatrix, this.modelMatrix, this.xf);
        mat4.fromQuat(this.quatMat, this.radianOffset);
        mat4.mul(this.modelMatrix, this.modelMatrix, this.quatMat);
        mat4.translate(this.modelMatrix, this.modelMatrix, this.position);
        mat4.fromQuat(this.quatMat, this.rotation);
        mat4.mul(this.modelMatrix, this.modelMatrix, this.quatMat);
        mat4.scale(this.modelMatrix, this.modelMatrix, this.scale);
        mat4.mul(this.mvpMatrix, this.projViewMatrix, this.modelMatrix);
        gl.uniformMatrix4fv(this.uMvpMatrixLocation, false, this.mvpMatrix);
        gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);

But I see nothing and I don't understand why because everything looks right:

        mat4.identity(this.modelMatrix);
        mat4.translate(this.modelMatrix, this.modelMatrix, this.xf);
        mat4.fromQuat(this.quatMat, this.radianOffset);
        mat4.mul(this.modelMatrix, this.modelMatrix, this.quatMat);
        mat4.translate(this.modelMatrix, this.modelMatrix, this.position);
        mat4.fromQuat(this.quatMat, this.rotation);
        mat4.mul(this.modelMatrix, this.modelMatrix, this.quatMat);
        mat4.scale(this.modelMatrix, this.modelMatrix, this.scale);
        mat4.mul(this.mvpMatrix, this.projViewMatrix, this.modelMatrix);
        gl.uniformMatrix4fv(this.uMvpMatrixLocation, false, this.mvpMatrix);
        gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);

image

8Observer8 commented 1 year ago

I found a problem in this line:

mat4.fromQuat(this.quatMat, this.radianOffset);

The second argument of mat4.fromQuat must be a quaternion (not angle in radians). I don't have error message because it is JavaScript.

I am very close to a solution. I have a slight angle offset between the object and the borders of the colliders:

very-close-to-sovle-a-rotation-program

8Observer8 commented 1 year ago

I have a slight angle offset between the object and the borders of the colliders

I think it is a problem with box2d-core. Because I set angle = 40 for boxBody, but here:

    PushTransform(xf) {
        // Translation
        this.xf[0] = xf.p.x * this.pixelsPerMeter;
        this.xf[1] = xf.p.y * this.pixelsPerMeter;
        // Rotation
        this.radianOffset = xf.q.s;
        console.log(this.radianOffset * 180 / Math.PI);
    }

I print angle and it is equal to 36.

8Observer8 commented 1 year ago

I created this demo in pure WebGL 1.0, glMatrix, Tiled, FreeTexturePacker and box2d/core: https://8observer8.github.io/webgl10-js/mario-2d-jumps-box2dcore-webgl-js/ to show how I draw colliders with sprites.

mario-2d-jumps-box2dcore-webgl-js

image

8Observer8 commented 1 year ago

Discussion: The debug drawing of colliders for box2d/core using pure WebGL 1.0, glMatrix, and JavaScript

8Observer8 commented 4 months ago

I think there are no plans to implement drawing with direct coordinates of vertices without save() and restore() line in Box2D-WASM.