Helper to debug BabylonJS code

Hi there!
I don’t know guys how you are debugging your realtime code but I believe sooner or later everyone will get sick of console.log :smiley: I think everyone had an idea to display debugging information directly onto the canvas using BabylonJS GUI and so I did. This is a small utitlity class which allows you to easily display multiple information on any object you provide directly on the rendering canvas.

This is how it looks in action:

The Playground:
https://playground.babylonjs.com/#UNL3H0#4

You create an instance by simply calling:

        const c3d = VisualConsole
        c3d.create(engine, scene)

Or if you already have a full screen gui texture created pass it as the third parameter.

Than you can simply

        c3d.log('Camera alpha - bound', camera, 'alpha')

and your camera alpha will be displayed on every frame in a debug frame. The first one on the picture.

This logs the position of the box every frame and since it is a Vector3, you get x, y, z in the debug frame. It is the second frame.

        c3d.log('Box position - bound', box, 'position')

You can observe a Vector3 object itself:

        const v = new BABYLON.Vector3(0, 0, 0)
        c3d.log('Vector - bound', v)

It can deal with colors as well. The log60 is a convenience method for updating the value every 60 FPS. Check the source code for more methods like this.

        const c = new BABYLON.Color3(1, 0, 0)
        c3d.log60('Color - every 60 frame - bound', c)

What if you want the debug frame to follow your mesh so you have better visual feedback? Just use logf (floating).

        c3d.logf('Mesh - highlite at x > 0.4', box)

You want to be notified when a certain value is hit? Use method chaining and the highlite method:

        c3d.logf('Mesh - highlite at x > 0.4', box).highlite((value: BABYLON.AbstractMesh) => value.position.x > 0.4)

Do you want to start the debugger when a condition is met? Use the debug method. This will not open the Developer tools for you, it is just a plain debugger statement being hit in the code so open the Developer tools before testing this one:

        c3d.log('Mesh - debug at x > 1', box).debug((value: BABYLON.AbstractMesh) => value.position.x > 1)

You have to call these methods only once.

If you want to manually update the values in the debugging frames, you can use log every frame. The first parameter must remain the same, otherwise a new frame will be created:

scene.onBeforeRenderObservable.add(() => {  
            c3d.log('Random', Math.random())
            c3d.log('Camera alpha - manual update', camera.alpha.toString())
            c3d.log('Camera beta - manual update', camera.beta.toString())
        })

The buttons allows you to toggle the location of the panel and D opens the BabylonJS Scene explorer and Inspector.

You can introduce new debugging frame types by adding in the createMappings methods. The green frames are wider because the default value of the width is overridden in the mappings.

        entityTypeMappings.set(LoggedEntityType.Vector3, {
            color: new BABYLON.Color3(0.0, 0.7, 0.1),
            drawFunction: entity => this._drawVector3(entity),
            width: 200
        })

and implement a coresponding method like for example:

    private _drawFloat(entity: LoggedEntity) {
        if (entity.textInputs) {
            entity.textInputs[0].text = VisualConsole._getObject<number>(entity).toString()
        }
    }

    private _drawBoolean(entity: LoggedEntity) {
        if (entity.textInputs) {
            entity.textInputs[0].text = VisualConsole._getObject<boolean>(entity).toString()
        }
    }

    private _drawMesh(entity: LoggedEntity) {
        if (entity.textInputs) {
            const obj = VisualConsole._getObject<BABYLON.AbstractMesh>(entity)
            entity.textInputs[0].text = `${obj.position.x.toFixed(FLOAT_PRECISION)}, ${obj.position.y.toFixed(FLOAT_PRECISION)}, ${obj.position.z.toFixed(
                FLOAT_PRECISION
            )} `
            entity.textInputs[1].text = `${obj.rotation.x.toFixed(FLOAT_PRECISION)}, ${obj.rotation.y.toFixed(FLOAT_PRECISION)}, ${obj.rotation.z.toFixed(
                FLOAT_PRECISION
            )} `
            entity.textInputs[2].text = `${obj.scaling.x.toFixed(FLOAT_PRECISION)}, ${obj.scaling.y.toFixed(FLOAT_PRECISION)}, ${obj.scaling.z.toFixed(
                FLOAT_PRECISION
            )} `
        }
    }

Please note, all the values are displayed in text inputs so you can copy them out easily.

Some values to tweak:

const FLOAT_PRECISION = 4

const MAIN_PANEL_WIDTH = 0.4

const DEFAULT_PANEL_WIDTH = 160
const DEFAULT_PANEL_COLOR = Color3.Gray()

const DEBUGGER_HIT_PANEL_COLOR = Color3.Red()
const HIGHLITE_HIT_PANEL_COLOR = Color3.Purple()

const MESH_BADGE_LINK_X_OFFSET = -200
const MESH_BADGE_LINK_Y_OFFSET = -200

const MAIN_PANEL_ALPHA = 0.4
const LINES_ALPHA = 0.4

This small class contains only the basics I need, I already started to create a version which stores the history of the observed objects and one can display the last X positions of an object at once, so for example the cube is rendered X times leaving a trail every frame. I also tried to move this to a second scene, override the original scene’s renderloop allowing me to pause the scene or go backwards but this topic is so complicated that it works only in basic scenes which are strictly using only observables. I will be happy to share it with you when the time comes! :vulcan_salute:

Hopefully guys you will find this usefull!

Thanks!
R.

7 Likes

This is pretty cool and beautifull :slight_smile:

Thanks @sebavan !

Did you make it in Babylon UI or is it html?

I have one of these as well that I made so I’m wondering how you went about it. We needed ours to work I’m VR so we used the UI system.

Mines reactive based, yours seems to be active poling which I can see beneficial as well. Do you have the ability to edit the link values as well?

If this is a more refined solution I might point it out to the team for us to use instead.

Hello @Pryme8 !
It renders on an AdvancedDynamicTexture of the BabylonJS GUI, no HTML used so it should play nicely on VR as well.

4 Likes

Back to answer the second part of your question. What do you mean by editing the link values? All values are bound to an object’s property and each debug frame is identified by it’s name which has to be unique. Once they are bound, the values gets updated automatically.

You can manually change the value simply by changing the object property value or manually call every frame the log method with a primitive parameter, string, float or boolean and specifiyng the name of the debug frame you want to update.

1 Like

Ahh ok cool, yeah the only difference is we have a two way link where you can change the values right on the debugger. Might have to check this out it especially if you are maintaining it, that will save me some headache having to maintain yet another component for the Unity Toolkit XD.

@MackeyK24 did you see this bro?

I was thinking of two way data binding already and should be easy to implement. If there will be interest from the community for something like this I could start to take this stuff seriously :vulcan_salute: I have millions of ideas and I bet the others too!

2 Likes

Making it better:

3 Likes