Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Please, add Debug Drawer #152

Open
8Observer8 opened this issue Jul 10, 2022 · 20 comments
Open

Please, add Debug Drawer #152

8Observer8 opened this issue Jul 10, 2022 · 20 comments

Comments

@8Observer8
Copy link

8Observer8 commented Jul 10, 2022

I know cannon-es-debugger exists that uses Three.js but I am using pure WebGL API. It is very complicated to develop without collider drawing:

jill-cannon-es-collider-problem

Please, add generic collider drawing like in Ammo.js with the DebugDrawer class. I am rewriting my project in Ammo.js to see problems with colliders:

jill-colliders-ammojs

jill-colliders-of-obstacle-ammojs

@8Observer8 8Observer8 changed the title Please, add Debug Drawer without Three.js Please, add Debug Drawer Jul 10, 2022
@Dannie226
Copy link

Here is a quick question. Why aren't you using threejs? Why are you writing the 3D rendering from scratch when a library like threejs exists and does it for you? I am certain that whatever you are doing with the WebGL you wrote, you could use it with threejs with a bit of refactoring.

@8Observer8
Copy link
Author

8Observer8 commented Jul 31, 2022

I love to create interactive animations from scratch and use computer graphics in pure WebGL and OpenGL. When you create it from scratch again and again you can automate it. Frameworks has many things that I do not understand and I do not need. Sometimes I do not use a render loop. I do not need PBR and skinning. Skinning and PBR is hard for mobile browser games and laptops. I use skeleton animation above for RE1 but without skinning. I extracted original animation Jill's parts and animations using RE1MVP:

https://www.youtube.com/watch?v=zN0iA0b2k7s

IMAGE ALT TEXT HERE

I wrote a script in Blender Python API to load Jill's parts and animations. All Jill's parts are separated objects that does not require skinning:

840a6c3f1fb063fd6dc02826db7db454bb52a830

It is very important for me to have Desktop and Mobile versions written in Qt6 C++ and OpenGL ES. At first I write my applications in Python and PyQt6 + OpenGL. Python is very good language for prototyping. I often write Desktop applications in PyQt6 because they do not require to build EXE, copy Qt DLL's or run http-server (and open browser). I can run PyQt6 scripts by double click and scripts do not require a big space on my laptop. I use Python instead of calculator for a long time. It is easy to rewrite from PyQt6/OpenGL to Qt6 C++ (for creating EXE or Android APK). I do not like PyInstaller or Electron because they create a very big EXE and require a lot of time to study them.

WebGL and QtOpenGL allows to make 2D and 3D games with Physics (with Box2D/Planck.js and with BulletPhysics/Ammo.js/CannonES) with multiplayer (with client and server side physics loops for client predictions) in one workspace. I just need simple graphics and understanding how it works. I do not want to study Pixi,js, Mellon.js or Phaser for 2D games. I know how to make sprite animations and how to use Free Texture Packer and Tiled Map Editor (Tiled | Flexible level editor). I already know enough from WebGL and QtOpenGL to make 2D/3D games and non game interactive applications with 3D graphics and GUI (with Handlebars/Bootstrap and QtGui).

You cannot change my opinion. Please, do not try to do it. Let's do not spend our time. Thanks.

@Dannie226
Copy link

I wasn’t going to try to convince you to use three js. I was just kind of wondering why you aren’t. Three js does have a wide variety of things it can do, but there are some things it can’t. If you have hit one of those, perfectly fine by me. Would you be looking for something like Ammojs’ debug drawer where you just create a massive array for line points and colors, and it draws everything for you?

@8Observer8
Copy link
Author

Would you be looking for something like Ammojs’ debug drawer where you just create a massive array for line points and colors, and it draws everything for you?

I used the Ammo.js DebugDrawer class in my gif-animation in the first post --> demo

69c378c1c1c54f3889198db476b5dab4 jill-movement

Ammo.js is very good because I can write Qt C++ Desktop/Mobile clients with Bullet Physics for multiplayer with Ammo.js on server side. I use the free Heroku server with WebSockets. But Cannon-ES is better for lightweight apps. My laptop works better with Cannon-ES. I will just draw colliders using cube and sphere objects. I am writing a demo based on Battle City but in 3D in Python, PyQt6, OpenGL3 and Bullet Physics (from Panda3D module) It is a demo to start: https://github.com/8Observer8/falling-collada-cube-bullet-physics-opengl33-pyqt6

falling-tank-bullet-opengl3-pyqt6

@Dannie226
Copy link

Ok... that didn’t answer my question. So, how do you want the debug drawer to function. Like, obviously, it can’t use three.js, so it has to have some other way to interact with your environment. How do you want that to look? Do you want it to just take a massive float32 array like the ammojs debug drawer would, or do you want to do something else. I am trying to gauge what you are asking for so that way I might be able to accommodate it.

@8Observer8
Copy link
Author

I just need one drawLine function that I will override and that I will use to draw segments, like I do with Ammo.js in my DebugDrawer class:

debug-drawer.ts

import Ammojs from "ammojs-typed";
import { Ammo, physicsWorld } from "./ammojs-init";
import { mat4, quat, vec3 } from "gl-matrix";
import ColliderEdge from "./collider-edge";

export default class DebugDrawer
{
    public projMatrix: mat4;
    public viewMatrix: mat4;

    private _edgeObject: ColliderEdge;
    private _debugDrawer: Ammojs.DebugDrawer;
    private _unitX = vec3.fromValues(1, 0, 0);
    private _tempVec = vec3.create();
    private _projViewMatrix = mat4.create();

    public constructor(edgeObject: ColliderEdge)
    {
        this._edgeObject = edgeObject;
        this._debugDrawer = new Ammo.DebugDrawer();

        this._debugDrawer.drawLine = (from: any, to: any, color: any): void =>
        {
            const heap = Ammo.HEAPF32;
            const r = heap[(parseInt(color) + 0) / 4];
            const g = heap[(parseInt(color) + 4) / 4];
            const b = heap[(parseInt(color) + 8) / 4];

            const fromX = heap[(parseInt(from) + 0) / 4];
            const fromY = heap[(parseInt(from) + 4) / 4];
            const fromZ = heap[(parseInt(from) + 8) / 4];

            const toX = heap[(parseInt(to) + 0) / 4];
            const toY = heap[(parseInt(to) + 4) / 4];
            const toZ = heap[(parseInt(to) + 8) / 4];

            let centerX: number;
            let centerY: number;
            let centerZ: number;

            if (fromX > toX)
            {
                centerX = toX + Math.abs(fromX - toX) / 2;
            }
            else
            {
                centerX = fromX + Math.abs(toX - fromX) / 2;
            }

            if (fromY > toY)
            {
                centerY = toY + Math.abs(fromY - toY) / 2;
            }
            else
            {
                centerY = fromY + Math.abs(toY - fromY) / 2;
            }

            if (fromZ > toZ)
            {
                centerZ = toZ + Math.abs(fromZ - toZ) / 2;
            }
            else
            {
                centerZ = fromZ + Math.abs(toZ - fromZ) / 2;
            }

            this._tempVec[0] = toX - fromX;
            this._tempVec[1] = toY - fromY;
            this._tempVec[2] = toZ - fromZ;
            const length = vec3.length(this._tempVec);
            vec3.normalize(this._tempVec, this._tempVec);

            quat.rotationTo(this._edgeObject.rotation, this._unitX, this._tempVec);
            this._edgeObject.scale[0] = length;
            this._edgeObject.scale[1] = 0.1;
            this._edgeObject.scale[2] = 0.1;
            this._edgeObject.position[0] = centerX;
            this._edgeObject.position[1] = centerY;
            this._edgeObject.position[2] = centerZ;

            mat4.mul(this._projViewMatrix, this.projMatrix, this.viewMatrix);
            this._edgeObject.draw(this._projViewMatrix);
        };

        this._debugDrawer.setDebugMode = (debugMode: number) => { }

        let debugMode = 1; // 0 - 0ff, 1 - on
        this._debugDrawer.getDebugMode = (): number =>
        {
            return debugMode;
        }

        this._debugDrawer.drawContactPoint = (pointOnB: Ammojs.btVector3, normalOnB: Ammojs.btVector3,
            distance: number, lifeTime: number, color: Ammojs.btVector3): void => { }

        this._debugDrawer.reportErrorWarning = (warningString: string): void => { };
        this._debugDrawer.draw3dText = (location: Ammojs.btVector3, textString: string): void => { };

        this._debugDrawer.setDebugMode(0);
        physicsWorld.setDebugDrawer(this._debugDrawer);
    }
}

@8Observer8
Copy link
Author

8Observer8 commented Aug 1, 2022

    public constructor(edgeObject: ColliderEdge)
    {
        this._edgeObject = edgeObject;

edgeObject is a cube with the size 1x1x1 which I load from .dae

@Dannie226
Copy link

So... all you want is some class, with a draw line function that you will override. I should be able to do that.

@Dannie226
Copy link

Now, obviously this isn't complete, I will add cylinder and convex poly later, but I just wish to know, is this near what you are looking for. In the gif you posted, there were only boxes and spheres, so this should work for that, but, idk. I only did minimal testing to ensure that stuff rendered and updated. Anyway, if this is what you are looking for, I will continue to work on it.

import * as CANNON from "cannon-es";
import * as THREE from "three";

type ShapeInfo = {
    points:[number, number, number][],
    indices:[number, number][]
};

export class CannonDebugger {
    private physicsWorld:CANNON.World;
    public drawLine:(from:CANNON.Vec3, to:CANNON.Vec3, color:THREE.Color) => void;
    private sphereInfo:ShapeInfo;
    constructor(world:CANNON.World){
        this.physicsWorld = world;
        this.drawLine = () => {};
        this.sphereInfo = {
            points:[
                [0, 1, 0]
            ],
            indices:[
               [ 0, 1],
               [ 0, 2],
               [ 0, 3],
               [ 0, 4],
               [ 0, 5],
               [ 0, 6],
               [ 0, 7],
               [ 0, 8],
               [ 0, 9],
               [ 0,10],
               [ 0,11],
               [ 0,12],
               [ 1, 2],
               [ 2, 3],
               [ 3, 4],
               [ 4, 5],
               [ 5, 6],
               [ 6, 7],
               [ 7, 8],
               [ 8, 9],
               [ 9,10],
               [10,11],
               [11,12],
               [12, 1],
               [ 1,13],
               [ 2,14],
               [ 3,15],
               [ 4,16],
               [ 5,17],
               [ 6,18],
               [ 7,19],
               [ 8,20],
               [ 9,21],
               [10,22],
               [11,23],
               [12,24],
               [13,14],
               [14,15],
               [15,16],
               [16,17],
               [17,18],
               [18,19],
               [19,20],
               [20,21],
               [21,22],
               [22,23],
               [23,24],
               [24,13],
               [13,25],
               [14,26],
               [15,27],
               [16,28],
               [17,29],
               [18,30],
               [19,31],
               [20,32],
               [21,33],
               [22,34],
               [23,35],
               [24,36],
               [25,26],
               [26,27],
               [27,28],
               [28,29],
               [29,30],
               [30,31],
               [31,32],
               [32,33],
               [33,34],
               [34,35],
               [35,36],
               [36,25],
               [25,37],
               [26,38],
               [27,39],
               [28,40],
               [29,41],
               [30,42],
               [31,43],
               [32,44],
               [33,45],
               [34,46],
               [35,47],
               [36,48],
               [37,38],
               [38,39],
               [39,40],
               [40,41],
               [41,42],
               [42,43],
               [43,44],
               [44,45],
               [45,46],
               [46,47],
               [47,48],
               [48,37],
               [37,49],
               [38,50],
               [39,51],
               [40,52],
               [41,53],
               [42,54],
               [43,55],
               [44,56],
               [45,57],
               [46,58],
               [47,59],
               [48,60],
               [49,50],
               [50,51],
               [51,52],
               [52,53],
               [53,54],
               [54,55],
               [55,56],
               [56,57],
               [57,58],
               [58,59],
               [59,60],
               [60,49],
               [49,61],
               [50,61],
               [51,61],
               [52,61],
               [53,61],
               [54,61],
               [55,61],
               [56,61],
               [57,61],
               [58,61],
               [59,61],
               [60,61]
           ]
        };
        for(let i = 1; i < 6; i++){
            const phi = i * Math.PI / 6, sp = Math.sin(phi);
            for(let j = 0; j < 12; j++){
                const theta = j * Math.PI / 6;
                this.sphereInfo.points.push([Math.sin(theta) * sp, Math.cos(phi), Math.cos(theta) * sp]);
            }
        }
        this.sphereInfo.points.push([0, -1, 0]);
    }

    private boxInfo:ShapeInfo = {
        points:[
            [ 1, 1, 1],
            [ 1, 1,-1],
            [ 1,-1, 1],
            [ 1,-1,-1],
            [-1, 1, 1],
            [-1, 1,-1],
            [-1,-1, 1],
            [-1,-1,-1]
        ],
        indices:[
            [0, 1],
            [1, 3],
            [3, 2],
            [2, 0],
            [4, 5],
            [5, 7],
            [7, 6],
            [6, 4],
            [0, 4],
            [1, 5],
            [2, 6],
            [3, 7]
        ]
    };

    private from = new CANNON.Vec3;
    private to = new CANNON.Vec3;
    private color = new THREE.Color(0x0000FF);

    private drawBox(body:CANNON.Body){
        const shape = body.shapes[0] as CANNON.Box;

        const info = this.boxInfo;
        for(const index of info.indices){
            let a = index[0], b = index[1];
            this.from.set(...info.points[a]);
            this.to.set(...info.points[b]);
            this.from.vmul(shape.halfExtents, this.from);
            this.to.vmul(shape.halfExtents, this.to);
            body.quaternion.vmult(this.from, this.from);
            body.quaternion.vmult(this.to, this.to);
            this.from.vadd(body.position, this.from);
            this.to.vadd(body.position, this.to);

            this.drawLine(this.from, this.to, this.color);
        }
    }

    private drawSphere(body:CANNON.Body){
        const shape = body.shapes[0] as CANNON.Sphere;

        const info = this.sphereInfo;
        for(const index of info.indices){
            let a = index[0], b = index[1];
            this.from.set(...info.points[a]);
            this.to.set(...info.points[b]);
            this.from.scale(shape.radius, this.from);
            this.to.scale(shape.radius, this.to);
            body.quaternion.vmult(this.from, this.from);
            body.quaternion.vmult(this.to, this.to);
            this.from.vadd(body.position, this.from);
            this.to.vadd(body.position, this.to);

            this.drawLine(this.from, this.to, this.color);
        }
    }

    public update(){
        for(const body of this.physicsWorld.bodies){
            switch(body.shapes[0].type){
                case CANNON.SHAPE_TYPES.BOX:
                    this.drawBox(body);
                    break;

                case CANNON.SHAPE_TYPES.SPHERE:
                    this.drawSphere(body);
                    break;
            }
        }
    }
}

@8Observer8
Copy link
Author

8Observer8 commented Aug 2, 2022

I just need to know the beginning and end of each segment for collider like it made in Ammo.js. I can draw the segments by myself using a cube with lightless fragment shader like here:

image

Every collider segment on the screenshot above is a green cube that I transformed using only from and to points for each segments. What if engine developers (Babylon.js or PlayCanvas) will release drawing of collider segments in they engines? Should the developer of Babylon.js install Three.js? It will be sad if Cannon-ES is for Three.js only. Ammo.js (and Bullet Physics) developers made an universal method: we give you the from and to points of segment and you can draw a segment as you like.

I already made what you want: drawing a whole collider with box or sphere. It is my temporary solution (I have a problem with size of ground collider):

tank-colliders-with-ground-problem

@Dannie226
Copy link

The code above doesn’t draw the entire collider. It uses the draw line function that you override to draw a bunch of lines. And it is also for cannon js... not ammo js. I thought that that was what you wanted

@8Observer8
Copy link
Author

Could you remove Three.js dependency?

@Dannie226
Copy link

I don’t think I even use three anywhere. Just open it up in an editor, and see whether it is used. It shouldn’t be.

@Dannie226
Copy link

Oh. Color. That can be easily changed to a number triple or cannon.vec3, or even removed. You can decide what is best for your scenario.

@8Observer8
Copy link
Author

Thank you very much! It looks like it will work. I will write some simple examples and I will post links on playground here for beginners. If I have problems I will write about them here.

@Dannie226
Copy link

I would still need to add like, convex polyhedron rendering, cylinder rendering, and trimesh rendering, but, I will get that done and hopefully get back to you.

@8Observer8
Copy link
Author

8Observer8 commented Aug 4, 2022

Drawing of a box and sphere colliders work! Thanks very much! I will make interesting examples and I will test your code.

https://codesandbox.io/s/cannon-es-box-sphere-debugger-webgl-ts-9co3qr (toggle the preview to see the result: Ctrl+Shift+D)

Source: https://github.com/8Observer8/cannon-es-box-sphere-debugger-webgl-ts

  • Install globally: npm i -g typescipt parcel browserify uglify-js http-server
  • Install locally: npm i
  • Run parcel for debug mode: npm run dev
  • Run http-server without caching: http-server -c-1
  • Go to browser and type: localhost:8080
  • Run this command to make the release bundle: npm run release

image image

@8Observer8
Copy link
Author

It will be good if you add axes to understand orientation like it is in Bullet Physics and Ammo.js:

CollidersIsDrawn

@8Observer8
Copy link
Author

The custom debug drawer is needed because I could draw cuboids instead of lines like in cannon-es-debugger. It's hard to see these lines because they are too thin:

image

@8Observer8
Copy link
Author

8Observer8 commented Jun 16, 2023

You can get ideas from OimoPhysics:

export default class DebugDrawer extends OIMO.DebugDraw {
    line(from, to, color) {
    }
}

I use this class to draw cuboids instead of lines with pure WebGL:

debug-drawer.js

import { mat4, quat, vec3 } from "gl-matrix";

export default class DebugDrawer extends OIMO.DebugDraw {
    constructor(edge) {
        super();
        this.edge = edge;
        this.projViewMatrix = null;

        this.centerX = 0;
        this.centerY = 0;
        this.centerZ = 0;

        this.length = 0;
        this.vec = vec3.create();
        this.x = 0;
        this.y = 0;
        this.z = 0;
        this.unitX = vec3.fromValues(1, 0, 0);
    }

    point(v, color) {
        console.log("point");
    }

    triangle(v1, v2, v3, n1, n2, n3, color) {
        console.log("triangle");
    }

    line(from, to, color) {
        this.edge.color[0] = color.x;
        this.edge.color[1] = color.y;
        this.edge.color[2] = color.z;

        if (from.x > to.x) {
            this.centerX = to.x + Math.abs(from.x - to.x) / 2;
        } else {
            this.centerX = from.x + Math.abs(to.x - from.x) / 2;
        }

        if (from.y > to.y) {
            this.centerY = to.y + Math.abs(from.y - to.y) / 2;
        } else {
            this.centerY = from.y + Math.abs(to.y - from.y) / 2;
        }

        if (from.z > to.z) {
            this.centerZ = to.z + Math.abs(from.z - to.z) / 2;
        } else {
            this.centerZ = from.z + Math.abs(to.z - from.z) / 2;
        }

        this.vec[0] = to.x - from.x;
        this.vec[1] = to.y - from.y;
        this.vec[2] = to.z - from.z;
        this.length = vec3.length(this.vec);

        vec3.normalize(this.vec, this.vec);
        quat.rotationTo(this.edge.rotation, this.unitX, this.vec);

        this.edge.scale = [this.length, 0.2, 0.2];
        this.edge.position = [this.centerX, this.centerY, this.centerZ];
        this.edge.draw(this.projViewMatrix);
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants