/core

Modularizing the @BabylonJS applications

Primary LanguageTypeScriptApache License 2.0Apache-2.0

Modularizing the @BabylonJS applications

Why

  • Don't let your code grow to a mammoth size
    • When it becomes a medium sized project, you have the overhead of maintaining the project because your code starts bloating. How to apply a bandage to that bleeding code?
    • It is a nightmare to scroll that thousand line javascript file where a variable assignment is breaking five other places. The global variables play a magical role in confusing you.
    • The maintainability of the project becomes painful. It is very tough to debug the code at some point of time.
  • It is easier to achieve scalability with modular programming
    • Good authors divide their books into chapters and sections; good programmers divide their programs into modules.
    • A well-designed module aims to lessen the dependencies on parts of the codebase as much as possible, so that it can grow and improve independently

Getting started

You need to setup your environment to generate BabylonJS bundle, you can

createApp

/** app.js **/
import {createApp} from "@babylon-app/core";

createApp({
    name : 'app',
    rootUrl : 'https://models.babylonjs.com/CornellBox/'
    sceneFileName : 'cornellBox.glb',
    canvas : document.getElementById('render-canvas')
});

Will setup anything, download the glb file...but you will get the usual exception : 'No default camera'. Let create the default camera in a component wrote in a dedicated js module file

createComponent

/** activeCamera.js **/
import {createComponent} from "@babylon-app/core";
import {ArcRotateCamera, Vector3} from "@babylonjs/core";

createComponent({
    name : 'activeCamera',
    mounted() {
         let camera = new ArcRotateCamera("arcRotateCamera", Math.PI / 2, 1.6, 7.6, new Vector3(0,1.5,0), this.$scene);
         camera.wheelPrecision = 150;
         camera.attachControl(this.$canvas, true);
    }
});

mounted()

mounted() is a reserved function name, it will be called when the app is ready to render (but before the rendering loop start).

this.$scene

this.$scene represent the current Babylon Scene. With component you don't have to think about if the scene is ready, if the app is launched etc. it is just available anytime and you can call it securely.

this.$canvas, this.$engine

Same principle for the HTMLCanvasElement (this.$canvas) used by the BabylonJS Engine (this.$engine)

import component.js

We need to import this file in app.js to let the app known about this component

/** app.js **/
import {createApp} from "@babylon-app/core";
import './environment.js';

createApp({
    name : 'app',
    rootUrl : 'https://models.babylonjs.com/CornellBox/'
    sceneFileName : 'cornellBox.glb',
    canvas : document.getElementById('render-canvas')
});

The cornell box rendered but its all black because we need to setup an environment, we'll make a component for it

/** environment.js **/
import {createComponent} from "@babylon-app/core";
import { CubeTexture } from "@babylonjs/core";

createComponent({
    name : 'environment',
    mounted() {
         let hdrTexture = new CubeTexture(url, this.$scene);
         hdrTexture.gammaSpace = false;
         this.$scene.environmentTexture = hdrTexture;
    }
});

We want to tweak the environment intensity on material so we will declare a methods in the component

methods

/** environment.js **/
import {createComponent} from "@babylon-app/core";
import { CubeTexture } from "@babylonjs/core";

createComponent({
    name : 'environment',
    methods : {
        setIntensity(intensity) {
            this.$scene.materials.forEach(material => {
                material.environmentIntensity = intensity
            });
        }
    },
    mounted() {
         let hdrTexture = new CubeTexture(url, this.$scene);
         hdrTexture.gammaSpace = false;
         this.$scene.environmentTexture = hdrTexture;
    }
});

$app.$getComponent

Open your browser console :

$app.$getComponent('environment').setIntensity(0.4)

$app is the only global object created by @babylon-core so you can use it to plug your HTML events.

props

/** light.js **/
import {createComponent} from "@babylon-app/core";
import { CubeTexture } from "@babylonjs/core";

createComponent({
    name : 'light',
    props : {
         light : new DirectionalLight('dirLight', new Vector3(0,-1,0), this.$scene)
    },
    methods : {
        on() {
            this.light.intensity = 1;
        },
        off() {
            this.light.intensity = 0;
        }
    }
});

props is a reserved keyword to specify the component properties. Props are available everywhere in the component.

Don't forget to update app.js

/** app.js **/
import {createApp} from "@babylon-app/core";

import './activeCamera.js'
import './light.js';
import './environment.js';
import './shaderBall.js';

createApp({
   
});

Maybe you want to interact with the environment while your are tweaking the light so you could do :

this.$app.$getComponent

/** light.js **/ 
import {createComponent} from "@babylon-app/core";
import { CubeTexture } from "@babylonjs/core";
 
createComponent({
     name : 'light',
     props : {
         light : new DirectionalLight('dirLight', new Vector3(0,-1,0), this.$scene)
     },
     methods : {
         on() {
             this.setIntensity(1);
         },
         off() {
             this.setIntensity(0);
         },
         setIntensity(intensity) {
            this.light.intensity = intensity;
            this.$app.$getComponent('environment').setIntensity(1 - intensity);
         },
     }
});

Component can interact each other.

this.$assets

Import GLTF to the scene with a component :

/** shaderBall.js **/
import { createComponent} from "@babylon-app/core";

createComponent({
    name : 'shaderBall',
    rootUrl : 'https://models.babylonjs.com/',
    sceneFileName : 'shaderBall.glb',
    mounted() {
        console.log("assets downloaded in a BabylonJS AssetContainer : ", this.$assets);
    }
});

Query and Patch the scene graph

this.$queryAll

Available in App and Components :

let meshes = this.$queryAll( { type : 'Mesh' } ) // return all meshes
let filteredMaterials = this.$queryAll( { type : 'Material', name : 'material*'})

In the console

$app.$queryAll({ type : 'Mesh'});

this.$patch

this.$patch({
    selector : {
        type : 'Mesh',
        name : 'mesh.*.000'
    },
    patch : {
        isVisible : true
    }
})

Patch can be stored in a JSON file and imported :

scene.json

{
    "selector" : {
        "type" : "Mesh",
        "name" : "mesh.*.000"
    },
    "patch" : {
        "isVisible" : true
    }
}
/** example.js **/
import { createComponent} from "@babylon-app/core";
import patchScene from '../patches/scene.json';
createComponent({
    name : 'example',
    mounted() {
       this.$patch(patchScene);
    }
});