Blog

Creating an Interactive Web Application

2014-05-08

Blend4Web makes it possible to create both simple and relatively complex interactive rich internet applications. Let's take a look at creating a simple application using the Blend4Web API. In this application we will implement interactions with 3D objects, for example, animating them on mouse click.

Creating a New Application

In order to create a new application one can use the Project Manager which is included in the Blend4Web SDK. It can be launched via the link at the SDK main page. This page is available on the address: http://localhost:6687/ if the local development server is used.

Next we should click the Projects[+new] link.

Then by filling the fields like in an example below we'll specify the name for our new project (for example "simple_app") and tell the Project Manager to create all the resources and files required for a basic yet fully working application. All of them will also be located in a single directory for simplicity. In addition, we can specify the application title which will be displayed in the browser ("Interactive Web Application" for example).

There is nothing to do but click Create to create the application. Then we can view the application main HTML file through the Project Manager page:

Thereafter, we'll see the base scene with a simple cube:

Usually, a new application is located inside the SDK's ./apps_dev/PROJECT_NAME/ folder. It's the ./apps_dev/simple_app/ folder in our case. There is the main application files among the others inside the project directory:

- simple_app.html - main HTML file

- simple_app.js - JavaScript file which contains the logic for this app

- simple_app.css - CSS file

- simple_app.blend - main blend file

- simple_app.json/simple_app.bin - exported 3D scene files

Now the application bones are ready and we can start creating a 3D scene and writing logic.

Preparing and Exporting the 3D Scene

We'll create a scene with objects, light sources and a camera in Blender.



For each of the "interactive" objects we'll make a simple animation of movement, turning (in the XYZ Euler or Quaternion WXYZ mode) and scaling.



We'll also enable the Selectable checkbox under the Object->Selection and Outlining tab. This will make the objects selectable with a mouse click.



Having set the camera, lighting and the objects' materials to your liking, we'll export the scene using the File -> Export -> Blend4Web (.json) menu option. Export path will already be corrected according to the project directory.

Application Template

The application logic is contained in the simple_app.js script. It will have some boilerplate code right away after the project creation. The code is sufficient for loading a scene and provides basic camera controls.

The source code of simple_app.js:

"use strict"

// register the application module
b4w.register("simple_app", function(exports, require) {

// import modules used by the app
var m_app       = require("app");
var m_data      = require("data");

/**
 * export the method to initialize the app (called at the bottom of this file)
 */
exports.init = function() {
    m_app.init({
        canvas_container_id: "main_canvas_container",
        callback: init_cb,
        show_fps: true,
        console_verbose: true,
        autoresize: true
    });
}

/**
 * callback executed when the app is initizalized 
 */
function init_cb(canvas_elem, success) {

    if (!success) {
        console.log("b4w init failure");
        return;
    }

    load();
}

/**
 * load the scene data
 */
function load() {
    m_data.load("simple_app.json", load_cb);
}

/**
 * callback executed when the scene is loaded
 */
function load_cb(data_id) {
    m_app.enable_controls();
    m_app.enable_camera_controls();

    // place your code here

}


});

// import the app module and start the app by calling the init method
b4w.require("simple_app").init();

Let's look at this script in more detail:

a) Registering the application modules

A typical Blend4Web application has a modular design in which the application scripts are organized into modules and are registered by the engine:

// register the application module
b4w.register("simple_app", function(exports, require) {...}

Two parameters are important here:

- exports - the global array; serves for accessing the module functions from the outside

- require - the function for loading other modules


b) Loading the neccessary engine modules

This modules are loaded by default:

b4w.register("simple_app", function(exports, require) {
    ...
// import modules used by the app
var m_app       = require("app");
var m_data      = require("data");
    ...
});

- app.js - serves to simplify the application's initialization

- data.js - API for loading the 3D scene's data


c) Initializing the application

The application is initialized with the exports.init() function. This function is declared via the "exports" array in order to be available from the outside:

b4w.register("simple_app", function(exports, require) {
    ...
/**
 * export the method to initialize the app (called at the bottom of this file)
 */
exports.init = function() {
    m_app.init({
        canvas_container_id: "main_canvas_container",
        callback: init_cb,
        show_fps: true,
        console_verbose: true,
        autoresize: true
    });
}
    ...
}); 

// import the app module and start the app by calling the init method
b4w.require("simple_app").init();

The m_app.init() function which is used here will create a Canvas HTML element and will initialize WebGL. It takes many parameters:

- canvas_container_id - id of the HTML element inside which the Canvas element will be created; the element with the "main_canvas_container" id which is presented in the main HTML file (simple_app.html) is used by default

- callback - function which is called after the app is initialized

- show_fps - whether to show the framerate

- console_verbose - whether to show debug messages in the browser console

- autoresize - whether to automatically adjust the size of the Canvas element according to the browser window

Note

exports.init() will be called asynchronously after the page is loaded. The m_app.init() function will assign the initialization stage to the window.onload event to ensure that the whole DOM tree is available.


d) Loading the scene

After the initialization is finished, the init_cb() will be called. This function is specified as the callback parameter described above. It is already possible to perform some preparatory actions inside it even if the scene have not loaded yet.

/**
 * callback executed when the app is initizalized 
 */
function init_cb(canvas_elem, success) {

    if (!success) {
        console.log("b4w init failure");
        return;
    }

    load();
}

Function arguments:

- canvas_elem - the created Canvas HTML element

- success - flag showing that Canvas is successfully created and initialized

The scene is loaded inside the load() function.

/**
 * load the scene data
 */
function load() {
    m_data.load("simple_app.json", load_cb);
}

Here the simple_app.json scene is loaded using the data.js module. The first argument is the path to the JSON file relative to the HTML file, and the second one is the function which is called after the 3D scene data is loaded.

Calling the load_cb() function means that we stepped over the initialization/loading stage and the application was started just now. It also started to render the scene. By default, the application enables user input and camera controls at this moment:

/**
 * callback executed when the scene is loaded
 */
function load_cb(data_id) {
    m_app.enable_controls();
    m_app.enable_camera_controls();

    // place your code here

}

This is the earliest moment when we have all the scene data loaded. So it is a suitable place to initialize and prepare all the things that related to the scene, scene's objects, animation and so on.


Adding the Functionality

Having the application bones prepared we can now implement simple user interaction with the scene's objects.

We need several modules to implement this functionality:

- animation.js - provides the API for controlling the objects' animation

- scenes.js - API for accessing the scene's objects

Import them in our code:

// import modules used by the app
var m_anim      = require("animation");
var m_app       = require("app");
var m_data      = require("data");
var m_scenes    = require("scenes");

In our example an animation should be played upon clicking an object. We will also cancel the animation for the previously selected object. We will create the "mousedown" event callback right away after the app has initialized, namely in the init_cb() function:

function init_cb(canvas_elem, success) {
    ...
    canvas_elem.addEventListener("mousedown", main_canvas_click, false);
    ...
}        

function main_canvas_click(e) {
    if (e.preventDefault)
        e.preventDefault();

    var x = e.clientX;
    var y = e.clientY;

    var obj = m_scenes.pick_object(x, y);

    if (obj) {
        if (_previous_selected_obj) {
            m_anim.stop(_previous_selected_obj);
            m_anim.set_frame(_previous_selected_obj, 0);
        }
        _previous_selected_obj = obj;

        m_anim.apply_def(obj);
        m_anim.play(obj);
    }
}

The main logic is contained in the main_canvas_click() function. In short, the algorithm will be as follows:

1) Get the object under the cursor.

var x = e.clientX;
var y = e.clientY;

var obj = m_scenes.pick_object(x, y);

2) Cancel the animation for the previously selected object.

Stop the animation:

m_anim.stop(_previous_selected_obj);

Set the frame number to zero i.e. return to the original state:

m_anim.set_frame(_previous_selected_obj, 0);

The _previous_selected_obj global variable is used to save the previous object.

3) Apply the animation to the object.

Load and precompute the animation which is assigned to the object in Blender:

m_anim.apply_def(obj);

Animation playback:

m_anim.play(obj);

Let's overview the final application code:

"use strict"

// register the application module
b4w.register("simple_app", function(exports, require) {

// import modules used by the app
var m_anim      = require("animation");
var m_app       = require("app");
var m_data      = require("data");
var m_scenes    = require("scenes");

var _previous_selected_obj = null;

/**
 * export the method to initialize the app (called at the bottom of this file)
 */
exports.init = function() {
    m_app.init({
        canvas_container_id: "main_canvas_container",
        callback: init_cb,
        show_fps: true,
        console_verbose: true,
        autoresize: true
    });
}

/**
 * callback executed when the app is initizalized 
 */
function init_cb(canvas_elem, success) {

    if (!success) {
        console.log("b4w init failure");
        return;
    }

    canvas_elem.addEventListener("mousedown", main_canvas_click, false);

    load();
}

/**
 * load the scene data
 */
function load() {
    m_data.load("simple_app.json", load_cb);
}

/**
 * callback executed when the scene is loaded
 */
function load_cb(data_id) {
    m_app.enable_controls();
    m_app.enable_camera_controls();

    // place your code here

}

function main_canvas_click(e) {
    if (e.preventDefault)
        e.preventDefault();

    var x = e.clientX;
    var y = e.clientY;

    var obj = m_scenes.pick_object(x, y);

    if (obj) {
        if (_previous_selected_obj) {
            m_anim.stop(_previous_selected_obj);
            m_anim.set_frame(_previous_selected_obj, 0);
        }
        _previous_selected_obj = obj;

        m_anim.apply_def(obj);
        m_anim.play(obj);
    }
}


});

// import the app module and start the app by calling the init method
b4w.require("simple_app").init();


The web application described in this article is simple but still interactive. This is a minimal example of what is possible with Blend4Web and its API.

The source files of the application and the scene are available in the free Blend4Web SDK distribution at the following location: ./deploy/tutorials/examples/interactive_web_application/.

Link to the standalone application

Changelog

[2014-05-08] Initial release.

[2014-06-30] Refined text about the alpha initialization option.

[2014-07-07] Minor changes in text about the application source files.

[2014-07-22] Path to the application changed.

[2014-08-06] Updated the example code because of picking API change.

[2014-10-29] Updated the article text because of API change.

[2014-11-28] Changes in text about the application source files.

[2015-04-23] Changes in text about the application source files.

[2015-05-08] Updated the article text because of API change.

[2015-09-30] Minor changes in article.

[2015-10-02] Rewrited the article in accordance with the new project management system.