Three.js: THREE.App

Created on 29 Jan 2017  路  16Comments  路  Source: mrdoob/three.js

Today I found this article and got me thinking about implementing some sort of THREE.App...

Something like this:

var canvas = document.getElementById( 'canvas' );
var app = new THREE.App( canvas );

var geometry = new THREE.BoxGeometry( 2, 2, 2 );
var material = new THREE.MeshLambertMaterial( { color: 0xdddddd } );

var light = new THREE.HemisphereLight( 0xffffff, 0x000000, 1 );
app.scene.add( light );

var mesh = new THREE.Mesh( geometry, material );
app.scene.add( mesh );

app.onUpdate( function ( time, delta ) {

    mesh.rotation.y = time / 1000;

} );

It actually shouldn't be much code I think...

What do you guys think? Suggestions?

PS: @greggman I think THREE.App could handle the canvas resizing automatically like you've always suggested.

Suggestion

Most helpful comment

I went ahead and created a github repository with a minimal cleaned up excerpt from my application. There is a lot of room for improvement (i.e. passing config options for each plugin). The base class framework also has a few edges that I don't really like. And I need to extract a few more plugins to really show why I needed a class framework. It is probably not needed for the simplest cases.

I created a small demo (source) to show the simplest usage.

Edit: Added asset loading and another demo (source) showing how I use the custom class framework. Also added GPU stats plugin using the well known stats.js (older version though).

Edit 2: Added some more core documentation and more plugins (ie. text label plugin). This is pretty much the amount of code I'm willing to put under MIT. Let's see how it evolves from here ...

All 16 comments

Yes to this! Would THREE.App take some kind of configuration object? You'd at least need to be able to specify the camera and renderer type I guess.

Also +1 for auto-resizing - it's something that is needed in nearly every use case and is a bit of work to set up. It would be good to have an included cross-browser and community tested standard method for doing this.

So there's one main onUpdate function and one main scene and one main camera. What would an example look like when using render targets or other multiple scene, multiple camera situations?

Another could one to work out is what would your code look like if you want to take screenshot as in call canvas.toDataURL?

What would an example look like when using render targets or other multiple scene, multiple camera situations?

I was just giving this scenario some thought and agree that this would be one of the major issues. THREE.App could take already instantiated scenes / cameras / renderers as optional arguments though and use those instead.

Alternately it could (at least at first) be intended just to replace the most common use cases, one scene with an orthographic or perspective camera and renderer. More complex cases could still be implemented as before.

Another could one to work out is what would your code look like if you want to take screenshot as in call canvas.toDataURL?

Presumably you could just use app.canvas.toDataURL?

I like the idea that time values are automatically provided by three.js. Similar to the Time class in Unity. Even if we don't implement something like THREE.App, THREE.Time could be desirable...

That's a good article.

Just a thought as I was going to open an issue with a question but saw that questions should be asked on SO...why not delete the Issues that are asking questions, with a reminder that this repo isn't for support questions? That would help SO have a greater amount of solutions for (un)common issues. I just posted one about adding octahedrons to the editor and didn't see related posts.

Basically you are proposing to add the new object "THREE.App" that would boilerplate a basic THREE JS setup right? (renderer, scene, camera, controller)

I think it is very useful to get people started or to write quick demos/examples but to build more advanced stuff like mentioned above people might have to go back to the basics.

Is the idea to make it more accessible?

In my opinion, the main reason for XTK's success was that we made it VERY easy to get started with.
(http://jsfiddle.net/gh/get/toolkit/edge/xtk/lessons/tree/master/00/#run)

But as you want to get more advanced features it tends to make things more difficult (to use and maintain).

I now use THREE JS in AMI (https://github.com/FNNDSC/ami) and it is much easier to get intermediate/advanced features setup. But it requires more coding.

A quick experiment was to write XTK "helpers" for AMI that would boilerplate the setup code and make an XTK-like API for AMI:
https://fnndsc.github.io/ami/#xtk_lesson13
https://github.com/FNNDSC/ami/blob/dev/examples/xtk_lesson13/xtk_lesson13.js

My 2 cents :)

What if THREE.App did slightly less?

var canvas = document.getElementById( 'canvas' );
var app = new THREE.App( canvas );

var geometry = new THREE.BoxGeometry( 2, 2, 2 );
var material = new THREE.MeshLambertMaterial( { color: 0xdddddd } );

var light = new THREE.HemisphereLight( 0xffffff, 0x000000, 1 );
app.scene.add( light );

var mesh = new THREE.Mesh( geometry, material );
app.scene.add( mesh );

app.onRender( function ( time, delta ) {

    app.renderer.render( app.scene, app.camera );

} );

In this case you're still calling renderer.render directly so it's easy to work with when you start needing multiple scenes (like when using post processing, or HUDs, etc...). There's also no issues with screen capture because you can call toDataURL immediately after rendering. Basically THREE.App just gives you a renderer, default scene, and default camera and manages them for you (size, aspect, ...)

I feel like app making a scene and camera for you suggest there can only be one which seems misleading but maybe that's something users google when then want to have multiple scenes. There's also a bunch of issues with camera options because you'd probably want the option to make an orthographic camera but now you need not only options about size but if you're letting THREE.App automatically manage the size you need to be able to tell with what kind of size you want. Some fixed number of units down or do you want it to be units based on the window size. I guess that's not that big a deal but a little more complicated

I think writing code and trying things out is better than just talking because I don't see the issues until I actually try to use it. (for example I didn't think about orthographic issues)

// Perspective example
http://codepen.io/greggman/pen/pRpeXO

// Orthographic example, always N units tall
http://codepen.io/greggman/details/egyWPN/

// Orthographic example, always 1x1 pixels
http://codepen.io/greggman/pen/ggoRbB

Just throwing out examples, I don't know if the orthographic issues point out reasons this is a bad idea or if more options are needed (like what if you want 0,0 to be the top left corner) or if maybe those are "advanced use cases" where you don't use THREE.App or what

I have been building my own framework around three to simplify the process of setting up projects.
It's based on "presentation" objects which contains all the main boilerplate, and then components for smaller boilerplate set ups around specific features.

Here is my repository if anyone wants to take a look at it.

@Glinkis Your repo would be more useful with a README.

So there's one main onUpdate function and one main scene and one main camera. What would an example look like when using render targets or other multiple scene, multiple camera situations?

Maybe App could have setCamera() and setScene()?

setCamera would potential solve the issue of which camera to make but it wouldn't solve the issue of how to manage it. Although moving those options to the camera might be better.

setScene doesn't seem to solve anything though. There'd still be the issues from multi-scene situations, render target situations, screen capture situations etc though if App automagically calls renderer.render

@mrdoob I was trying to implement something similar in my framework for three.js. Yep yep, those ideas about autoresize and simple app with a few steps (setting renderer, cemera, ...)

I want to share here my implementation (maybe it will help you with some stuff in THREE.App if you are going to implement it).

The code that best explains what i did i here. I wrapped separate parts of three.js (like scene, camera, renderer) in an API called "modules". Modules can access App and share their data to other modules (yep, dependencies). For example RenderingModule requires CameraModule and SceneModule to be set first - and we pass them first in an WHS.App array. They execute in the following order.

Same thing with ResizeModule (autoresize). It requires RenderingModule to be set first. That's how it works.

P.s.: There are many ways you can implement THREE.App. I tried to make it (WHS.App) as much flexible as i can.

image

Just my 2 cents. I pretty much went down the same idea for my application and incidentally I called mine ThreeApp 馃槃 I also added some kind of plugin/module system to actually add functionality to the app. One other important aspect that was very important for me was the loading of assets. So it turned out that I still needed quite a bit of functionality in the base class to support all cases. Currently I'm in the process of refactoring my whole code-base (3rd iteration) and I'm planning to release the whole thing on github soon-ish.

My app initialization code looks very similar to the one from @sasha240100. In my implementation I only pass the plugin constructors. This allows me to create plugins after the main app was created. It also allows to initialize them (TBD) in the right order (if plugins mention their dependencies; thinking of a needs/provides API). At some point I probably will need some kind of virtual deps like "camera", since there are multiple plugins that can satisfy the dependency (perspective/orthogonal). Since I only pass constructors, this kind of information is stored on the function or its prototype. This does lead to some framework "magic" that might not be very easy to grasp for beginners ...

var vp = document.getElementById('vp');
var app = new ThreeApp(vp, {
    plugins: [
        ThreeApp.Clock,
        ThreeApp.Fullsize,
        ThreeApp.Loader,
        ThreeApp.Progress,
        ThreeApp.TrackballControl,
        ThreeApp.PerspectiveCamera,
        ...
    ],
    start: true
});

I think most would agree that something like this would already be a huge improvement!

In order to have a neat experience with "delayed" initialization cases, I resorted to use a base class/mixin system that integrates with ThreeApp. Every instance has three life-cycle events: ctor, init: ready.

The usage then looks something like:

var Stars = Class.create('Stars', Object3D, ['Resources', 'Options', 'Events'])

.ctor(function (app) {
    // fetch resources
    this.prefetch({
        's_col': ['A', 'data/stars/stars.col.db'],
        'names': ['J', 'data/stars/stars.names.json'],
    });
})

.init(function (app) {
    // wait for any promise or
    // may wait for plugins etc.
    this.wait(this.dependency);
})

.ready(function () {
    var names = this.asset('names');
    var s_col = this.asset('s_col');
})

I guess this is probably already out of the scope for THREE.App, but for me that was the most important part to be able to create big applications with many widgets. I also need to handle "LOD" and camera shifting directly in the app as AFAIK the camera should always be close to the zero world point, so I need to shift the scene around instad of the camera.

Thanks and kind regards!

FWIW: You can see this in action in one of my previews (can load slow or crash).

I went ahead and created a github repository with a minimal cleaned up excerpt from my application. There is a lot of room for improvement (i.e. passing config options for each plugin). The base class framework also has a few edges that I don't really like. And I need to extract a few more plugins to really show why I needed a class framework. It is probably not needed for the simplest cases.

I created a small demo (source) to show the simplest usage.

Edit: Added asset loading and another demo (source) showing how I use the custom class framework. Also added GPU stats plugin using the well known stats.js (older version though).

Edit 2: Added some more core documentation and more plugins (ie. text label plugin). This is pretty much the amount of code I'm willing to put under MIT. Let's see how it evolves from here ...

I have extracted and "finalized" quite a lot more plugins since my last post. Also updated the core documentation. One plugin I'd like to mention is a pretty advanced text label renderer, which shows the interaction between the grouped and group class mixin API. My framework aims to help you to build the most performant solution (in regard of supporting you in creating buffered/instaced drawings). It might therefore not be suited for the very basic beginner; but that way I get 60k text labels rendered at nearly 60fps (with dynamic position updates on each frame)! See the text label borg cube below:

borg-cube60k

Pixi.js has a similar class called Application that seems to be pretty widely used in that community. You might want to take a look for inspiration.

Was this page helpful?
0 / 5 - 0 ratings