KEMBAR78
Integrating Angular js & three.js
Josh Staples 
@cubicleDowns 
blog.tempt3d.com 
github.com/cubicleDowns/ng-three-viewer
WHO? Software Engineer @ napofearth.com 
“Explore Music Visually” PRIVATE BETA (#ngmeetup)
TODAY 
1. (v1) Angular & Three.js 3D Model Viewer 
2. (v2) “Controller as” & 
prototypal Angular components 
3. Grunt & Closure compilation approaches 
4. (v3*) Scoped scene graph
WHY? 
Great pattern(s) with low barrier of entry 
& high productivity 
All web devs can easily be 3D web devs 
Isolate THREE code from Angular UI/UX code!
THREE WHAT? 
threejs.org three features 
● Renderers 
● Scene Graph 
● Cameras 
● Animation 
● Lights 
● Materials 
● Shaders 
● Objects 
● Geometry 
● Loaders 
● Utilities 
● Export/Import 
● Support 
● Examples 
● WebVR 
● DIYVR 
Started by Mr. Doob on April 23, 2010
ANGULAR WHO?
Demo v1 
http://localhost:8000/v1/dist/
CONTROLLER 
AppController 
CONTROLLER 
FileLoaderController 
UI Elements and Controllers 
DIRECTIVE INCLUDE 
file-loader.html 
DIRECTIVE INCLUDE 
about.html 
DIRECTIVE INCLUDE 
chrome.html 
DIRECTIVE INCLUDE 
toolbar.html
CONTROLLER 
FileLoaderController 
Dependency Injections Across Angular Components 
CONTROLLER 
AppController 
DIRECTIVE 
select 
SERVICE 
StorageService 
SERVICE 
MessageBus 
FACTORY 
Viewer
Viewer.factory('ViewerFactory', … ) { 
init() 
home = new Viewer.Scene() 
animate () 
render () 
makeSelection () 
loadOBJMTL () 
loadGLTF () 
loadOBJ () 
loadJSON () 
scale () 
rotate () 
/** Translate the model along an axis 
* @param {number} x 
* @param {number} y 
* @param {number} z */ 
translate (x,y,z) 
home.wrangler.currentModel.position.set(x, y, z); 
CONTROLLER 
AppController 
● init () 
● rotate () 
● scale () 
● translate () 
SERVICE 
MessageBus 
DIRECTIVE 
select 
● makeSelection () 
CONTROLLER 
FileLoaderController 
● loadOBJMTL () 
● loadGLTF () 
● loadOBJ () 
● loadJSON () 
Viewer Factory Interface
Viewer Factory Architecture 
Viewer Factory Singleton 
function init(params) { 
home = new Viewer.Scene(params); 
animate(); 
} 
Viewer.Scene() 
this.scene 
THREE.Scene() 
this.renderer 
THREE.WebGLRenderer() 
this.wrangler 
Viewer.Wrangler() 
function animate () { 
requestAnimationFrame(animate); 
render(); 
} 
this.setup 
Viewer.Setup() 
this.cameras 
Viewer.Cameras() 
this.controls 
THREE.OrbitControls() 
this.raycaster 
THREE.Raycaster()
USE CASE - User Click, Intersect 3D Model, Return Model Information 
Angular Directive, <canvas select> 
elem.on(tap, function(e) 
x = e.gesture.center.x; 
y = e.gesture.center.y; 
// creating NDC coordinates for ray intersection. 
mouseDown.x = (x / width) * 2 - 1; 
mouseDown.y = -(y / height) * 2 + 1; 
ViewerFactory.makeSelection(mouseDown); 
Viewer Factory, makeSelection 
makeSelection(mouse): 
Angular Controller/Factory 
$scope.$on(‘objectSelected’, function () { 
// Do Something. 
}); 
var vector = new THREE.Vector3( mouse.x, mouse.y, 1).unproject(home.cameras.liveCam); 
home.raycaster.set(home.cameras.liveCam.position, vector.sub(home.cameras.liveCam.position).normalize()); 
var intersected = home.raycaster.intersectObjects(home.wrangler.currentModel, true); 
MessageBus.trigger('objectSelected', intersected[0])
MOST PROFITABLE MOVIE?
MOST PROFITABLE MOVIE? 
THE SEQUEL!
STARRING 
“Controller as” 
& 
as ctrl 
Annotations 
as SNAFU
Sequel (v2) 
http://localhost:8000/v2/dist/
Controller as 
<div id="file-loader" ng-controller="FileLoaderController as loader" ng-show=”loader.visible”> 
<input type="text" ng-model="loader.data.obj" placeholder="obj file url"> 
<input type="text" ng-model="loader.data.name" placeholder="unique name"> 
<button ng-click="otherLoader.loadOBJMTL()">Load OBJ/MTL</button> 
<button ng-click="loader.loadSampleOBJMTL()">SAMPLE OBJ-MTL</button> 
Controller 
level scope 
:) 
<div id="file-loader" ng-controller="FileLoaderController" ng-show=”visible”> 
- - - 
<input type="text" ng-model="data.obj" placeholder="obj file url"> 
<input type="text" ng-model="data.mtl" placeholder="mtl file url"> 
<button ng-click="loadOBJMTL()">Load OBJ/MTL</button> 
<button ng-click="loadSampleOBJMTL()">SAMPLE OBJ-MTL</button> 
nearest scope 
:(
Service, !Factory 
Viewer.ViewerService 
.prototype 
init 
listeners 
animate 
render 
makeSelection 
loadOBJMTL 
loadOBJ 
loadGLTF 
loadJSON 
rotate 
translate 
scale 
● No More Factories 
○ closure pattern 
● Instead, prototypal Service 
○ ‘new’ and this 
○ .bind() for callbacks 
● Saves Memory, Time, Searches (sorry) 
● Single Pattern For Everything! 
● IMHO, the best way to code JS
/** @ngInject */ 
Viewer.ViewerService = function($timeout, MessageBus){ 
this.timeout = $timeout; 
this.MessageBus = MessageBus; 
}; 
Viewer.ViewerService.prototype.init = function (params){ 
this.home = new Viewer.Scene(params); 
this.MessageBus.trigger(‘app-ready’); 
animate(); 
}; 
Viewer.factory('ViewerFactory', ['MessageBus', function (MessageBus) 
function init () {} 
function makeSelection() {} 
return { 
init: init, 
makeSelection: makeSelection 
} 
closure style, 
ng-annotate 
: 
prototypal 
:) 
Prototypal Angular
CAonnntorotallteior nass 
/** Service which initiates the THREE.js scene and 
* provides methods to interact with that scene 
* 
* @param {angular.$timeout} $timeout 
* @param {!Viewer.MessageBus} MessageBus 
* @constructor 
* @ngInject 
*/ 
Viewer.ViewerService = function($timeout, MessageBus){ 
this.timeout = $timeout; 
this.MessageBus = MessageBus; 
}; 
/** 
* Translate the model along an axis 
* @param {number} x 
* @param {number} y 
* @param {number} z 
*/ 
Viewer.ViewerService.prototype.translate = function(x, y, z){ 
this.home.wrangler.currentModel.position.set(x, y, z) 
}; 
/** 
* @param {number} s 
*/ 
Viewer.ViewerService.prototype.scale = function(s) { 
this.home.wrangler.currentModel.scale.set(s, s, s); 
}; 
The Closure Compiler can use data type 
information about JavaScript variables to provide 
enhanced optimization and warnings.
APP INIT (app.js) 
angular.module('ThreeViewer', ['ngHammer', 'ngRoute', 'LocalStorageModule']) 
.config(['localStorageServiceProvider',function(localStorageServiceProvider){ 
…. 
.config(['$locationProvider', function($locationProvider) { 
…. 
$locationProvider.html5Mode(true); 
.config(['$routeProvider', function($routeProvider){ 
angular.module('ThreeViewer', ['ngRoute', 'LocalStorageModule']) 
.config(ThreeViewer.ConfigLocation) 
…. 
.directive('select', ['ViewerService', ThreeViewer.SelectDirective.factory]) 
…. 
.filter('forceInt', ThreeViewer.ForceInt.factory) 
…. 
.service('ViewerService', [MessageBus', ThreeViewer.ViewerService]) 
…. 
.controller('AppController', ['$scope', 'ViewerService', ThreeViewer.AppController]); 
v2 
:) 
v1 
:
uglify: { 
ng3: { 
options: { 
compress: { 
drop_console: true 
}, 
sourceMap: true, 
}, 
files: { 
'dist/app.min.js': ['<%= concat.ng3.dest %>'] 
} 
} 
}, 
command: 'java -jar closure/compiler.jar ' + 
'--compilation_level SIMPLE_OPTIMIZATIONS' + 
'--language_in ECMASCRIPT5_STRICT ' + 
'--angular_pass ' + 
'--externs closure/externs/angular-1.3.js ' + 
'--externs closure/externs/three.js ' + 
'--generate_exports ' + 
'--manage_closure_dependencies ' + 
'--js closure/library/base.js ' + 
'--js <%= app %> ' + 
'--js <%= ng %> ' + 
'--js <%= three %> ' + 
'--js_output_file dist/app.min.js' 
Minify or Closure Compilation? 
Closure Compiler 
● type checking 
● ngInject 
● goog.provide / require 
Grunt ng-annotate 
● uglify 
● ng-annotate
NOGN Eto A TPHPR, ETEWO PATTERNS 
V1 
● Most Common Angular Pattern 
● Grunt uglify / minify 
● Factories 
● Services 
● Filters 
● Directives 
● Init controllers from DOM 
V2 
● Prototypal Pattern for Everything! 
● Bridge to Angular 2.0 
● Controller as (local scope) 
● Closure Compilation 
○ type checking 
○ -- angular_pass 
○ dependency chains 
○ minification 
● App.js Initialization 
● No closure pattern (factories)
JNOGIN t oU TSH! REE Mobile Developer - Backend Guru 
napofearth.com/jobs UI/UX Designer - QA #ngmeetup

Integrating Angular js & three.js

  • 2.
    Josh Staples @cubicleDowns blog.tempt3d.com github.com/cubicleDowns/ng-three-viewer
  • 3.
    WHO? Software Engineer@ napofearth.com “Explore Music Visually” PRIVATE BETA (#ngmeetup)
  • 4.
    TODAY 1. (v1)Angular & Three.js 3D Model Viewer 2. (v2) “Controller as” & prototypal Angular components 3. Grunt & Closure compilation approaches 4. (v3*) Scoped scene graph
  • 5.
    WHY? Great pattern(s)with low barrier of entry & high productivity All web devs can easily be 3D web devs Isolate THREE code from Angular UI/UX code!
  • 6.
    THREE WHAT? threejs.orgthree features ● Renderers ● Scene Graph ● Cameras ● Animation ● Lights ● Materials ● Shaders ● Objects ● Geometry ● Loaders ● Utilities ● Export/Import ● Support ● Examples ● WebVR ● DIYVR Started by Mr. Doob on April 23, 2010
  • 7.
  • 8.
  • 9.
    CONTROLLER AppController CONTROLLER FileLoaderController UI Elements and Controllers DIRECTIVE INCLUDE file-loader.html DIRECTIVE INCLUDE about.html DIRECTIVE INCLUDE chrome.html DIRECTIVE INCLUDE toolbar.html
  • 10.
    CONTROLLER FileLoaderController DependencyInjections Across Angular Components CONTROLLER AppController DIRECTIVE select SERVICE StorageService SERVICE MessageBus FACTORY Viewer
  • 11.
    Viewer.factory('ViewerFactory', … ){ init() home = new Viewer.Scene() animate () render () makeSelection () loadOBJMTL () loadGLTF () loadOBJ () loadJSON () scale () rotate () /** Translate the model along an axis * @param {number} x * @param {number} y * @param {number} z */ translate (x,y,z) home.wrangler.currentModel.position.set(x, y, z); CONTROLLER AppController ● init () ● rotate () ● scale () ● translate () SERVICE MessageBus DIRECTIVE select ● makeSelection () CONTROLLER FileLoaderController ● loadOBJMTL () ● loadGLTF () ● loadOBJ () ● loadJSON () Viewer Factory Interface
  • 12.
    Viewer Factory Architecture Viewer Factory Singleton function init(params) { home = new Viewer.Scene(params); animate(); } Viewer.Scene() this.scene THREE.Scene() this.renderer THREE.WebGLRenderer() this.wrangler Viewer.Wrangler() function animate () { requestAnimationFrame(animate); render(); } this.setup Viewer.Setup() this.cameras Viewer.Cameras() this.controls THREE.OrbitControls() this.raycaster THREE.Raycaster()
  • 13.
    USE CASE -User Click, Intersect 3D Model, Return Model Information Angular Directive, <canvas select> elem.on(tap, function(e) x = e.gesture.center.x; y = e.gesture.center.y; // creating NDC coordinates for ray intersection. mouseDown.x = (x / width) * 2 - 1; mouseDown.y = -(y / height) * 2 + 1; ViewerFactory.makeSelection(mouseDown); Viewer Factory, makeSelection makeSelection(mouse): Angular Controller/Factory $scope.$on(‘objectSelected’, function () { // Do Something. }); var vector = new THREE.Vector3( mouse.x, mouse.y, 1).unproject(home.cameras.liveCam); home.raycaster.set(home.cameras.liveCam.position, vector.sub(home.cameras.liveCam.position).normalize()); var intersected = home.raycaster.intersectObjects(home.wrangler.currentModel, true); MessageBus.trigger('objectSelected', intersected[0])
  • 14.
  • 15.
  • 16.
    STARRING “Controller as” & as ctrl Annotations as SNAFU
  • 17.
  • 18.
    Controller as <divid="file-loader" ng-controller="FileLoaderController as loader" ng-show=”loader.visible”> <input type="text" ng-model="loader.data.obj" placeholder="obj file url"> <input type="text" ng-model="loader.data.name" placeholder="unique name"> <button ng-click="otherLoader.loadOBJMTL()">Load OBJ/MTL</button> <button ng-click="loader.loadSampleOBJMTL()">SAMPLE OBJ-MTL</button> Controller level scope :) <div id="file-loader" ng-controller="FileLoaderController" ng-show=”visible”> - - - <input type="text" ng-model="data.obj" placeholder="obj file url"> <input type="text" ng-model="data.mtl" placeholder="mtl file url"> <button ng-click="loadOBJMTL()">Load OBJ/MTL</button> <button ng-click="loadSampleOBJMTL()">SAMPLE OBJ-MTL</button> nearest scope :(
  • 19.
    Service, !Factory Viewer.ViewerService .prototype init listeners animate render makeSelection loadOBJMTL loadOBJ loadGLTF loadJSON rotate translate scale ● No More Factories ○ closure pattern ● Instead, prototypal Service ○ ‘new’ and this ○ .bind() for callbacks ● Saves Memory, Time, Searches (sorry) ● Single Pattern For Everything! ● IMHO, the best way to code JS
  • 20.
    /** @ngInject */ Viewer.ViewerService = function($timeout, MessageBus){ this.timeout = $timeout; this.MessageBus = MessageBus; }; Viewer.ViewerService.prototype.init = function (params){ this.home = new Viewer.Scene(params); this.MessageBus.trigger(‘app-ready’); animate(); }; Viewer.factory('ViewerFactory', ['MessageBus', function (MessageBus) function init () {} function makeSelection() {} return { init: init, makeSelection: makeSelection } closure style, ng-annotate : prototypal :) Prototypal Angular
  • 21.
    CAonnntorotallteior nass /**Service which initiates the THREE.js scene and * provides methods to interact with that scene * * @param {angular.$timeout} $timeout * @param {!Viewer.MessageBus} MessageBus * @constructor * @ngInject */ Viewer.ViewerService = function($timeout, MessageBus){ this.timeout = $timeout; this.MessageBus = MessageBus; }; /** * Translate the model along an axis * @param {number} x * @param {number} y * @param {number} z */ Viewer.ViewerService.prototype.translate = function(x, y, z){ this.home.wrangler.currentModel.position.set(x, y, z) }; /** * @param {number} s */ Viewer.ViewerService.prototype.scale = function(s) { this.home.wrangler.currentModel.scale.set(s, s, s); }; The Closure Compiler can use data type information about JavaScript variables to provide enhanced optimization and warnings.
  • 22.
    APP INIT (app.js) angular.module('ThreeViewer', ['ngHammer', 'ngRoute', 'LocalStorageModule']) .config(['localStorageServiceProvider',function(localStorageServiceProvider){ …. .config(['$locationProvider', function($locationProvider) { …. $locationProvider.html5Mode(true); .config(['$routeProvider', function($routeProvider){ angular.module('ThreeViewer', ['ngRoute', 'LocalStorageModule']) .config(ThreeViewer.ConfigLocation) …. .directive('select', ['ViewerService', ThreeViewer.SelectDirective.factory]) …. .filter('forceInt', ThreeViewer.ForceInt.factory) …. .service('ViewerService', [MessageBus', ThreeViewer.ViewerService]) …. .controller('AppController', ['$scope', 'ViewerService', ThreeViewer.AppController]); v2 :) v1 :
  • 23.
    uglify: { ng3:{ options: { compress: { drop_console: true }, sourceMap: true, }, files: { 'dist/app.min.js': ['<%= concat.ng3.dest %>'] } } }, command: 'java -jar closure/compiler.jar ' + '--compilation_level SIMPLE_OPTIMIZATIONS' + '--language_in ECMASCRIPT5_STRICT ' + '--angular_pass ' + '--externs closure/externs/angular-1.3.js ' + '--externs closure/externs/three.js ' + '--generate_exports ' + '--manage_closure_dependencies ' + '--js closure/library/base.js ' + '--js <%= app %> ' + '--js <%= ng %> ' + '--js <%= three %> ' + '--js_output_file dist/app.min.js' Minify or Closure Compilation? Closure Compiler ● type checking ● ngInject ● goog.provide / require Grunt ng-annotate ● uglify ● ng-annotate
  • 24.
    NOGN Eto ATPHPR, ETEWO PATTERNS V1 ● Most Common Angular Pattern ● Grunt uglify / minify ● Factories ● Services ● Filters ● Directives ● Init controllers from DOM V2 ● Prototypal Pattern for Everything! ● Bridge to Angular 2.0 ● Controller as (local scope) ● Closure Compilation ○ type checking ○ -- angular_pass ○ dependency chains ○ minification ● App.js Initialization ● No closure pattern (factories)
  • 25.
    JNOGIN t oUTSH! REE Mobile Developer - Backend Guru napofearth.com/jobs UI/UX Designer - QA #ngmeetup