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

Feature Request: Implementation of b2Draw, SetDebugDraw and DebugDraw #33

Closed 8Observer8 closed 1 year ago

8Observer8 commented 1 year ago

Please, implement b2Draw, SetDebugDraw, and DebugDraw like it was implemented in pybox2d

edit-gravity-debug-drawer-opengl3-pyqt6

from Box2D import b2Draw
from OpenGL.GL import *

class DebugDrawer(b2Draw):

    def __init__(self, program, worldScale):
        super().__init__()
        self.program = program
        self.WORLD_SCALE = worldScale

    def DrawSolidPolygon(self, vertexes, color):
        print("Polygon. Begin")
        print(vertexes[0][0] * 30, vertexes[0][1] * 30)
        print(vertexes[1][0] * 30, vertexes[1][1] * 30)
        print(vertexes[2][0] * 30, vertexes[2][1] * 30)
        print(vertexes[3][0] * 30, vertexes[3][1] * 30)
        print("Polygon. End")
        self.debugDrawer = DebugDrawer(self.program, self.WORLD_SCALE)
        self.world.renderer = self.debugDrawer
        self.debugDrawer.flags = { 'drawShapes': True,
            'drawJoints': True, 'drawAABBs': True, 'drawPairs': True }
    def paintGL(self):
        glClear(GL_COLOR_BUFFER_BIT)
        glBindVertexArray(self.vao)
        self.projViewMatrix = self.projMatrix * self.viewMatrix

        if self.showColliders:
            self.world.DrawDebugData()
Lusito commented 1 year ago

Did you miss https://lusito.github.io/box2d.ts/docs/guide/debug-draw/ or is it not good enough?

8Observer8 commented 1 year ago

I tried your solution but it is very inconvenient for pure WebGL.

1) I can't set an arbitrary coordinate center on the debug canvas like I do with glMatrix.ortho(out, left, right, bottom, top, near, far) 2) But I need the colliders to be drawn behind the game objects in order to see the boundaries of the sprites

Why not pass the vertex coordinates to the DrawSolidPolygon method so I can draw the borders myself with pure WebGL?

debug_drawer.py

from Box2D import b2Draw
from OpenGL.GL import *
from PyQt6.QtGui import QMatrix4x4, QQuaternion, QVector3D

class DebugDrawer(b2Draw):

    def __init__(self, program, worldScale):
        super().__init__()
        self.program = program
        self.WORLD_SCALE = worldScale
        self.mvpMatrixLocation = self.program.uniformLocation("uMvpMatrix")
        self.colorLocation = self.program.uniformLocation("uColor")
        self.modelMatrix = QMatrix4x4()
        self.projMatrix = None
        self.viewMatrix = None
        self.projViewMatrix = None
        self.color = None
        self.lineWidth = 4

    def DrawSolidPolygon(self, vertexes, color):
        print("Polygon. Begin")
        print(vertexes[0][0] * 30, vertexes[0][1] * 30)
        print(vertexes[1][0] * 30, vertexes[1][1] * 30)
        print(vertexes[2][0] * 30, vertexes[2][1] * 30)
        print(vertexes[3][0] * 30, vertexes[3][1] * 30)
        print("Polygon. End")

        # My custom drawing with OpenGL

    def DrawPolygon(self, vertexes, color):
        pass
    def DrawSegment(self, p1, p2, color):
        pass
    def DrawPoint(self, p, size, color):
        pass
    def DrawCircle(self, center, radius, color, drawwidth=1):
        pass
    def DrawSolidCircle(self, center, radius, axis, color):
        pass
    def DrawTransform(self, xf):
        pass
Lusito commented 1 year ago

I usually use 2 canvases. One for drawing the game and one for the debug draw.

But if that doesn't work for you, take a look at the implementation, it's very little code. Feel free to write your own implementation of the b2Draw interface.

I understand, that you'd ideally only want to implement the drawsolidpolygon method, but that would require me to actually calculate all the vertices. The canvas implementation does not need this and is much simpler this way.

Lusito commented 1 year ago

Feel free to create a PR with necessary changes.

8Observer8 commented 1 year ago

But if that doesn't work for you, take a look at the implementation, it's very little code. Feel free to write your own implementation of the b2Draw interface.

It's exactly what I need! It works! Now I can draw colliders on one canvas with pure WebGL 1.0. Thanks very much!

npm i -g browserify parcel typescript uglify-js npm i @box2d/core gl-matrix npm i @types/gl-matrix

src/debug-drawer.ts

import  { b2Draw, b2Transform, RGBA, XY } from "@box2d/core";

// This class implements debug drawing callbacks that are invoked inside b2World::Step
export default class DebugDrawer implements b2Draw {
    private readonly gl: WebGLRenderingContext;

    public constructor(gl: WebGLRenderingContext) {
        this.gl = gl;
    }

    DrawSolidPolygon(vertices: XY[], vertexCount: number, color: RGBA): void {
        console.log(vertices);
        console.log(color);
    }

    PushTransform(xf: b2Transform): void {}
    PopTransform(xf: b2Transform): void {}
    DrawPolygon(vertices: XY[], vertexCount: number, color: RGBA): void {}
    DrawCircle(center: XY, radius: number, color: RGBA): void {}
    DrawSolidCircle(center: XY, radius: number, axis: XY, color: RGBA): void {}
    DrawSegment(p1: XY, p2: XY, color: RGBA): void {}
    DrawTransform(xf: b2Transform): void {}
    DrawPoint(p: XY, size: number, color: RGBA): void {}
}

src/main.ts

import { b2BodyType, b2PolygonShape, b2World, DrawShapes } from "@box2d/core";
import DebugDrawer from "./debug-drawer";
import { gl, initWebGLContext } from "./webgl-context";

const world = b2World.Create({ x: 0, y: 9.8 });
const WORLD_SCALE = 30;

let debugDrawer;

function gameLoop() {
    requestAnimationFrame(gameLoop);
    world.Step(0.016, { velocityIterations: 8, positionIterations: 3 });

    gl.clear(gl.COLOR_BUFFER_BIT); // Clear canvas
    DrawShapes(debugDrawer, world); // Draw colliders
}

async function init() {
    if (!initWebGLContext("renderCanvas")) return;

    debugDrawer = new DebugDrawer(gl);

    const groundShape = new b2PolygonShape();
    groundShape.SetAsBox(100 / WORLD_SCALE, 10 / WORLD_SCALE);
    const groundBody = world.CreateBody({ type: b2BodyType });
    groundBody.CreateFixture({ shape: groundShape });
    // console.log(groundBody);

    gameLoop();
}

init()

src/webgl-context.ts

export let gl: WebGLRenderingContext;

export function initWebGLContext(canvasName)
{
    const canvas = document.getElementById(canvasName);
    if (canvas === null)
    {
        console.log(`Failed to get a canvas element with the name "${canvasName}"`);
        return false;
    }
    gl = (canvas as HTMLCanvasElement).getContext("webgl", { alpha: false, premultipliedAlpha: false }) as WebGLRenderingContext;

    return true;
}

public/index.html

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Example</title>
</head>

<body>
    <canvas id="renderCanvas" width="500" height="500"></canvas>

    <script type="module" src="js/bundle.js"></script>
</body>

</html>

package.json

{
  "name": "debug-drawer-box2dcore-ts",
  "version": "1.0.0",
  "description": "",
  "main": "public/js/bundle.js",
  "targets": {
    "main": {
       "includeNodeModules": true
    }
  },
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "del_files": "del /f /q /s .\\public\\js\\*.*",
    "dev": "npm run del_files && parcel watch src/main.ts",
    "compile": "tsc -p tsconfigs/tsconfig.release.json",
    "bundle": "browserify public/js/main.js -o public/js/bundle.js",
    "uglify": "uglifyjs public/js/bundle.js -o public/js/bundle.js",
    "release": "npm run compile && npm run bundle && npm run uglify"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "@box2d/core": "^0.10.0",
    "gl-matrix": "^3.4.3"
  },
  "devDependencies": {
    "@types/gl-matrix": "^3.2.0"
  }
}

tsconfigs/tsconfig.json

{
    "compilerOptions": {
        "target": "ES5",
        "outDir": "../public/js",
        "sourceMap": false,
        "types": [
            "node"
        ],
        "allowSyntheticDefaultImports": true,
        "esModuleInterop": true,
        "skipLibCheck": true
    },
    "include": [
        "../src/**/*.ts"
    ],
    "exclude": [
        "node_modules"
    ]
}
8Observer8 commented 1 year ago

I'm trying to build the above example for release using the npm run release command from package.json, but I'm getting the following errors:

E:_Projects\Physics\box2d-core\debug-drawer-box2dcore-webgl-ts>npm run release

debug-drawer-box2dcore-webgl-ts@1.0.0 release E:_Projects\Physics\box2d-core\debug-drawer-box2dcore-webgl-ts npm run compile && npm run bundle && npm run uglify

debug-drawer-box2dcore-webgl-ts@1.0.0 compile E:_Projects\Physics\box2d-core\debug-drawer-box2dcore-webgl-ts tsc -p tsconfigs/tsconfig.release.json

src/main.ts:25:43 - error TS2322: Type 'typeof b2BodyType' is not assignable to type 'b2BodyType'.

25 const groundBody = world.CreateBody({ type: b2BodyType });


  ../../../node_modules/@box2d/core/dist/dynamics/b2_body.d.ts:27:5
    27     type?: b2BodyType;
The expected type comes from property 'type' which is declared here on type 'b2BodyDef'

Found 1 error in src/main.ts:25

npm ERR! code ELIFECYCLE npm ERR! errno 2 npm ERR! debug-drawer-box2dcore-webgl-ts@1.0.0 compile: tsc -p tsconfigs/tsconfig.release.json npm ERR! Exit status 2 npm ERR! npm ERR! Failed at the debug-drawer-box2dcore-webgl-ts@1.0.0 compile script. npm ERR! This is probably not a problem with npm. There is likely additional logging output above. npm WARN Local package.json exists, but node_modules missing, did you mean to install?

npm ERR! A complete log of this run can be found in: npm ERR! C:\Users\8Observer8\AppData\Roaming\npm-cache_logs\2023-01-19T02_13_40_122Z-debug.log npm ERR! code ELIFECYCLE npm ERR! errno 2 npm ERR! debug-drawer-box2dcore-webgl-ts@1.0.0 release: npm run compile && npm run bundle && npm run uglify npm ERR! Exit status 2 npm ERR! npm ERR! Failed at the debug-drawer-box2dcore-webgl-ts@1.0.0 release script. npm ERR! This is probably not a problem with npm. There is likely additional logging output above. npm WARN Local package.json exists, but node_modules missing, did you mean to install?

npm ERR! A complete log of this run can be found in: npm ERR! C:\Users\8Observer8\AppData\Roaming\npm-cache_logs\2023-01-19T02_13_40_555Z-debug.log

8Observer8 commented 1 year ago

It is something with b2BodyType: error TS2322: Type 'typeof b2BodyType' is not assignable to type 'b2BodyType'.

8Observer8 commented 1 year ago

@Lusito, can I use @box2d/core with JavaScript?

8Observer8 commented 1 year ago

box2d-wasm has the b2Draw class with JavaScript and ES6 module support.

Official example: https://github.com/Birch-san/box2d-wasm/tree/master/demo/modern/public

My example:

<!DOCTYPE html>

<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>

<body>
    <!-- Since import maps are not yet supported by all browsers, it is
        necessary to add the polyfill es-module-shims.js
        Source: https://threejs.org/docs/index.html#manual/en/introduction/Installation
    -->
    <script async src="https://unpkg.com/es-module-shims@1.3.6/dist/es-module-shims.js"></script>

    <script type="importmap">
        {
            "imports": {
                "gl-matrix": "https://cdn.skypack.dev/gl-matrix@3.4.3",
                "box2d": "https://8observer8.github.io/lib/box2d-wasm-2.4.1/box2d-wasm-2.4.1.min.js"
            }
        }
    </script>

    <script type="module">
        import Box2DLib from "box2d";
        let box2d;

        function initBox2D() {
            return new Promise(resolve => {
                Box2DLib().then((re) => {
                    box2d = re;
                    resolve();
                });
            });
        }

        async function main() {
            await initBox2D();

            const {
                b2Draw: { e_shapeBit },
                b2Vec2,
                JSDraw
            } = box2d;

            const vec = new b2Vec2(1, 2);
            console.log(`vec = ${vec.x}, ${vec.y}`);

            function makeDebugDrawer() {
                const debugDrawer = Object.assign(new JSDraw(), {
                    DrawSegment(vert1_p, vert2_p, color_p) {},
                    DrawPolygon(vertices_p, vertexCount, color_p) {},
                    DrawSolidPolygon(vertices_p, vertexCount, color_p) {},
                    DrawCircle(center_p, radius, color_p) {},
                    DrawSolidCircle(center_p, radius, axis_p, color_p) {},
                    DrawTransform(transform_p) {},
                    DrawPoint(vertex_p, sizeMetres, color_p) {}
                });
                debugDrawer.SetFlags(e_shapeBit);
                return debugDrawer;
            }

            const debudDrawer = makeDebugDrawer();
        }

        main();
    </script>
</body>

</html>
Lusito commented 1 year ago

None of these issues are related to this topic. Ideally, they should be separate issues.

Aside from that, quickly:

8Observer8 commented 1 year ago

So yes, you can use it with just javascript

The challenge is to replace Planck.js with @box2d/core in this example: https://plnkr.co/edit/nFHRxUFhcKhL4Jpd

<!DOCTYPE html>

<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>

<body>
    <canvas id="renderCanvas" width="256" height="256"></canvas>

    <script type="importmap">
        {
            "imports": {
                "gl-matrix": "https://cdn.skypack.dev/gl-matrix@3.4.3",
                "planck-js": "https://cdn.skypack.dev/planck-js@0.3.29"
            }
        }
    </script>

    <script type="module">
        import { mat4, vec3 } from "gl-matrix";
        import * as pl from "planck-js";

        const gravity = pl.Vec2(0, 0);
        const world = pl.World(gravity);

        const output = document.createElement("div");
        output.innerText = `gravity = ${world.getGravity()}`;
        document.body.appendChild(output);

        const gl = document.getElementById("renderCanvas").getContext("webgl2");
        gl.clearColor(0.2, 0.2, 0.2, 1);
        gl.clear(gl.COLOR_BUFFER_BIT);
    </script>
</body>

</html>
8Observer8 commented 1 year ago

Could you create a CDN link?

Lusito commented 1 year ago

Not sure what the issue is for creating the CDN link. Just replace package name and version and you have the new CDN link.

<!DOCTYPE html>

<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Document</title>
  </head>

  <body>
    <canvas id="renderCanvas" width="256" height="256"></canvas>

    <script type="importmap">
      {
        "imports": {
          "gl-matrix": "https://cdn.skypack.dev/gl-matrix@3.4.3",
          "@box2d/core": "https://cdn.skypack.dev/@box2d/core@0.10.0"
        }
      }
    </script>

    <script type="module">
      import { mat4, vec3 } from "gl-matrix";
      import { b2World, b2Vec2 } from "@box2d/core";

      const gravity = new b2Vec2(0, 0);
      const world = b2World.Create(gravity);

      const output = document.createElement("div");
      output.innerText = `gravity = ${JSON.stringify(world.GetGravity())}`;
      document.body.appendChild(output);

      const gl = document.getElementById("renderCanvas").getContext("webgl2");
      gl.clearColor(0.2, 0.2, 0.2, 1);
      gl.clear(gl.COLOR_BUFFER_BIT);
    </script>
  </body>
</html>
8Observer8 commented 1 year ago

Wow, it's cool! Many thanks!

Lusito commented 1 year ago

About your type issue:

// Wrong:
const groundBody = world.CreateBody({ type: b2BodyType });
// Correct:
const groundBody = world.CreateBody({
    type: b2BodyType.b2_staticBody // or b2_kinematicBody or b2_dynamicBody, depending on your use-case
});
8Observer8 commented 1 year ago

Thank you very much! It's awesome! I tried to build my example and it works!

image

image

8Observer8 commented 1 year ago

This line of code should be added to index.html:

    <!-- Since import maps are not yet supported by all browsers, it is
        necessary to add the polyfill es-module-shims.js
        Source: https://threejs.org/docs/index.html#manual/en/introduction/Installation
    -->
    <script async src="https://unpkg.com/es-module-shims@1.3.6/dist/es-module-shims.js"></script>
8Observer8 commented 1 year ago

[Solved] The requested module '@box2d/core' does not provide an export named 'b2Draw'

8Observer8 commented 1 year ago

I have the problem with JavaScript code: DrawSolidPolygon gets wrong collider position