├── asteroid.png ├── scratch ├── there.cljs ├── virtual.js ├── repl.cljs ├── virtual-vector.js ├── hello.cljs ├── virtual-thunk.js ├── virtual-pixi.js ├── virtual-dom.js ├── virtual-three.js └── virtual-three-clj.js ├── js ├── loaders │ ├── ctm │ │ ├── CTMWorker.js │ │ ├── license │ │ │ ├── OpenCTM.txt │ │ │ ├── js-lzma.txt │ │ │ └── js-openctm.txt │ │ └── CTMLoader.js │ ├── SVGLoader.js │ ├── KMZLoader.js │ ├── PlayCanvasLoader.js │ ├── collada │ │ ├── AnimationHandler.js │ │ ├── KeyFrameAnimation.js │ │ └── Animation.js │ ├── BabylonLoader.js │ ├── gltf │ │ ├── glTFAnimation.js │ │ └── glTFLoaderUtils.js │ ├── PDBLoader.js │ ├── PVRLoader.js │ ├── DDSLoader.js │ ├── VTKLoader.js │ ├── PCDLoader.js │ ├── MTLLoader.js │ ├── OBJLoader.js │ ├── AssimpJSONLoader.js │ └── RGBELoader.js ├── zajal.js └── repl.js ├── zajal ├── input │ └── keyboard.cljs ├── core.cljs └── draw │ ├── pixi.ts │ ├── pixi.js │ └── three.cljs ├── main.js ├── package.json ├── index.html ├── LICENSE ├── examples ├── other.cljs └── asteroid.cljs ├── .gitignore ├── README.md └── ts └── zajal.ts /asteroid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nasser/zajal/HEAD/asteroid.png -------------------------------------------------------------------------------- /scratch/there.cljs: -------------------------------------------------------------------------------- 1 | (ns there) 2 | 3 | (defn prand [x] 4 | (str x :a x )) -------------------------------------------------------------------------------- /js/loaders/ctm/CTMWorker.js: -------------------------------------------------------------------------------- 1 | importScripts( "lzma.js", "ctm.js" ); 2 | 3 | self.onmessage = function( event ) { 4 | 5 | var files = []; 6 | 7 | for ( var i = 0; i < event.data.offsets.length; i ++ ) { 8 | 9 | var stream = new CTM.Stream( event.data.data ); 10 | stream.offset = event.data.offsets[ i ]; 11 | 12 | files[ i ] = new CTM.File( stream ); 13 | 14 | } 15 | 16 | self.postMessage( files ); 17 | self.close(); 18 | 19 | }; 20 | -------------------------------------------------------------------------------- /js/zajal.js: -------------------------------------------------------------------------------- 1 | const {ipcRenderer} = require("electron"), 2 | fs = require("fs") 3 | 4 | ipcRenderer.on('new', (event, path) => { 5 | fs.readFile(path, "utf8", (err, source) => { 6 | console.log(source); 7 | clojurescript.core.eval(source); 8 | }) 9 | }) 10 | 11 | ipcRenderer.on('changed', (event, path) => { 12 | // hack 13 | var oldTitle = document.title; 14 | document.title = document.title + "*"; 15 | fs.readFile(path, "utf8", (err, source) => { 16 | clojurescript.core.eval(source); 17 | document.title = oldTitle; 18 | }) 19 | }) -------------------------------------------------------------------------------- /js/repl.js: -------------------------------------------------------------------------------- 1 | const dgram = require('dgram'); 2 | const server = dgram.createSocket('udp4'); 3 | 4 | server.on('error', (err) => { 5 | console.log(`server error:\n${err.stack}`); 6 | server.close(); 7 | }); 8 | 9 | server.on('message', (msg, rinfo) => { 10 | let code = msg.toString(), 11 | result = cljs.core.pr_str(clojurescript.core.eval(code)); 12 | server.send(`${clojurescript.core.eval("(str *ns*)")}=> ${code}${result}\n\n`, rinfo.port, rinfo.address); 13 | }); 14 | 15 | server.on('listening', () => { 16 | var address = server.address(); 17 | console.log(`server listening ${address.address}:${address.port}`); 18 | }); 19 | 20 | server.bind(10011); -------------------------------------------------------------------------------- /zajal/input/keyboard.cljs: -------------------------------------------------------------------------------- 1 | (ns zajal.input.keyboard 2 | (:require [clojure.string :as string])) 3 | 4 | (defn- camels-to-hyphens [s] 5 | (-> s 6 | (string/replace #"([A-Za-z])([A-Z][a-z])" "$1-$2") 7 | (string/replace #"([A-Za-z]{2,})([A-Z0-9])$" "$1-$2") 8 | string/lower-case)) 9 | 10 | (defn- kw [s] 11 | (-> s camels-to-hyphens keyword)) 12 | 13 | (def pressed-keys (atom {})) 14 | 15 | (defn add-listeners! [] 16 | (js/window.document.addEventListener 17 | "keydown" 18 | #(swap! pressed-keys assoc (.-code %) %)) 19 | (js/window.document.addEventListener 20 | "keyup" 21 | #(swap! pressed-keys dissoc (.-code %)))) 22 | 23 | (add-listeners!) 24 | 25 | (defn key-set [] 26 | (->> @pressed-keys 27 | keys 28 | (map kw) 29 | set)) -------------------------------------------------------------------------------- /js/loaders/SVGLoader.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author mrdoob / http://mrdoob.com/ 3 | * @author zz85 / http://joshuakoo.com/ 4 | */ 5 | 6 | THREE.SVGLoader = function ( manager ) { 7 | 8 | this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager; 9 | 10 | }; 11 | 12 | THREE.SVGLoader.prototype = { 13 | 14 | constructor: THREE.SVGLoader, 15 | 16 | load: function ( url, onLoad, onProgress, onError ) { 17 | 18 | var scope = this; 19 | 20 | var parser = new DOMParser(); 21 | 22 | var loader = new THREE.XHRLoader( scope.manager ); 23 | loader.load( url, function ( svgString ) { 24 | 25 | var doc = parser.parseFromString( svgString, 'image/svg+xml' ); // application/xml 26 | 27 | onLoad( doc.documentElement ); 28 | 29 | }, onProgress, onError ); 30 | 31 | } 32 | 33 | }; 34 | -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | const {BrowserWindow, app} = require('electron'); 2 | os = require('os'), 3 | path = require('path'), 4 | chokidar = require('chokidar'); 5 | 6 | let win; 7 | 8 | var watcher = chokidar.watch(process.argv[2]); 9 | watcher.on('change', path => win.webContents.send('changed', path)) 10 | 11 | function createWindow() { 12 | win = new BrowserWindow({width: 800, height: 600, 'title-bar-style': 'hidden'}); 13 | win.loadURL(`file:///${__dirname}/index.html`); 14 | win.webContents.on('did-finish-load', () => { 15 | win.webContents.send('new', process.argv[2]) 16 | }); 17 | } 18 | 19 | app.on('ready', createWindow); 20 | 21 | app.on('window-all-closed', () => { 22 | if (process.platform !== 'darwin') { 23 | app.quit(); 24 | } 25 | }); 26 | 27 | app.on('activate', () => { 28 | if (win === null) { 29 | createWindow(); 30 | } 31 | }); 32 | -------------------------------------------------------------------------------- /scratch/virtual.js: -------------------------------------------------------------------------------- 1 | var current; 2 | var next; 3 | 4 | function renderer(container, initial) { 5 | current = initial; 6 | next = initial; 7 | 8 | container.appendChild( current.render().domElement ); 9 | 10 | current.camera.render().aspect = window.innerWidth / window.innerHeight; 11 | current.camera.render().updateProjectionMatrix(); 12 | current.render().setSize( window.innerWidth, window.innerHeight ); 13 | 14 | var _update = function(newState) { 15 | next = newState; 16 | } 17 | 18 | var _render = function() { 19 | requestAnimationFrame(_render); 20 | if(current === next) return; 21 | 22 | current.reconcile(next); 23 | current = next; 24 | current.render().render( current.scene.render(), current.camera.render() ); 25 | } 26 | 27 | 28 | return { 29 | update: _update, 30 | render: _render 31 | } 32 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "electron-react", 3 | "version": "0.6.1", 4 | "description": "Zajal Creative Coding Experiment", 5 | "main": "main.js", 6 | "dependencies": { 7 | "chokidar": "^1.6.1", 8 | "clojurescript": "^0.0.11", 9 | "pixi.js": "4.3.5", 10 | "three": "0.84.0" 11 | }, 12 | "devDependencies": {}, 13 | "scripts": { 14 | "test": "echo \"Error: no test specified\" && exit 1" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "git+https://github.com/nasser/zajal.git" 19 | }, 20 | "keywords": [ 21 | "functional", 22 | "clojurescript", 23 | "lisp", 24 | "creative", 25 | "threejs", 26 | "react" 27 | ], 28 | "author": "Ramsey Nasser", 29 | "license": "MIT", 30 | "bugs": { 31 | "url": "https://github.com/nasser/zajal/issues" 32 | }, 33 | "homepage": "https://github.com/nasser/zajal#readme" 34 | } 35 | -------------------------------------------------------------------------------- /scratch/repl.cljs: -------------------------------------------------------------------------------- 1 | (require '[zajal.three :as t]) 2 | (require '[zajal.core :as z]) 3 | 4 | (defn step [x] 5 | (update x :angle #(+ 0.01 %))) 6 | 7 | (def box-geo (js/THREE.BoxGeometry. 0.1 0.1 0.1)) 8 | (def mesh-mat (js/THREE.MeshBasicMaterial. 9 | #js {:wireframe true 10 | :color "white"})) 11 | 12 | (defn box [x y z r] 13 | (t/mesh 14 | #js {:position (js/THREE.Vector3. x y z) 15 | :rotation (js/THREE.Euler. 0 r 0) 16 | :geometry box-geo 17 | :material mesh-mat})) 18 | 19 | 20 | (defn draw [{:keys [angle]}] 21 | (t/rednerer 22 | {} 23 | (t/perspectiveCamera 24 | #js {:position (js/THREE.Vector3. 0 0 3)}) 25 | (t/scene 26 | {} 27 | (into-array 28 | (for [x (range -8 8) 29 | y (range -8 8)] 30 | (box (/ x 8) (/ y 8) 0 (* x angle))))))) 31 | 32 | (z/sketch {} #'step #'draw) -------------------------------------------------------------------------------- /js/loaders/ctm/license/OpenCTM.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009-2010 Marcus Geelnard 2 | 3 | This software is provided 'as-is', without any express or implied 4 | warranty. In no event will the authors be held liable for any damages 5 | arising from the use of this software. 6 | 7 | Permission is granted to anyone to use this software for any purpose, 8 | including commercial applications, and to alter it and redistribute it 9 | freely, subject to the following restrictions: 10 | 11 | 1. The origin of this software must not be misrepresented; you must not 12 | claim that you wrote the original software. If you use this software 13 | in a product, an acknowledgment in the product documentation would be 14 | appreciated but is not required. 15 | 16 | 2. Altered source versions must be plainly marked as such, and must not 17 | be misrepresented as being the original software. 18 | 19 | 3. This notice may not be removed or altered from any source 20 | distribution. 21 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | zajal 4 | 15 | 16 | 17 |
18 |
loading
19 |
20 | 21 | 22 | 23 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2011-2016 Ramsey Nasser 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /js/loaders/ctm/license/js-lzma.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011 Juan Mellado 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /js/loaders/ctm/license/js-openctm.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011 Juan Mellado 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /js/loaders/KMZLoader.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author mrdoob / http://mrdoob.com/ 3 | */ 4 | 5 | THREE.KMZLoader = function ( manager ) { 6 | 7 | this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager; 8 | 9 | }; 10 | 11 | THREE.KMZLoader.prototype = { 12 | 13 | constructor: THREE.KMZLoader, 14 | 15 | load: function ( url, onLoad, onProgress, onError ) { 16 | 17 | var scope = this; 18 | 19 | var loader = new THREE.XHRLoader( scope.manager ); 20 | loader.setResponseType( 'arraybuffer' ); 21 | loader.load( url, function ( text ) { 22 | 23 | onLoad( scope.parse( text ) ); 24 | 25 | }, onProgress, onError ); 26 | 27 | }, 28 | 29 | parse: function ( data ) { 30 | 31 | var zip = new JSZip( data ); 32 | 33 | // console.log( zip ); 34 | 35 | for ( var name in zip.files ) { 36 | 37 | if ( name.toLowerCase().substr( - 4 ) === '.dae' ) { 38 | 39 | return new THREE.ColladaLoader().parse( zip.file( name ).asText() ); 40 | 41 | } 42 | 43 | } 44 | 45 | console.error( 'KZMLoader: Couldn\'t find .dae file.' ); 46 | 47 | return { 48 | scene: new THREE.Group() 49 | } 50 | 51 | } 52 | 53 | }; 54 | -------------------------------------------------------------------------------- /examples/other.cljs: -------------------------------------------------------------------------------- 1 | (ns examples.other 2 | (:require [zajal.draw.pixi :refer 3 | [sketch v2 v2+]] 4 | [zajal.alt.draw.pixi :as p] 5 | [zajal.input.keyboard :as keyboard])) 6 | 7 | (def init {:count 0}) 8 | 9 | (defn step [state input] 10 | (if (empty? input) 11 | (update state :count inc) 12 | (update state :count + 3))) 13 | 14 | (defn draw [state] 15 | (p/renderer 16 | #js {:width 400 17 | :alpha true 18 | :height 400} 19 | #js [(p/container 20 | nil 21 | (loop [texts #js [] 22 | i 400 23 | j 10] 24 | (.push texts (p/text #js {:text "Hello" 25 | :fill "white" 26 | :fontSize 18 27 | :rotation (* 0.01 (:count state)) 28 | :x i 29 | :y j})) 30 | (cond 31 | (zero? i) (recur texts 400 (dec j)) 32 | (zero? j) texts 33 | :else (recur texts (dec i) j))))])) 34 | 35 | (defonce the-sketch (sketch init #'step #'draw #'keyboard/key-set)) -------------------------------------------------------------------------------- /zajal/core.cljs: -------------------------------------------------------------------------------- 1 | (ns zajal.core) 2 | 3 | 4 | ;; input 5 | 6 | (defonce start-time (js/Date.now)) 7 | (defonce mouse-state (atom {:x 0 :y 0 :pressed false})) 8 | 9 | (js/window.document.addEventListener 10 | "mousemove" 11 | #(swap! mouse-state 12 | assoc 13 | :x (.-pageX %) 14 | :y (.-pageY %))) 15 | 16 | (js/window.document.addEventListener 17 | "mousedown" 18 | #(swap! mouse-state 19 | assoc :pressed? true)) 20 | 21 | (js/window.document.addEventListener 22 | "mouseup" 23 | #(swap! mouse-state 24 | assoc :pressed? false)) 25 | 26 | (defn input [] 27 | (let [t (js/Date.now)] 28 | {:time {:elapsed (- t start-time)} 29 | :mouse @mouse-state })) 30 | 31 | (defn sketch [start step draw] 32 | (let [state (atom start) 33 | renderer (js/renderer (js/document.querySelector "#sketch") 34 | (draw start))] 35 | (letfn [(render-loop [t] 36 | (.requestAnimationFrame js/window render-loop) 37 | (swap! state step (input)) 38 | (.update renderer (draw @state)))] 39 | (render-loop 0) 40 | (.render renderer) 41 | )) 42 | :ok) 43 | 44 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | */bin/* 2 | */obj/* 3 | 4 | ## Xamarin 5 | #User Specific 6 | *.userprefs 7 | *.usertasks 8 | 9 | #Mono Project Files 10 | *.pidb 11 | *.resources 12 | test-results/ 13 | 14 | ## OSX 15 | .DS_Store 16 | .AppleDouble 17 | .LSOverride 18 | 19 | # Icon must end with two \r 20 | Icon 21 | 22 | 23 | # Thumbnails 24 | ._* 25 | 26 | # Files that might appear in the root of a volume 27 | .DocumentRevisions-V100 28 | .fseventsd 29 | .Spotlight-V100 30 | .TemporaryItems 31 | .Trashes 32 | .VolumeIcon.icns 33 | 34 | # Directories potentially created on remote AFP share 35 | .AppleDB 36 | .AppleDesktop 37 | Network Trash Folder 38 | Temporary Items 39 | .apdisk 40 | # Logs 41 | logs 42 | *.log 43 | npm-debug.log* 44 | 45 | # Runtime data 46 | pids 47 | *.pid 48 | *.seed 49 | *.pid.lock 50 | 51 | # Directory for instrumented libs generated by jscoverage/JSCover 52 | lib-cov 53 | 54 | # Coverage directory used by tools like istanbul 55 | coverage 56 | 57 | # nyc test coverage 58 | .nyc_output 59 | 60 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 61 | .grunt 62 | 63 | # node-waf configuration 64 | .lock-wscript 65 | 66 | # Compiled binary addons (http://nodejs.org/api/addons.html) 67 | build/Release 68 | 69 | # Dependency directories 70 | node_modules 71 | jspm_packages 72 | 73 | # Optional npm cache directory 74 | .npm 75 | 76 | # Optional eslint cache 77 | .eslintcache 78 | 79 | # Optional REPL history 80 | .node_repl_history 81 | 82 | # Output of 'npm pack' 83 | *.tgz 84 | -------------------------------------------------------------------------------- /scratch/virtual-vector.js: -------------------------------------------------------------------------------- 1 | //////// CLJS VECTOR SYNTAX //////// 2 | 3 | // copied from virtual-thunk.js 4 | cljs.core.PersistentVector.prototype.type = "vector-thunk"; 5 | 6 | cljs.core.PersistentVector.prototype.fn = function() { 7 | return this.cljs$core$IIndexed$_nth$arity$3(null, 0); 8 | } 9 | 10 | cljs.core.PersistentVector.prototype.args = function() { 11 | return cljs.core.subvec.cljs$core$IFn$_invoke$arity$2(this, 1); 12 | } 13 | 14 | cljs.core.PersistentVector.prototype.evaluate = function() { 15 | if(!this._cache) { 16 | this._cache = cljs.core.apply(this.fn(), this.args()); 17 | } 18 | 19 | return this._cache; 20 | } 21 | 22 | cljs.core.PersistentVector.prototype.render = function() { 23 | return this.evaluate().render(); 24 | } 25 | 26 | function vectorSame(a, b) { 27 | if(cljs.core.count(a) !== cljs.core.count(b)) 28 | return false; 29 | 30 | var same = true; 31 | var i = cljs.core.count(a); 32 | 33 | while(same && i--) { 34 | same = a.cljs$core$IIndexed$_nth$arity$3(null, i) === b.cljs$core$IIndexed$_nth$arity$3(null, i) 35 | } 36 | 37 | return same; 38 | } 39 | 40 | cljs.core.PersistentVector.prototype.reconcile = function(other) { 41 | if(this === other) 42 | return; 43 | 44 | if(this.type !== other.type) 45 | throw "Cannot reconcile " + this.type + " and " + other.type; 46 | 47 | // fn changed, rerun 48 | if(this.fn() !== other.fn()) { 49 | this._cache.reconcile(other.evaluate()); 50 | 51 | // args changed, rerun 52 | } else if(!vectorSame(this.args(), other.args())) { 53 | this._cache.reconcile(other.evaluate()); 54 | 55 | } 56 | 57 | // propagate the cache 58 | other._cache = this._cache; 59 | } -------------------------------------------------------------------------------- /scratch/hello.cljs: -------------------------------------------------------------------------------- 1 | (ns hello 2 | (:require zajal 3 | there)) 4 | 5 | ;; dom functions 6 | (defn vdom-node [name] 7 | (fn [& body] 8 | (if (map? (first body)) 9 | (zajal/node name (clj->js (first body)) (rest body)) 10 | (zajal/node name nil body)))) 11 | 12 | (def p (vdom-node "p")) 13 | (def em (vdom-node "em")) 14 | (def div (vdom-node "div")) 15 | (defn text [& s] 16 | (zajal/text (apply str s))) 17 | 18 | (defn custom-node [name impl] 19 | (fn [& body] 20 | (if (map? (first body)) 21 | (zajal/custom name (clj->js (first body)) (rest body)) 22 | (zajal/custom name nil body))) 23 | ) 24 | 25 | (def scene (vdom-node "scene")) 26 | (def line (vdom-node "line")) 27 | (def object3d (vdom-node "object3d")) 28 | (def mesh (vdom-node "mesh")) 29 | (def three (vdom-node "three")) 30 | 31 | ;; sketch 32 | (def start {:click-count 0}) 33 | 34 | (defn step [state {:keys [mouse]}] 35 | (if (:pressed? mouse) 36 | (update state :click-count inc) 37 | state)) 38 | 39 | (defn draw [{:keys [click-count]}] 40 | (div 41 | (p (text "Here We Go")) 42 | (zajal/custom 43 | {} 44 | (three {:background 0x2C3E50}) 45 | {:diff (fn [a b] 46 | (when-not (identical? (.-tag a) (.-tag b)) 47 | {} 48 | )) 49 | :render (fn [vdom] 50 | (case (.-tag vdom) 51 | "three" (let [gl (js/THREE.WebGLRenderer.)] 52 | (.setSize gl js/window.-innerWidth js/window.-innerHeight) 53 | ; (.. gl -domElement) 54 | ) 55 | "scene" (js/THREE.Scene.) 56 | "line" (js/THREE.Line.) 57 | "mesh" (js/THREE.Mesh.) 58 | "object3d" (js/THREE.Object3D.) 59 | (throw (str "Unknown node type " (.-tag vdom))) 60 | ))} 61 | ))) 62 | 63 | (defonce test-sketch 64 | (zajal/sketch start #'step #'draw)) 65 | -------------------------------------------------------------------------------- /scratch/virtual-thunk.js: -------------------------------------------------------------------------------- 1 | //////// THUNK NODE //////// 2 | 3 | 4 | ThunkNode = function(fn, args) { 5 | this.type = "thunk" 6 | this.fn = fn; 7 | this.args = args; 8 | this._cache = undefined; 9 | } 10 | 11 | ThunkNode.prototype.evaluate = function() { 12 | if(!this._cache) { 13 | this._cache = this.fn.apply(null, this.args); 14 | } 15 | 16 | return this._cache; 17 | } 18 | 19 | ThunkNode.prototype.render = function() { 20 | return this.evaluate().render(); 21 | } 22 | 23 | function arraySame(a, b) { 24 | if(a.length !== b.length) 25 | return false; 26 | 27 | var same = true; 28 | var i = a.length; 29 | 30 | while(same && i--) { 31 | same = a[i] === b[i] 32 | } 33 | 34 | return same; 35 | } 36 | 37 | ThunkNode.prototype.reconcile = function(other) { 38 | if(this === other) 39 | return; 40 | 41 | if(this.type !== other.type) 42 | throw "Cannot reconcile " + this.type + " and " + other.type; 43 | 44 | // fn changed, rerun 45 | if(this.fn !== other.fn) { 46 | this._cache.reconcile(other.evaluate()); 47 | 48 | // args changed, rerun 49 | } else if(!arraySame(this.args, other.args)) { 50 | this._cache.reconcile(other.evaluate()); 51 | 52 | } 53 | 54 | // propagate the cache 55 | other._cache = this._cache; 56 | } 57 | 58 | 59 | //////// API //////// 60 | 61 | goog.provide('zajal.thunk'); 62 | 63 | zajal.thunk.thunk = function(fn, args) { return new ThunkNode(fn, args); } 64 | 65 | 66 | 67 | //////// TEST //////// 68 | 69 | 70 | // function testInner(t, a) { 71 | // return mesh({ 72 | // rotation: {x: 0, y: 0, z:+t * 0.001}, 73 | // position: new THREE.Vector3(a, 0, 0), 74 | // geometry:boxGeo, 75 | // material:meshMat}) 76 | // } 77 | 78 | // function thunkTest(t) { 79 | // return new ThreeRendererNode( 80 | // {}, 81 | // new ThreePerspectiveCameraNode({fov:60, aspect: 1, near:1, far:1000}), 82 | // new ThreeSceneNode({}, 83 | // [thunk(testInner, [1, 0]), 84 | // thunk(testInner, [1, 1]), 85 | // thunk(testInner, [1*2, 2]), 86 | // thunk(testInner, [1*3, 3]), 87 | // thunk(testInner, [t*4, 4]), 88 | // ])); 89 | // } -------------------------------------------------------------------------------- /zajal/draw/pixi.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | import pixi = require("pixi.js"); 4 | 5 | namespace Zajal { 6 | function applyProperties(obj, props) { 7 | for (var k in props) { 8 | obj[k] = props[k]; 9 | } 10 | } 11 | 12 | interface IVirtual { 13 | parent: IVirtual; 14 | pointer: T; 15 | render(): T; 16 | } 17 | 18 | export class Virtual implements IVirtual { 19 | parent: IVirtual; 20 | pointer: T; 21 | props; 22 | children: Container[]; 23 | type = ""; 24 | 25 | constructor (props?, children?) { 26 | this.props = props || {}; 27 | this.children = children || []; 28 | } 29 | 30 | render() { return this.pointer; } 31 | reconcile(other:Virtual) { 32 | if(this.type != other.type) { 33 | // replace child 34 | } else { 35 | // same type, apply props 36 | applyProperties(this.render(), other.props); 37 | this.reconcileChildren(other); 38 | } 39 | } 40 | 41 | reconcileChildren(other:Virtual) { 42 | let minLength = Math.min(this.children.length, other.children.length); 43 | for (var i = 0; i < minLength; i++) { 44 | this.children[i].reconcile(other.children[i]); 45 | } 46 | if(other.children.length > this.children.length) { 47 | // more children 48 | for (var i = minLength; i < other.children.length; i++) { 49 | // this.pointer.a 50 | } 51 | } 52 | } 53 | } 54 | 55 | export class Applicaiton extends Virtual { 56 | stage = new Container(); 57 | type = "application"; 58 | 59 | // seems faster 60 | constructor (props?, children?) { super(props, children); } 61 | 62 | render() { 63 | if(!this.pointer) { 64 | this.pointer = new pixi.Application(this.props); 65 | for (var i = 0; i < this.children.length; ++i) { 66 | this.pointer.stage.addChild(this.children[i].render()); 67 | this.children[i].parent = this.stage; 68 | } 69 | } 70 | 71 | return this.pointer; 72 | } 73 | } 74 | 75 | export class Container extends Virtual { 76 | render() { 77 | if(!this.pointer) { 78 | this.pointer = new pixi.Container(); 79 | applyProperties(this.pointer, this.props); 80 | for (var i = 0; i < this.children.length; ++i) { 81 | this.pointer.addChild(this.children[i].render()); 82 | this.children[i].parent = this; 83 | } 84 | } 85 | 86 | return this.pointer; 87 | } 88 | } 89 | 90 | export class Sprite extends Container { 91 | render() { 92 | if(!this.pointer) { 93 | this.pointer = pixi.Sprite.fromImage(this.props.image); 94 | applyProperties(this.pointer, this.props); 95 | for (var i = 0; i < this.children.length; ++i) { 96 | this.pointer.addChild(this.children[i].render()); 97 | this.children[i].parent = this; 98 | } 99 | } 100 | 101 | return this.pointer; 102 | } 103 | } 104 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Zajal 0.6 - Atlantic 2 | ==================== 3 | Atlantic is the next major rewrite of the Zajal creative coding framework. It is built on [ClojureScript](http://clojurescript.org), [Pixi.js](http://www.pixijs.com/), [ThreeJS](https://threejs.org/) and the ongoing progress of the [Arcadia project](https://github.com/arcadia-unity/arcadia). The goal, as always, is to create a highly expressive, completely live creative coding experience suitable for beginners and advanced programmers alike. 4 | 5 | This fourth rewrite, continues the exploration of Clojure and Lisp in the service of creative coding, but builds on a web-based stack of functional reactive tools that have become practical in recent years. This incarnation of Zajal, like the previous one and the Arcadia project, experiments with the effects of functional programming on high performance interactive graphics programming. The hope is to provide a real semantic alternative to the imperative painters-algorithm style tools available today. 6 | 7 | Status 8 | ------ 9 | Extremely early, nothing beyond the basic concept has been demonstrated to work. 10 | 11 | ```clojure 12 | (defn step [x] (inc x)) 13 | 14 | (defn draw [t] 15 | (renderer 16 | {:width 400 17 | :height 400} 18 | [(graphics {:shape (circle 20) 19 | :line-width 4 20 | :line-color 0xffffff 21 | :x 100 22 | :y 100}) 23 | (graphics {:shape (polygon [0 -15 -10 10 0 5 10 10 0 -15]) 24 | :fill 0 25 | :line-width 1 26 | :line-color 0xffffff 27 | :x 100 28 | :y 75 29 | :rotation (* 0.05 t)}) 30 | (text {:text "Zajal!" 31 | :fill "white" 32 | :x 50}) 33 | (graphics {:shape (polygon [0 -15 -10 10 0 5 10 10 0 -15]) 34 | :fill 0 35 | :line-width 1 36 | :line-color 0xffffff 37 | :x 100 38 | :y 125 39 | :rotation (* 0.05 t) 40 | })])) 41 | 42 | (sketch 0 #'step #'draw) 43 | ``` 44 | 45 | ![](https://i.imgur.com/OfVhDOH.png) 46 | 47 | 48 | Plan 49 | ---- 50 | The trajectory of the project is towards a functional approach to creative coding that uses a "virtual scene graph" representation akin to [React's](https://facebook.github.io/react/). The current implementation is faster than any other similar technology for our purposes and can target the HTML DOM, ThreeJS scene graph, Pixi,js scene graph, or any other mutable tree-like data structure. The current prototype a minimal [JavaScript Electron](http://electron.atom.io/) application that loads ClojureScript and sets up a socket REPL and filewatcher. 51 | 52 | Using 53 | ----- 54 | [Electron](http://electron.atom.io/) needs to be installed. 55 | 56 | git clone https://github.com/nasser/zajal.git 57 | cd zajal 58 | git checkout atlantic 59 | npm install 60 | electron . zajal/draw/pixi.cljs 61 | 62 | Legal 63 | ----- 64 | Zajal is a labor of love by [Ramsey Nasser](http://nas.sr/). Use it for good, not evil. 65 | 66 | Provided under the MIT License. 67 | 68 | Support 69 | ------- 70 | This project has been generously supported by the following institutions. They believed in it, challenged it, and pushed it forward. Zajal would be nowhere without them, and I thank them all deeply. 71 | 72 | - [Parsons The New School for Design](http://amt.parsons.edu/), where Zajal was my thesis project towards an MFA in Design and Technology 73 | - [Karaj Beirut](http://www.karajbeirut.org/), where Zajal was the focus of my residency 74 | - [Eyebeam Art and Technology Center](http://eyebeam.org/), where Zajal was part of my fellowship exploring code as self expression -------------------------------------------------------------------------------- /js/loaders/PlayCanvasLoader.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author mrdoob / http://mrdoob.com/ 3 | */ 4 | 5 | THREE.PlayCanvasLoader = function ( manager ) { 6 | 7 | this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager; 8 | 9 | }; 10 | 11 | THREE.PlayCanvasLoader.prototype = { 12 | 13 | constructor: THREE.PlayCanvasLoader, 14 | 15 | load: function ( url, onLoad, onProgress, onError ) { 16 | 17 | var scope = this; 18 | 19 | var loader = new THREE.XHRLoader( scope.manager ); 20 | loader.load( url, function ( text ) { 21 | 22 | onLoad( scope.parse( JSON.parse( text ) ) ); 23 | 24 | }, onProgress, onError ); 25 | 26 | }, 27 | 28 | parse: function ( json ) { 29 | 30 | function parseVertices( data ) { 31 | 32 | var attributes = {}; 33 | 34 | for ( var name in data ) { 35 | 36 | var attribute = data[ name ]; 37 | 38 | var type = attribute.type; 39 | var size = attribute.components; 40 | 41 | var array; 42 | 43 | if ( type === 'float32' ) array = new Float32Array( attribute.data ); 44 | if ( array === undefined ) console.log( 'PlayCanvasLoader: TODO', type ); 45 | 46 | attributes[ name ] = new THREE.BufferAttribute( array, size ); 47 | 48 | } 49 | 50 | data._attributes = attributes; 51 | 52 | } 53 | 54 | function parseMeshes( data ) { 55 | 56 | var geometry = new THREE.BufferGeometry(); 57 | 58 | geometry.setIndex( new THREE.Uint16Attribute( data.indices, 1 ) ); 59 | 60 | var attributes = model.vertices[ data.vertices ]._attributes; 61 | 62 | for ( var name in attributes ) { 63 | 64 | var attribute = attributes[ name ]; 65 | 66 | if ( name === 'texCoord0' ) name = 'uv'; 67 | 68 | geometry.addAttribute( name, attribute ); 69 | 70 | } 71 | 72 | data._geometry = geometry; 73 | 74 | } 75 | 76 | function parseMeshInstances( data ) { 77 | 78 | var node = model.nodes[ data.node ]; 79 | var mesh = model.meshes[ data.mesh ]; 80 | 81 | if ( node._geometries === undefined ) { 82 | 83 | node._geometries = []; 84 | 85 | } 86 | 87 | node._geometries.push( mesh._geometry ); 88 | 89 | } 90 | 91 | function parseNodes( data ) { 92 | 93 | var object = new THREE.Group(); 94 | object.name = data.name; 95 | 96 | if ( data._geometries !== undefined ) { 97 | 98 | var material = new THREE.MeshPhongMaterial(); 99 | 100 | for ( var i = 0; i < data._geometries.length; i ++ ) { 101 | 102 | var geometry = data._geometries[ i ]; 103 | 104 | object.add( new THREE.Mesh( geometry, material ) ); 105 | 106 | } 107 | 108 | } 109 | 110 | for ( var i = 0; i < data.rotation.length; i ++ ) { 111 | 112 | data.rotation[ i ] *= Math.PI / 180; 113 | 114 | } 115 | 116 | object.position.fromArray( data.position ); 117 | object.rotation.fromArray( data.rotation ); 118 | object.scale.fromArray( data.scale ); 119 | 120 | data._object = object; 121 | 122 | } 123 | 124 | // 125 | 126 | console.log( json ); 127 | 128 | var model = json.model; 129 | 130 | for ( var i = 0; i < model.vertices.length; i ++ ) { 131 | 132 | parseVertices( model.vertices[ i ] ); 133 | 134 | } 135 | 136 | for ( var i = 0; i < model.meshes.length; i ++ ) { 137 | 138 | parseMeshes( model.meshes[ i ] ); 139 | 140 | } 141 | 142 | for ( var i = 0; i < model.meshInstances.length; i ++ ) { 143 | 144 | parseMeshInstances( model.meshInstances[ i ] ); 145 | 146 | } 147 | 148 | for ( var i = 0; i < model.nodes.length; i ++ ) { 149 | 150 | parseNodes( model.nodes[ i ] ); 151 | 152 | } 153 | 154 | for ( var i = 0; i < model.parents.length; i ++ ) { 155 | 156 | var parent = model.parents[ i ]; 157 | 158 | if ( parent === -1 ) continue; 159 | 160 | model.nodes[ parent ]._object.add( model.nodes[ i ]._object ); 161 | 162 | 163 | } 164 | 165 | return model.nodes[ 0 ]._object; 166 | 167 | } 168 | 169 | }; 170 | -------------------------------------------------------------------------------- /zajal/draw/pixi.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | /// 3 | var __extends = (this && this.__extends) || (function () { 4 | var extendStatics = Object.setPrototypeOf || 5 | ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || 6 | function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; 7 | return function (d, b) { 8 | extendStatics(d, b); 9 | function __() { this.constructor = d; } 10 | d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); 11 | }; 12 | })(); 13 | exports.__esModule = true; 14 | var pixi = require("pixi.js"); 15 | var Zajal; 16 | (function (Zajal) { 17 | function applyProperties(obj, props) { 18 | for (var k in props) { 19 | obj[k] = props[k]; 20 | } 21 | } 22 | var Virtual = (function () { 23 | function Virtual(props, children) { 24 | this.type = ""; 25 | this.props = props || {}; 26 | this.children = children || []; 27 | } 28 | Virtual.prototype.render = function () { return this.pointer; }; 29 | Virtual.prototype.reconcile = function (other) { 30 | if (this.type != other.type) { 31 | // replace child 32 | } 33 | else { 34 | // same type, apply props 35 | applyProperties(this.render(), other.props); 36 | } 37 | }; 38 | return Virtual; 39 | }()); 40 | Zajal.Virtual = Virtual; 41 | var Applicaiton = (function (_super) { 42 | __extends(Applicaiton, _super); 43 | // seems faster 44 | function Applicaiton(props, children) { 45 | var _this = _super.call(this, props, children) || this; 46 | _this.stage = new Container(); 47 | _this.type = "application"; 48 | return _this; 49 | } 50 | Applicaiton.prototype.render = function () { 51 | if (!this.pointer) { 52 | this.pointer = new pixi.Application(this.props); 53 | for (var i = 0; i < this.children.length; ++i) { 54 | this.pointer.stage.addChild(this.children[i].render()); 55 | this.children[i].parent = this.stage; 56 | } 57 | } 58 | return this.pointer; 59 | }; 60 | return Applicaiton; 61 | }(Virtual)); 62 | Zajal.Applicaiton = Applicaiton; 63 | var Container = (function (_super) { 64 | __extends(Container, _super); 65 | function Container() { 66 | return _super !== null && _super.apply(this, arguments) || this; 67 | } 68 | Container.prototype.render = function () { 69 | if (!this.pointer) { 70 | this.pointer = new pixi.Container(); 71 | applyProperties(this.pointer, this.props); 72 | for (var i = 0; i < this.children.length; ++i) { 73 | this.pointer.addChild(this.children[i].render()); 74 | this.children[i].parent = this; 75 | } 76 | } 77 | return this.pointer; 78 | }; 79 | return Container; 80 | }(Virtual)); 81 | Zajal.Container = Container; 82 | var Sprite = (function (_super) { 83 | __extends(Sprite, _super); 84 | function Sprite() { 85 | return _super !== null && _super.apply(this, arguments) || this; 86 | } 87 | Sprite.prototype.render = function () { 88 | if (!this.pointer) { 89 | this.pointer = pixi.Sprite.fromImage(this.props.image); 90 | applyProperties(this.pointer, this.props); 91 | for (var i = 0; i < this.children.length; ++i) { 92 | this.pointer.addChild(this.children[i].render()); 93 | this.children[i].parent = this; 94 | } 95 | } 96 | return this.pointer; 97 | }; 98 | return Sprite; 99 | }(Container)); 100 | Zajal.Sprite = Sprite; 101 | })(Zajal || (Zajal = {})); 102 | -------------------------------------------------------------------------------- /js/loaders/collada/AnimationHandler.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author mikael emtinger / http://gomo.se/ 3 | */ 4 | 5 | THREE.AnimationHandler = { 6 | 7 | LINEAR: 0, 8 | CATMULLROM: 1, 9 | CATMULLROM_FORWARD: 2, 10 | 11 | // 12 | 13 | add: function () { 14 | 15 | console.warn( 'THREE.AnimationHandler.add() has been deprecated.' ); 16 | 17 | }, 18 | get: function () { 19 | 20 | console.warn( 'THREE.AnimationHandler.get() has been deprecated.' ); 21 | 22 | }, 23 | remove: function () { 24 | 25 | console.warn( 'THREE.AnimationHandler.remove() has been deprecated.' ); 26 | 27 | }, 28 | 29 | // 30 | 31 | animations: [], 32 | 33 | init: function ( data ) { 34 | 35 | if ( data.initialized === true ) return data; 36 | 37 | // loop through all keys 38 | 39 | for ( var h = 0; h < data.hierarchy.length; h ++ ) { 40 | 41 | for ( var k = 0; k < data.hierarchy[ h ].keys.length; k ++ ) { 42 | 43 | // remove minus times 44 | 45 | if ( data.hierarchy[ h ].keys[ k ].time < 0 ) { 46 | 47 | data.hierarchy[ h ].keys[ k ].time = 0; 48 | 49 | } 50 | 51 | // create quaternions 52 | 53 | if ( data.hierarchy[ h ].keys[ k ].rot !== undefined && 54 | ! ( data.hierarchy[ h ].keys[ k ].rot instanceof THREE.Quaternion ) ) { 55 | 56 | var quat = data.hierarchy[ h ].keys[ k ].rot; 57 | data.hierarchy[ h ].keys[ k ].rot = new THREE.Quaternion().fromArray( quat ); 58 | 59 | } 60 | 61 | } 62 | 63 | // prepare morph target keys 64 | 65 | if ( data.hierarchy[ h ].keys.length && data.hierarchy[ h ].keys[ 0 ].morphTargets !== undefined ) { 66 | 67 | // get all used 68 | 69 | var usedMorphTargets = {}; 70 | 71 | for ( var k = 0; k < data.hierarchy[ h ].keys.length; k ++ ) { 72 | 73 | for ( var m = 0; m < data.hierarchy[ h ].keys[ k ].morphTargets.length; m ++ ) { 74 | 75 | var morphTargetName = data.hierarchy[ h ].keys[ k ].morphTargets[ m ]; 76 | usedMorphTargets[ morphTargetName ] = - 1; 77 | 78 | } 79 | 80 | } 81 | 82 | data.hierarchy[ h ].usedMorphTargets = usedMorphTargets; 83 | 84 | 85 | // set all used on all frames 86 | 87 | for ( var k = 0; k < data.hierarchy[ h ].keys.length; k ++ ) { 88 | 89 | var influences = {}; 90 | 91 | for ( var morphTargetName in usedMorphTargets ) { 92 | 93 | for ( var m = 0; m < data.hierarchy[ h ].keys[ k ].morphTargets.length; m ++ ) { 94 | 95 | if ( data.hierarchy[ h ].keys[ k ].morphTargets[ m ] === morphTargetName ) { 96 | 97 | influences[ morphTargetName ] = data.hierarchy[ h ].keys[ k ].morphTargetsInfluences[ m ]; 98 | break; 99 | 100 | } 101 | 102 | } 103 | 104 | if ( m === data.hierarchy[ h ].keys[ k ].morphTargets.length ) { 105 | 106 | influences[ morphTargetName ] = 0; 107 | 108 | } 109 | 110 | } 111 | 112 | data.hierarchy[ h ].keys[ k ].morphTargetsInfluences = influences; 113 | 114 | } 115 | 116 | } 117 | 118 | 119 | // remove all keys that are on the same time 120 | 121 | for ( var k = 1; k < data.hierarchy[ h ].keys.length; k ++ ) { 122 | 123 | if ( data.hierarchy[ h ].keys[ k ].time === data.hierarchy[ h ].keys[ k - 1 ].time ) { 124 | 125 | data.hierarchy[ h ].keys.splice( k, 1 ); 126 | k --; 127 | 128 | } 129 | 130 | } 131 | 132 | 133 | // set index 134 | 135 | for ( var k = 0; k < data.hierarchy[ h ].keys.length; k ++ ) { 136 | 137 | data.hierarchy[ h ].keys[ k ].index = k; 138 | 139 | } 140 | 141 | } 142 | 143 | data.initialized = true; 144 | 145 | return data; 146 | 147 | }, 148 | 149 | parse: function ( root ) { 150 | 151 | var parseRecurseHierarchy = function ( root, hierarchy ) { 152 | 153 | hierarchy.push( root ); 154 | 155 | for ( var c = 0; c < root.children.length; c ++ ) 156 | parseRecurseHierarchy( root.children[ c ], hierarchy ); 157 | 158 | }; 159 | 160 | // setup hierarchy 161 | 162 | var hierarchy = []; 163 | 164 | if ( root instanceof THREE.SkinnedMesh ) { 165 | 166 | for ( var b = 0; b < root.skeleton.bones.length; b ++ ) { 167 | 168 | hierarchy.push( root.skeleton.bones[ b ] ); 169 | 170 | } 171 | 172 | } else { 173 | 174 | parseRecurseHierarchy( root, hierarchy ); 175 | 176 | } 177 | 178 | return hierarchy; 179 | 180 | }, 181 | 182 | play: function ( animation ) { 183 | 184 | if ( this.animations.indexOf( animation ) === - 1 ) { 185 | 186 | this.animations.push( animation ); 187 | 188 | } 189 | 190 | }, 191 | 192 | stop: function ( animation ) { 193 | 194 | var index = this.animations.indexOf( animation ); 195 | 196 | if ( index !== - 1 ) { 197 | 198 | this.animations.splice( index, 1 ); 199 | 200 | } 201 | 202 | }, 203 | 204 | update: function ( deltaTimeMS ) { 205 | 206 | for ( var i = 0; i < this.animations.length; i ++ ) { 207 | 208 | this.animations[ i ].resetBlendWeights(); 209 | 210 | } 211 | 212 | for ( var i = 0; i < this.animations.length; i ++ ) { 213 | 214 | this.animations[ i ].update( deltaTimeMS ); 215 | 216 | } 217 | 218 | } 219 | 220 | }; 221 | -------------------------------------------------------------------------------- /scratch/virtual-pixi.js: -------------------------------------------------------------------------------- 1 | //////// PIXI //////// 2 | 3 | function applyProps(obj, props) { 4 | for (var propKey in props) { 5 | obj[propKey] = props[propKey]; 6 | } 7 | } 8 | 9 | function reconcile(self, other) { 10 | if(self.type != other.type) { 11 | var index = self._cache.parent.getChildIndex(self._cache); 12 | self._cache.parent.removeChildAt(index) 13 | self._cache.parent.addChildAt(other.render(), index); 14 | } else { 15 | applyProps(self._cache, other.props) 16 | other._cache = self._cache; 17 | } 18 | } 19 | 20 | function reconcileChildren(self, other) { 21 | var selfChildCount = (self.children && self.children.length || 0); 22 | var otherChildCount = (other.children && other.children.length || 0); 23 | var minLength = Math.min(selfChildCount, otherChildCount); 24 | for (var i = 0; i < minLength; i++) { 25 | self.children[i].reconcile(other.children[i]); 26 | } 27 | if(otherChildCount > selfChildCount) { 28 | // more elements 29 | for(var i = minLength; i< otherChildCount; i++) { 30 | self._cache.addChild(other.children[i].render()); 31 | } 32 | } else { 33 | // fewer elements 34 | for(var i = minLength; i < selfChildCount; i++) { 35 | self._cache.removeChild(self.children[i].render()); 36 | } 37 | } 38 | } 39 | 40 | function ApplicationNode(props, children) { 41 | this.type = "pixi-application" 42 | this.props = props; 43 | this.children = children; 44 | this._cache = undefined; 45 | } 46 | 47 | ApplicationNode.prototype.render = function() { 48 | if(!this._cache) { 49 | // create and cache node 50 | this._cache = new PIXI.Application(this.props.width || 800, this.props.height || 600, this.props); 51 | 52 | // normalize interface 53 | this._cache.removeChild = function(c) { return this._cache.stage.removeChild(c); } 54 | this._cache.addChild = function(c) { return this._cache.stage.addChild(c); } 55 | 56 | // render and add children 57 | var childCount = this.children.length; 58 | for (var i = 0; i < childCount; i++) { 59 | this._cache.stage.addChild(this.children[i].render()); 60 | } 61 | } 62 | 63 | return this._cache; 64 | } 65 | 66 | ApplicationNode.prototype.reconcile = function(other) { 67 | if(this.type != other.type) 68 | throw "Different types not supported " + this.type + " vs " + other.type; 69 | reconcile(this, other); 70 | reconcileChildren(this, other); 71 | } 72 | 73 | function DisplayObjectNode(ctor, props, children) { 74 | this.type = "pixi-displayobject" 75 | this.ctor = ctor; 76 | this.props = props; 77 | this.children = children; 78 | this._cache = undefined; 79 | } 80 | 81 | DisplayObjectNode.prototype.render = function() { 82 | if(!this._cache) { 83 | this._cache = Object.create(this.ctor.prototype); 84 | this.ctor.call(this._cache); 85 | applyProps(this._cache, this.props) 86 | if(this.children) { 87 | var childCount = this.children.length; 88 | for (var i = 0; i < childCount; i++) { 89 | this._cache.addChild(this.children[i].render()); 90 | } 91 | } 92 | 93 | } 94 | return this._cache; 95 | } 96 | 97 | DisplayObjectNode.prototype.reconcile = function(other) { 98 | if(this === other) return; // ??? 99 | reconcile(this, other); 100 | reconcileChildren(this, other); 101 | }; 102 | 103 | function SpriteNode(props, children) { 104 | this.type = "pixi-sprite" 105 | this.props = props; 106 | this.children = children; 107 | this._cache = undefined; 108 | } 109 | 110 | SpriteNode.prototype.render = function() { 111 | if(!this._cache) { 112 | this._cache = PIXI.Sprite.fromImage(this.props.image); 113 | applyProps(this._cache, this.props) 114 | if(this.children) { 115 | var childCount = this.children.length; 116 | for (var i = 0; i < childCount; i++) { 117 | this._cache.addChild(this.children[i].render()); 118 | } 119 | } 120 | 121 | } 122 | return this._cache; 123 | } 124 | 125 | SpriteNode.prototype.reconcile = function(other) { 126 | if(this === other) return; // ??? 127 | reconcile(this, other); 128 | reconcileChildren(this, other); 129 | }; 130 | 131 | function TextNode(props, children) { 132 | this.type = "pixi-text" 133 | this.props = props; 134 | this.children = children; 135 | this._cache = undefined; 136 | } 137 | 138 | TextNode.prototype.render = function() { 139 | if(!this._cache) { 140 | this._cache = new PIXI.Text(); 141 | applyProps(this._cache, this.props); 142 | applyProps(this._cache.style, this.props); 143 | if(this.children) { 144 | var childCount = this.children.length; 145 | for (var i = 0; i < childCount; i++) { 146 | this._cache.addChild(this.children[i].render()); 147 | } 148 | } 149 | 150 | } 151 | return this._cache; 152 | } 153 | 154 | TextNode.prototype.reconcile = function(other) { 155 | if(this === other) return; // ??? 156 | reconcile(this, other); 157 | applyProps(this._cache.style, this.props); 158 | reconcileChildren(this, other); 159 | }; 160 | 161 | 162 | 163 | goog.provide('zajal.alt.draw.pixi'); 164 | 165 | zajal.alt.draw.pixi.renderer = function(props, children) { return new ApplicationNode(props, children); } 166 | zajal.alt.draw.pixi.object = function(ctor, props, children) { return new DisplayObjectNode(ctor, props, children); } 167 | zajal.alt.draw.pixi.container = function(props, children) { return zajal.alt.draw.pixi.object(PIXI.Container, props, children); } 168 | zajal.alt.draw.pixi.sprite = function(props, children) { return new SpriteNode(props, children); } 169 | zajal.alt.draw.pixi.text = function(props, children) { return new TextNode(props, children); } 170 | -------------------------------------------------------------------------------- /js/loaders/collada/KeyFrameAnimation.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author mikael emtinger / http://gomo.se/ 3 | * @author mrdoob / http://mrdoob.com/ 4 | * @author alteredq / http://alteredqualia.com/ 5 | * @author khang duong 6 | * @author erik kitson 7 | */ 8 | 9 | THREE.KeyFrameAnimation = function ( data ) { 10 | 11 | this.root = data.node; 12 | this.data = THREE.AnimationHandler.init( data ); 13 | this.hierarchy = THREE.AnimationHandler.parse( this.root ); 14 | this.currentTime = 0; 15 | this.timeScale = 0.001; 16 | this.isPlaying = false; 17 | this.isPaused = true; 18 | this.loop = true; 19 | 20 | // initialize to first keyframes 21 | 22 | for ( var h = 0, hl = this.hierarchy.length; h < hl; h ++ ) { 23 | 24 | var keys = this.data.hierarchy[ h ].keys, 25 | sids = this.data.hierarchy[ h ].sids, 26 | obj = this.hierarchy[ h ]; 27 | 28 | if ( keys.length && sids ) { 29 | 30 | for ( var s = 0; s < sids.length; s ++ ) { 31 | 32 | var sid = sids[ s ], 33 | next = this.getNextKeyWith( sid, h, 0 ); 34 | 35 | if ( next ) { 36 | 37 | next.apply( sid ); 38 | 39 | } 40 | 41 | } 42 | 43 | obj.matrixAutoUpdate = false; 44 | this.data.hierarchy[ h ].node.updateMatrix(); 45 | obj.matrixWorldNeedsUpdate = true; 46 | 47 | } 48 | 49 | } 50 | 51 | }; 52 | 53 | THREE.KeyFrameAnimation.prototype = { 54 | 55 | constructor: THREE.KeyFrameAnimation, 56 | 57 | play: function ( startTime ) { 58 | 59 | this.currentTime = startTime !== undefined ? startTime : 0; 60 | 61 | if ( this.isPlaying === false ) { 62 | 63 | this.isPlaying = true; 64 | 65 | // reset key cache 66 | 67 | var h, hl = this.hierarchy.length, 68 | object, 69 | node; 70 | 71 | for ( h = 0; h < hl; h ++ ) { 72 | 73 | object = this.hierarchy[ h ]; 74 | node = this.data.hierarchy[ h ]; 75 | 76 | if ( node.animationCache === undefined ) { 77 | 78 | node.animationCache = {}; 79 | node.animationCache.prevKey = null; 80 | node.animationCache.nextKey = null; 81 | node.animationCache.originalMatrix = object.matrix; 82 | 83 | } 84 | 85 | var keys = this.data.hierarchy[ h ].keys; 86 | 87 | if ( keys.length > 1 ) { 88 | 89 | node.animationCache.prevKey = keys[ 0 ]; 90 | node.animationCache.nextKey = keys[ 1 ]; 91 | 92 | this.startTime = Math.min( keys[ 0 ].time, this.startTime ); 93 | this.endTime = Math.max( keys[ keys.length - 1 ].time, this.endTime ); 94 | 95 | } 96 | 97 | } 98 | 99 | this.update( 0 ); 100 | 101 | } 102 | 103 | this.isPaused = false; 104 | }, 105 | 106 | stop: function () { 107 | 108 | this.isPlaying = false; 109 | this.isPaused = false; 110 | 111 | // reset JIT matrix and remove cache 112 | 113 | for ( var h = 0; h < this.data.hierarchy.length; h ++ ) { 114 | 115 | var obj = this.hierarchy[ h ]; 116 | var node = this.data.hierarchy[ h ]; 117 | 118 | if ( node.animationCache !== undefined ) { 119 | 120 | var original = node.animationCache.originalMatrix; 121 | 122 | original.copy( obj.matrix ); 123 | obj.matrix = original; 124 | 125 | delete node.animationCache; 126 | 127 | } 128 | 129 | } 130 | 131 | }, 132 | 133 | update: function ( delta ) { 134 | 135 | if ( this.isPlaying === false ) return; 136 | 137 | this.currentTime += delta * this.timeScale; 138 | 139 | // 140 | 141 | var duration = this.data.length; 142 | 143 | if ( this.loop === true && this.currentTime > duration ) { 144 | 145 | this.currentTime %= duration; 146 | 147 | } 148 | 149 | this.currentTime = Math.min( this.currentTime, duration ); 150 | 151 | for ( var h = 0, hl = this.hierarchy.length; h < hl; h ++ ) { 152 | 153 | var object = this.hierarchy[ h ]; 154 | var node = this.data.hierarchy[ h ]; 155 | 156 | var keys = node.keys, 157 | animationCache = node.animationCache; 158 | 159 | 160 | if ( keys.length ) { 161 | 162 | var prevKey = animationCache.prevKey; 163 | var nextKey = animationCache.nextKey; 164 | 165 | if ( nextKey.time <= this.currentTime ) { 166 | 167 | while ( nextKey.time < this.currentTime && nextKey.index > prevKey.index ) { 168 | 169 | prevKey = nextKey; 170 | nextKey = keys[ prevKey.index + 1 ]; 171 | 172 | } 173 | 174 | animationCache.prevKey = prevKey; 175 | animationCache.nextKey = nextKey; 176 | 177 | } 178 | 179 | if ( nextKey.time >= this.currentTime ) { 180 | 181 | prevKey.interpolate( nextKey, this.currentTime ); 182 | 183 | } else { 184 | 185 | prevKey.interpolate( nextKey, nextKey.time ); 186 | 187 | } 188 | 189 | this.data.hierarchy[ h ].node.updateMatrix(); 190 | object.matrixWorldNeedsUpdate = true; 191 | 192 | } 193 | 194 | } 195 | 196 | }, 197 | 198 | getNextKeyWith: function ( sid, h, key ) { 199 | 200 | var keys = this.data.hierarchy[ h ].keys; 201 | key = key % keys.length; 202 | 203 | for ( ; key < keys.length; key ++ ) { 204 | 205 | if ( keys[ key ].hasTarget( sid ) ) { 206 | 207 | return keys[ key ]; 208 | 209 | } 210 | 211 | } 212 | 213 | return keys[ 0 ]; 214 | 215 | }, 216 | 217 | getPrevKeyWith: function ( sid, h, key ) { 218 | 219 | var keys = this.data.hierarchy[ h ].keys; 220 | key = key >= 0 ? key : key + keys.length; 221 | 222 | for ( ; key >= 0; key -- ) { 223 | 224 | if ( keys[ key ].hasTarget( sid ) ) { 225 | 226 | return keys[ key ]; 227 | 228 | } 229 | 230 | } 231 | 232 | return keys[ keys.length - 1 ]; 233 | 234 | } 235 | 236 | }; 237 | -------------------------------------------------------------------------------- /scratch/virtual-dom.js: -------------------------------------------------------------------------------- 1 | //////// TEXT NODE //////// 2 | 3 | 4 | var DomTextNode = function(text) { 5 | this.type = "text" 6 | this.text = text; 7 | this._cache = undefined; 8 | } 9 | 10 | DomTextNode.prototype.render = function() { 11 | if(!this._cache) { 12 | this._cache = document.createTextNode(this.text); 13 | } 14 | 15 | return this._cache; 16 | } 17 | 18 | DomTextNode.prototype.reconcile = function(other) { 19 | // TODO dont copy/paste 20 | // identical other, noop 21 | if(other === this) 22 | return; 23 | 24 | // different type, bail? 25 | if(other.type !== this.type) { 26 | throw "Cannot reconcile " + this.type + " and " + other.type; 27 | } 28 | 29 | if(other.text !== this.text) { 30 | this._cache.textContent = other.text; 31 | } 32 | 33 | other._cache = this._cache; 34 | } 35 | 36 | 37 | 38 | //////// DOM NODE //////// 39 | 40 | 41 | var DomNode = function(tag, props, children) { 42 | this.type = "dom"; 43 | this.tag = tag; 44 | this.props = props; 45 | this.children = children; 46 | this._cache = undefined; 47 | } 48 | 49 | DomNode.prototype.render = function() { 50 | if(!this._cache) { 51 | this._cache = document.createElement(this.tag); 52 | var c = this.children; 53 | for (var i = 0; i < c.length; i++) { 54 | this._cache.appendChild(c[i].render()); 55 | } 56 | 57 | var thisProps = this.props; 58 | for (var thisKey in thisProps) { 59 | this._cache.setAttribute(thisKey, thisProps[thisKey]); 60 | } 61 | } 62 | 63 | return this._cache; 64 | }; 65 | 66 | DomNode.prototype.reconcile = function(other) { 67 | // identical other, noop 68 | if(other === this) 69 | return; 70 | 71 | // different type, bail? 72 | if(other.type !== this.type) { 73 | throw "Cannot reconcile " + this.type + " and " + other.type; 74 | } 75 | 76 | // different tags, replace this node 77 | // assumes this._cache 78 | if(other.tag !== this.tag) { 79 | var newRoot = other.render(); 80 | this._cache.parentElement.replaceChild(newRoot, this._cache); 81 | return; 82 | } 83 | 84 | // other is a different dom node w/same tag 85 | this.reconcileProps(other); 86 | this.reconcileChildren(other); 87 | 88 | // propagate cache 89 | other._cache = this._cache; 90 | }; 91 | 92 | DomNode.prototype.reconcileProps = function(other) { 93 | var _cache = this._cache 94 | var thisProps = this.props; 95 | var otherProps = other.props; 96 | 97 | // check for changed/removed props 98 | for (var thisKey in thisProps) { 99 | // key missing, remove attribute it 100 | if (!(thisKey in otherProps)) { 101 | _cache.removeAttribute(thisKey); 102 | } 103 | 104 | var thisValue = thisProps[thisKey]; 105 | var otherValue = otherProps[thisKey]; 106 | 107 | // same value, move on 108 | if(thisValue === otherValue) 109 | continue; 110 | 111 | // TODO deep prop diff? 112 | // changed value, update attribute 113 | _cache.setAttribute(thisKey, otherValue); 114 | } 115 | 116 | // check for new props 117 | for (var otherKey in otherProps) { 118 | if (!(otherKey in thisProps)) { 119 | _cache.setAttribute(otherKey, otherProps[otherValue]); 120 | } 121 | } 122 | }; 123 | 124 | // TODO keyed nodes? 125 | DomNode.prototype.reconcileChildren = function(other) { 126 | var thisChildren = this.children; 127 | var otherChildren = other.children; 128 | var minLength = Math.min(thisChildren.length, otherChildren.length); 129 | 130 | for (var i = 0; i < minLength; i++) { 131 | thisChildren[i].reconcile(otherChildren[i]); 132 | }; 133 | 134 | // new elements 135 | if(otherChildren.length > thisChildren.length) { 136 | for (var i = minLength; i < otherChildren.length; i++) { 137 | this._cache.appendChild(otherChildren[i].render()); 138 | }; 139 | 140 | // fewer elements 141 | } else if(thisChildren.length > otherChildren.length) { 142 | for (var i = minLength; i < thisChildren.length; i++) { 143 | this._cache.removeChild(thisChildren[i]._cache); 144 | }; 145 | } 146 | }; 147 | 148 | function createDomNode(tag) { 149 | return function(props, children) { 150 | return new DomNode(tag, props, children) 151 | } 152 | } 153 | 154 | 155 | //////// API //////// 156 | 157 | goog.provide('zajal.dom'); 158 | 159 | zajal.dom.p = createDomNode("p"); 160 | zajal.dom.h1 = createDomNode("h1"); 161 | zajal.dom.h2 = createDomNode("h2"); 162 | zajal.dom.h3 = createDomNode("h3"); 163 | zajal.dom.h4 = createDomNode("h4"); 164 | zajal.dom.h5 = createDomNode("h5"); 165 | zajal.dom.strong = createDomNode("strong"); 166 | zajal.dom.em = createDomNode("em"); 167 | zajal.dom.div = createDomNode("div"); 168 | zajal.dom.span = createDomNode("span"); 169 | zajal.dom.text = function(t) { return new DomTextNode(t.toString()) }; 170 | 171 | 172 | //////// TEST //////// 173 | 174 | 175 | // var domTest = div( 176 | // {class:"pismo"}, 177 | // [h1({}, [text("Here we go")]), 178 | // p({}, [ 179 | // text("lorem impsum "), 180 | // span({}, [text("the jake")]), 181 | // text(" another one")])]) 182 | 183 | // var domTest2 = div( 184 | // {class:"pismo"}, 185 | // [h1({}, [text("Here we go")]), 186 | // p({}, [ 187 | // text("lorem impsum "), 188 | // em({}, [text("the jake")]), 189 | // text(" another one")])]) 190 | 191 | // var domClock = function(t) { 192 | // return div( 193 | // {class:"the-clock"}, 194 | // [h1({}, [text("The time")]), 195 | // p({}, [ 196 | // text("seems to be "), 197 | // t.getSeconds() % 2 == 1 ? 198 | // em({}, [text(t.getMilliseconds())]) : 199 | // span({}, [text(t.getMilliseconds())]), 200 | // text(" o'clock")])]) 201 | // } 202 | -------------------------------------------------------------------------------- /examples/asteroid.cljs: -------------------------------------------------------------------------------- 1 | (ns examples.asteroid 2 | (:require [zajal.draw.pixi :refer 3 | [sketch 4 | v2 v2+ 5 | circle polygon 6 | renderer container sprite graphics text thunk]] 7 | [zajal.input.keyboard :as keyboard])) 8 | 9 | (defn vmap [f coll] 10 | (let [length (count coll)] 11 | (loop [coll* (transient []) 12 | i 0] 13 | (if (< i length) 14 | (recur (conj! coll* (f (nth coll i))) 15 | (inc i)) 16 | (persistent! coll*))))) 17 | 18 | (defn asteroid* [x y] 19 | {:position (v2 x y) 20 | :radius (+ 0.5 (rand)) 21 | :speed (rand) 22 | :variation (inc (js/Math.floor (rand 3)))}) 23 | 24 | (def init 25 | {:player 26 | {:position (v2 0 100) 27 | :rotation 0 28 | :inertia 0} 29 | :asteroids 30 | (vec 31 | (for [x (range -5 5) 32 | y (range -5 5)] 33 | (asteroid* (+ 300 (* x 100)) 34 | (+ 300 (* y 100)))))}) 35 | 36 | (defn horizontal [player input] 37 | (cond (:arrow-right input) 38 | (update player :rotation + (* (:inertia player) 0.05)) 39 | (:arrow-left input) 40 | (update player :rotation - (* (:inertia player) 0.05)) 41 | :else player)) 42 | 43 | (defn vertical [player input] 44 | (let [forward (if (:arrow-up input) 1 0) 45 | rotation (:rotation player) 46 | inertia (:inertia player) 47 | movement (v2 (* (+ forward inertia) (js/Math.cos rotation)) 48 | (* (+ forward inertia) (js/Math.sin rotation)))] 49 | (update player :position v2+ movement))) 50 | 51 | (defn inertia [player input] 52 | (if (and (:arrow-up input) 53 | (< (:inertia player) 54 | 3)) 55 | (update player :inertia + 0.1) 56 | (update player :inertia * 0.9))) 57 | 58 | (defn controls [player input] 59 | (-> player 60 | (inertia input) 61 | (horizontal input) 62 | (vertical input))) 63 | 64 | (defn inside? [point circle] 65 | (let [p1 (:position point) 66 | p2 (:position circle) 67 | r (* 80 (:radius circle)) 68 | distance-squared (+ (* (- (.-x p2) (.-x p1)) 69 | (- (.-x p2) (.-x p1))) 70 | (* (- (.-y p2) (.-y p1)) 71 | (- (.-y p2) (.-y p1)))) 72 | r-sqared (* r r)] 73 | (< distance-squared r-sqared))) 74 | 75 | (defn collision [{:keys [player asteroids] :as state}] 76 | (let [asteroids* (map #(if (inside? player %) 77 | (assoc % :colliding? true) 78 | (assoc % :colliding? false)) 79 | asteroids)] 80 | (-> state 81 | (assoc :asteroids asteroids*) 82 | (update :player assoc :colliding? (some :colliding? asteroids*))))) 83 | 84 | (defn spin [objs] 85 | (map #(update % :rotation + (* 0.1 (:speed %))) objs)) 86 | 87 | (defn step [state input] 88 | (-> state 89 | (update :player controls input) 90 | collision 91 | (update :asteroids spin) 92 | (assoc :input input))) 93 | 94 | (defn debug [state] 95 | (container {} 96 | [(text {:text (str "asteroids: " (:asteroids state)) 97 | :fill "white" 98 | :y 1 99 | :fontSize 8 100 | :fontFamily "Menlo"}) 101 | (text {:text (str "input: " (:input state)) 102 | :fill "white" 103 | :y 10 104 | :fontSize 8 105 | :fontFamily "Menlo"})])) 106 | 107 | (defn draw-ship [position rotation colliding?] 108 | [:container 109 | {:position position 110 | :rotation rotation} 111 | [:sprite 112 | {:image "art/Cars/car_blue_5.png" 113 | ; :cacheAsBitmap true 114 | :tint (if colliding? 0x00ff00 0xffffff) 115 | :anchor (v2 0.5 0.5) 116 | :scale (v2 0.25 0.25) 117 | :rotation 1.57075}]]) 118 | 119 | (defn -draw-asteroid [{:keys [position colliding? radius shape rotation] :as asteroid}] 120 | (container {:position position 121 | :rotation rotation} 122 | [(graphics 123 | {:shape shape 124 | :fill (if colliding? 0x00ff00 0xf0ff0f) 125 | :alpha 1 126 | :line-width 1 127 | :line-color 0xffffff}) 128 | (graphics 129 | {:shape (circle radius) 130 | :alpha 0 131 | :fill 0xffffff 132 | :line-color 0x00ff00 133 | :line-width 1})])) 134 | 135 | (defn draw-asteroid [{:keys [position variation colliding? radius shape rotation] :as asteroid}] 136 | (sprite {:position position 137 | :rotation rotation 138 | :tint (if colliding? 0xff0000 0xffffff) 139 | :scale (v2 radius radius) 140 | :anchor (v2 0.5 0.5) 141 | :image (str "art/Objects/rock" variation ".png")})) 142 | 143 | (defn readout [s] 144 | (text {:text s 145 | :fill "white" 146 | :alpha 1 147 | :x 10 148 | :y 10})) 149 | 150 | 151 | (defn _draw [{:keys [player input] :as state}] 152 | (renderer 153 | {:width 800 154 | :height 800 155 | :resolution 1} 156 | [(container {:position (v2 100 100)} 157 | [(container {} (map draw-asteroid (:asteroids state))) 158 | (draw-ship (:position player) 159 | (:rotation player) 160 | (:colliding? player)) 161 | (thunk readout (str input))])])) 162 | 163 | (defn draw [{:keys [player input] :as state}] 164 | [:renderer {:width 800 165 | :height 800 166 | :resolution 1} 167 | [:container {:position (v2 100 100)} 168 | ; [:container {} (map draw-asteroid (:asteroids state))] 169 | [draw-ship 170 | (:position player) 171 | (:rotation player) 172 | (:colliding? player)] 173 | ; [readout (str input)] 174 | ]]) 175 | 176 | (defonce the-sketch (sketch init #'step #'draw #'keyboard/key-set)) 177 | 178 | -------------------------------------------------------------------------------- /js/loaders/BabylonLoader.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author mrdoob / http://mrdoob.com/ 3 | */ 4 | 5 | THREE.BabylonLoader = function ( manager ) { 6 | 7 | this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager; 8 | 9 | }; 10 | 11 | THREE.BabylonLoader.prototype = { 12 | 13 | constructor: THREE.BabylonLoader, 14 | 15 | load: function ( url, onLoad, onProgress, onError ) { 16 | 17 | var scope = this; 18 | 19 | var loader = new THREE.XHRLoader( scope.manager ); 20 | loader.load( url, function ( text ) { 21 | 22 | onLoad( scope.parse( JSON.parse( text ) ) ); 23 | 24 | }, onProgress, onError ); 25 | 26 | }, 27 | 28 | parse: function ( json ) { 29 | 30 | var materials = this.parseMaterials( json ); 31 | var scene = this.parseObjects( json, materials ); 32 | 33 | return scene; 34 | 35 | }, 36 | 37 | parseMaterials: function ( json ) { 38 | 39 | var materials = {}; 40 | 41 | for ( var i = 0, l = json.materials.length; i < l; i ++ ) { 42 | 43 | var data = json.materials[ i ]; 44 | 45 | var material = new THREE.MeshPhongMaterial(); 46 | material.name = data.name; 47 | material.color.fromArray( data.diffuse ); 48 | material.emissive.fromArray( data.emissive ); 49 | material.specular.fromArray( data.specular ); 50 | material.shininess = data.specularPower; 51 | material.opacity = data.alpha; 52 | 53 | materials[ data.id ] = material; 54 | 55 | } 56 | 57 | if ( json.multiMaterials ) { 58 | 59 | for ( var i = 0, l = json.multiMaterials.length; i < l; i ++ ) { 60 | 61 | var data = json.multiMaterials[ i ]; 62 | 63 | console.warn( 'THREE.BabylonLoader: Multi materials not yet supported.' ); 64 | 65 | materials[ data.id ] = new THREE.MeshPhongMaterial(); 66 | 67 | } 68 | 69 | } 70 | 71 | return materials; 72 | 73 | }, 74 | 75 | parseGeometry: function ( json ) { 76 | 77 | var geometry = new THREE.BufferGeometry(); 78 | 79 | // indices 80 | 81 | var indices = new Uint16Array( json.indices ); 82 | 83 | geometry.setIndex( new THREE.BufferAttribute( indices, 1 ) ); 84 | 85 | // positions 86 | 87 | var positions = new Float32Array( json.positions ); 88 | 89 | for ( var j = 2, jl = positions.length; j < jl; j += 3 ) { 90 | 91 | positions[ j ] = - positions[ j ]; 92 | 93 | } 94 | 95 | geometry.addAttribute( 'position', new THREE.BufferAttribute( positions, 3 ) ); 96 | 97 | // normals 98 | 99 | if ( json.normals ) { 100 | 101 | var normals = new Float32Array( json.normals ); 102 | 103 | for ( var j = 2, jl = normals.length; j < jl; j += 3 ) { 104 | 105 | normals[ j ] = - normals[ j ]; 106 | 107 | } 108 | 109 | geometry.addAttribute( 'normal', new THREE.BufferAttribute( normals, 3 ) ); 110 | 111 | } 112 | 113 | // uvs 114 | 115 | if ( json.uvs ) { 116 | 117 | var uvs = new Float32Array( json.uvs ); 118 | 119 | geometry.addAttribute( 'uv', new THREE.BufferAttribute( uvs, 2 ) ); 120 | 121 | } 122 | 123 | // offsets 124 | 125 | var subMeshes = json.subMeshes; 126 | 127 | if ( subMeshes ) { 128 | 129 | for ( var j = 0, jl = subMeshes.length; j < jl; j ++ ) { 130 | 131 | var subMesh = subMeshes[ j ]; 132 | 133 | geometry.addGroup( subMesh.indexStart, subMesh.indexCount ); 134 | 135 | } 136 | 137 | } 138 | 139 | return geometry; 140 | 141 | }, 142 | 143 | parseObjects: function ( json, materials ) { 144 | 145 | var objects = {}; 146 | var scene = new THREE.Scene(); 147 | 148 | var cameras = json.cameras; 149 | 150 | for ( var i = 0, l = cameras.length; i < l; i ++ ) { 151 | 152 | var data = cameras[ i ]; 153 | 154 | var camera = new THREE.PerspectiveCamera( ( data.fov / Math.PI ) * 180, 1.33, data.minZ, data.maxZ ); 155 | 156 | camera.name = data.name; 157 | camera.position.fromArray( data.position ); 158 | if ( data.rotation ) camera.rotation.fromArray( data.rotation ); 159 | 160 | objects[ data.id ] = camera; 161 | 162 | } 163 | 164 | var lights = json.lights; 165 | 166 | for ( var i = 0, l = lights.length; i < l; i ++ ) { 167 | 168 | var data = lights[ i ]; 169 | 170 | var light; 171 | 172 | switch ( data.type ) { 173 | 174 | case 0: 175 | light = new THREE.PointLight(); 176 | break; 177 | 178 | case 1: 179 | light = new THREE.DirectionalLight(); 180 | break; 181 | 182 | case 2: 183 | light = new THREE.SpotLight(); 184 | break; 185 | 186 | case 3: 187 | light = new THREE.HemisphereLight(); 188 | break; 189 | } 190 | 191 | light.name = data.name; 192 | if ( data.position ) light.position.set( data.position[ 0 ], data.position[ 1 ], - data.position[ 2 ] ); 193 | light.color.fromArray( data.diffuse ); 194 | if ( data.groundColor ) light.groundColor.fromArray( data.groundColor ); 195 | if ( data.intensity ) light.intensity = data.intensity; 196 | 197 | objects[ data.id ] = light; 198 | 199 | scene.add( light ); 200 | 201 | } 202 | 203 | var meshes = json.meshes; 204 | 205 | for ( var i = 0, l = meshes.length; i < l; i ++ ) { 206 | 207 | var data = meshes[ i ]; 208 | 209 | var object; 210 | 211 | if ( data.indices ) { 212 | 213 | var geometry = this.parseGeometry( data ); 214 | 215 | object = new THREE.Mesh( geometry, materials[ data.materialId ] ); 216 | 217 | } else { 218 | 219 | object = new THREE.Group(); 220 | 221 | } 222 | 223 | object.name = data.name; 224 | object.position.set( data.position[ 0 ], data.position[ 1 ], - data.position[ 2 ] ); 225 | object.rotation.fromArray( data.rotation ); 226 | if ( data.rotationQuaternion ) object.quaternion.fromArray( data.rotationQuaternion ); 227 | object.scale.fromArray( data.scaling ); 228 | // object.visible = data.isVisible; 229 | 230 | if ( data.parentId ) { 231 | 232 | objects[ data.parentId ].add( object ); 233 | 234 | } else { 235 | 236 | scene.add( object ); 237 | 238 | } 239 | 240 | objects[ data.id ] = object; 241 | 242 | } 243 | 244 | return scene; 245 | 246 | } 247 | 248 | }; 249 | -------------------------------------------------------------------------------- /js/loaders/gltf/glTFAnimation.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Tony Parisi / http://www.tonyparisi.com/ 3 | */ 4 | 5 | THREE.glTFAnimator = ( function () { 6 | 7 | var animators = []; 8 | 9 | return { 10 | add : function(animator) 11 | { 12 | animators.push(animator); 13 | }, 14 | 15 | remove: function(animator) 16 | { 17 | 18 | var i = animators.indexOf(animator); 19 | 20 | if ( i !== -1 ) { 21 | animators.splice( i, 1 ); 22 | } 23 | }, 24 | 25 | update : function() 26 | { 27 | for (i = 0; i < animators.length; i ++) 28 | { 29 | animators[i].update(); 30 | } 31 | }, 32 | }; 33 | })(); 34 | 35 | // Construction/initialization 36 | THREE.glTFAnimation = function(interps) 37 | { 38 | this.running = false; 39 | this.loop = false; 40 | this.duration = 0; 41 | this.startTime = 0; 42 | this.interps = []; 43 | 44 | if (interps) 45 | { 46 | this.createInterpolators(interps); 47 | } 48 | }; 49 | 50 | THREE.glTFAnimation.prototype.createInterpolators = function(interps) 51 | { 52 | var i, len = interps.length; 53 | for (i = 0; i < len; i ++) 54 | { 55 | var interp = new THREE.glTFInterpolator(interps[i]); 56 | this.interps.push(interp); 57 | this.duration = Math.max(this.duration, interp.duration); 58 | } 59 | }; 60 | 61 | // Start/stop 62 | THREE.glTFAnimation.prototype.play = function() 63 | { 64 | if (this.running) 65 | return; 66 | 67 | this.startTime = Date.now(); 68 | this.running = true; 69 | THREE.glTFAnimator.add(this); 70 | }; 71 | 72 | THREE.glTFAnimation.prototype.stop = function() 73 | { 74 | this.running = false; 75 | THREE.glTFAnimator.remove(this); 76 | }; 77 | 78 | // Update - drive key frame evaluation 79 | THREE.glTFAnimation.prototype.update = function() 80 | { 81 | if (!this.running) 82 | return; 83 | 84 | var now = Date.now(); 85 | var deltat = (now - this.startTime) / 1000; 86 | var t = deltat % this.duration; 87 | var nCycles = Math.floor(deltat / this.duration); 88 | 89 | if (nCycles >= 1 && !this.loop) 90 | { 91 | this.running = false; 92 | var i, len = this.interps.length; 93 | for (i = 0; i < len; i ++) 94 | { 95 | this.interps[i].interp(this.duration); 96 | } 97 | this.stop(); 98 | return; 99 | } 100 | else 101 | { 102 | var i, len = this.interps.length; 103 | for (i = 0; i < len; i ++) 104 | { 105 | this.interps[i].interp(t); 106 | } 107 | } 108 | }; 109 | 110 | //Interpolator class 111 | //Construction/initialization 112 | THREE.glTFInterpolator = function(param) 113 | { 114 | this.keys = param.keys; 115 | this.values = param.values; 116 | this.count = param.count; 117 | this.type = param.type; 118 | this.path = param.path; 119 | this.isRot = false; 120 | 121 | var node = param.target; 122 | node.updateMatrix(); 123 | node.matrixAutoUpdate = true; 124 | this.targetNode = node; 125 | 126 | switch (param.path) { 127 | case "translation" : 128 | this.target = node.position; 129 | this.originalValue = node.position.clone(); 130 | break; 131 | case "rotation" : 132 | this.target = node.quaternion; 133 | this.originalValue = node.quaternion.clone(); 134 | this.isRot = true; 135 | break; 136 | case "scale" : 137 | this.target = node.scale; 138 | this.originalValue = node.scale.clone(); 139 | break; 140 | } 141 | 142 | this.duration = this.keys[this.count - 1]; 143 | 144 | this.vec1 = new THREE.Vector3; 145 | this.vec2 = new THREE.Vector3; 146 | this.vec3 = new THREE.Vector3; 147 | this.quat1 = new THREE.Quaternion; 148 | this.quat2 = new THREE.Quaternion; 149 | this.quat3 = new THREE.Quaternion; 150 | }; 151 | 152 | //Interpolation and tweening methods 153 | THREE.glTFInterpolator.prototype.interp = function(t) 154 | { 155 | var i, j; 156 | if (t == this.keys[0]) 157 | { 158 | if (this.isRot) { 159 | this.quat3.set(this.values[0], this.values[1], this.values[2], this.values[3]); 160 | } 161 | else { 162 | this.vec3.set(this.values[0], this.values[1], this.values[2]); 163 | } 164 | } 165 | else if (t < this.keys[0]) 166 | { 167 | if (this.isRot) { 168 | this.quat1.set(this.originalValue.x, 169 | this.originalValue.y, 170 | this.originalValue.z, 171 | this.originalValue.w); 172 | this.quat2.set(this.values[0], 173 | this.values[1], 174 | this.values[2], 175 | this.values[3]); 176 | THREE.Quaternion.slerp(this.quat1, this.quat2, this.quat3, t / this.keys[0]); 177 | } 178 | else { 179 | this.vec3.set(this.originalValue.x, 180 | this.originalValue.y, 181 | this.originalValue.z); 182 | this.vec2.set(this.values[0], 183 | this.values[1], 184 | this.values[2]); 185 | 186 | this.vec3.lerp(this.vec2, t / this.keys[0]); 187 | } 188 | } 189 | else if (t >= this.keys[this.count - 1]) 190 | { 191 | if (this.isRot) { 192 | this.quat3.set(this.values[(this.count - 1) * 4], 193 | this.values[(this.count - 1) * 4 + 1], 194 | this.values[(this.count - 1) * 4 + 2], 195 | this.values[(this.count - 1) * 4 + 3]); 196 | } 197 | else { 198 | this.vec3.set(this.values[(this.count - 1) * 3], 199 | this.values[(this.count - 1) * 3 + 1], 200 | this.values[(this.count - 1) * 3 + 2]); 201 | } 202 | } 203 | else 204 | { 205 | for (i = 0; i < this.count - 1; i ++) 206 | { 207 | var key1 = this.keys[i]; 208 | var key2 = this.keys[i + 1]; 209 | 210 | if (t >= key1 && t <= key2) 211 | { 212 | if (this.isRot) { 213 | this.quat1.set(this.values[i * 4], 214 | this.values[i * 4 + 1], 215 | this.values[i * 4 + 2], 216 | this.values[i * 4 + 3]); 217 | this.quat2.set(this.values[(i + 1) * 4], 218 | this.values[(i + 1) * 4 + 1], 219 | this.values[(i + 1) * 4 + 2], 220 | this.values[(i + 1) * 4 + 3]); 221 | THREE.Quaternion.slerp(this.quat1, this.quat2, this.quat3, (t - key1) / (key2 - key1)); 222 | } 223 | else { 224 | this.vec3.set(this.values[i * 3], 225 | this.values[i * 3 + 1], 226 | this.values[i * 3 + 2]); 227 | this.vec2.set(this.values[(i + 1) * 3], 228 | this.values[(i + 1) * 3 + 1], 229 | this.values[(i + 1) * 3 + 2]); 230 | 231 | this.vec3.lerp(this.vec2, (t - key1) / (key2 - key1)); 232 | } 233 | } 234 | } 235 | } 236 | 237 | if (this.target) 238 | { 239 | this.copyValue(this.target); 240 | } 241 | }; 242 | 243 | THREE.glTFInterpolator.prototype.copyValue = function(target) { 244 | 245 | if (this.isRot) { 246 | target.copy(this.quat3); 247 | } 248 | else { 249 | target.copy(this.vec3); 250 | } 251 | }; 252 | -------------------------------------------------------------------------------- /js/loaders/ctm/CTMLoader.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Loader for CTM encoded models generated by OpenCTM tools: 3 | * http://openctm.sourceforge.net/ 4 | * 5 | * Uses js-openctm library by Juan Mellado 6 | * http://code.google.com/p/js-openctm/ 7 | * 8 | * @author alteredq / http://alteredqualia.com/ 9 | */ 10 | 11 | THREE.CTMLoader = function () { 12 | 13 | THREE.Loader.call( this ); 14 | 15 | // Deprecated 16 | 17 | Object.defineProperties( this, { 18 | statusDomElement: { 19 | get: function () { 20 | 21 | if ( this._statusDomElement === undefined ) { 22 | 23 | this._statusDomElement = document.createElement( 'div' ); 24 | 25 | } 26 | 27 | console.warn( 'THREE.BinaryLoader: .statusDomElement has been removed.' ); 28 | return this._statusDomElement; 29 | 30 | } 31 | }, 32 | } ); 33 | 34 | }; 35 | 36 | THREE.CTMLoader.prototype = Object.create( THREE.Loader.prototype ); 37 | THREE.CTMLoader.prototype.constructor = THREE.CTMLoader; 38 | 39 | // Load multiple CTM parts defined in JSON 40 | 41 | THREE.CTMLoader.prototype.loadParts = function( url, callback, parameters ) { 42 | 43 | parameters = parameters || {}; 44 | 45 | var scope = this; 46 | 47 | var xhr = new XMLHttpRequest(); 48 | 49 | var basePath = parameters.basePath ? parameters.basePath : this.extractUrlBase( url ); 50 | 51 | xhr.onreadystatechange = function() { 52 | 53 | if ( xhr.readyState === 4 ) { 54 | 55 | if ( xhr.status === 200 || xhr.status === 0 ) { 56 | 57 | var jsonObject = JSON.parse( xhr.responseText ); 58 | 59 | var materials = [], geometries = [], counter = 0; 60 | 61 | function callbackFinal( geometry ) { 62 | 63 | counter += 1; 64 | 65 | geometries.push( geometry ); 66 | 67 | if ( counter === jsonObject.offsets.length ) { 68 | 69 | callback( geometries, materials ); 70 | 71 | } 72 | 73 | } 74 | 75 | 76 | // init materials 77 | 78 | for ( var i = 0; i < jsonObject.materials.length; i ++ ) { 79 | 80 | materials[ i ] = scope.createMaterial( jsonObject.materials[ i ], basePath ); 81 | 82 | } 83 | 84 | // load joined CTM file 85 | 86 | var partUrl = basePath + jsonObject.data; 87 | var parametersPart = { useWorker: parameters.useWorker, offsets: jsonObject.offsets }; 88 | scope.load( partUrl, callbackFinal, parametersPart ); 89 | 90 | } 91 | 92 | } 93 | 94 | }; 95 | 96 | xhr.open( "GET", url, true ); 97 | xhr.setRequestHeader( "Content-Type", "text/plain" ); 98 | xhr.send( null ); 99 | 100 | }; 101 | 102 | // Load CTMLoader compressed models 103 | // - parameters 104 | // - url (required) 105 | // - callback (required) 106 | 107 | THREE.CTMLoader.prototype.load = function( url, callback, parameters ) { 108 | 109 | parameters = parameters || {}; 110 | 111 | var scope = this; 112 | 113 | var offsets = parameters.offsets !== undefined ? parameters.offsets : [ 0 ]; 114 | 115 | var xhr = new XMLHttpRequest(), 116 | callbackProgress = null; 117 | 118 | var length = 0; 119 | 120 | xhr.onreadystatechange = function() { 121 | 122 | if ( xhr.readyState === 4 ) { 123 | 124 | if ( xhr.status === 200 || xhr.status === 0 ) { 125 | 126 | var binaryData = new Uint8Array(xhr.response); 127 | 128 | var s = Date.now(); 129 | 130 | if ( parameters.useWorker ) { 131 | 132 | var worker = parameters.worker || new Worker( "js/loaders/ctm/CTMWorker.js" ); 133 | 134 | worker.onmessage = function( event ) { 135 | 136 | var files = event.data; 137 | 138 | for ( var i = 0; i < files.length; i ++ ) { 139 | 140 | var ctmFile = files[ i ]; 141 | 142 | var e1 = Date.now(); 143 | // console.log( "CTM data parse time [worker]: " + (e1-s) + " ms" ); 144 | 145 | scope.createModel( ctmFile, callback ); 146 | 147 | var e = Date.now(); 148 | console.log( "model load time [worker]: " + (e - e1) + " ms, total: " + (e - s)); 149 | 150 | } 151 | 152 | 153 | }; 154 | 155 | worker.postMessage( { "data": binaryData, "offsets": offsets } ); 156 | 157 | } else { 158 | 159 | for ( var i = 0; i < offsets.length; i ++ ) { 160 | 161 | var stream = new CTM.Stream( binaryData ); 162 | stream.offset = offsets[ i ]; 163 | 164 | var ctmFile = new CTM.File( stream ); 165 | 166 | scope.createModel( ctmFile, callback ); 167 | 168 | } 169 | 170 | //var e = Date.now(); 171 | //console.log( "CTM data parse time [inline]: " + (e-s) + " ms" ); 172 | 173 | } 174 | 175 | } else { 176 | 177 | console.error( "Couldn't load [" + url + "] [" + xhr.status + "]" ); 178 | 179 | } 180 | 181 | } else if ( xhr.readyState === 3 ) { 182 | 183 | if ( callbackProgress ) { 184 | 185 | if ( length === 0 ) { 186 | 187 | length = xhr.getResponseHeader( "Content-Length" ); 188 | 189 | } 190 | 191 | callbackProgress( { total: length, loaded: xhr.responseText.length } ); 192 | 193 | } 194 | 195 | } else if ( xhr.readyState === 2 ) { 196 | 197 | length = xhr.getResponseHeader( "Content-Length" ); 198 | 199 | } 200 | 201 | }; 202 | 203 | xhr.open( "GET", url, true ); 204 | xhr.responseType = "arraybuffer"; 205 | 206 | xhr.send( null ); 207 | 208 | }; 209 | 210 | 211 | THREE.CTMLoader.prototype.createModel = function ( file, callback ) { 212 | 213 | var Model = function () { 214 | 215 | THREE.BufferGeometry.call( this ); 216 | 217 | this.materials = []; 218 | 219 | var indices = file.body.indices, 220 | positions = file.body.vertices, 221 | normals = file.body.normals; 222 | 223 | var uvs, colors; 224 | 225 | var uvMaps = file.body.uvMaps; 226 | 227 | if ( uvMaps !== undefined && uvMaps.length > 0 ) { 228 | 229 | uvs = uvMaps[ 0 ].uv; 230 | 231 | } 232 | 233 | var attrMaps = file.body.attrMaps; 234 | 235 | if ( attrMaps !== undefined && attrMaps.length > 0 && attrMaps[ 0 ].name === 'Color' ) { 236 | 237 | colors = attrMaps[ 0 ].attr; 238 | 239 | } 240 | 241 | this.setIndex( new THREE.BufferAttribute( indices, 1 ) ); 242 | this.addAttribute( 'position', new THREE.BufferAttribute( positions, 3 ) ); 243 | 244 | if ( normals !== undefined ) { 245 | 246 | this.addAttribute( 'normal', new THREE.BufferAttribute( normals, 3 ) ); 247 | 248 | } 249 | 250 | if ( uvs !== undefined ) { 251 | 252 | this.addAttribute( 'uv', new THREE.BufferAttribute( uvs, 2 ) ); 253 | 254 | } 255 | 256 | if ( colors !== undefined ) { 257 | 258 | this.addAttribute( 'color', new THREE.BufferAttribute( colors, 4 ) ); 259 | 260 | } 261 | 262 | }; 263 | 264 | Model.prototype = Object.create( THREE.BufferGeometry.prototype ); 265 | Model.prototype.constructor = Model; 266 | 267 | var geometry = new Model(); 268 | 269 | // compute vertex normals if not present in the CTM model 270 | if ( geometry.attributes.normal === undefined ) { 271 | geometry.computeVertexNormals(); 272 | } 273 | 274 | callback( geometry ); 275 | 276 | }; 277 | -------------------------------------------------------------------------------- /js/loaders/PDBLoader.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author alteredq / http://alteredqualia.com/ 3 | */ 4 | 5 | THREE.PDBLoader = function ( manager ) { 6 | 7 | this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager; 8 | 9 | }; 10 | 11 | THREE.PDBLoader.prototype = { 12 | 13 | constructor: THREE.PDBLoader, 14 | 15 | load: function ( url, onLoad, onProgress, onError ) { 16 | 17 | var scope = this; 18 | 19 | var loader = new THREE.XHRLoader( scope.manager ); 20 | loader.load( url, function ( text ) { 21 | 22 | var json = scope.parsePDB( text ); 23 | scope.createModel( json, onLoad ); 24 | 25 | }, onProgress, onError ); 26 | 27 | }, 28 | 29 | // Based on CanvasMol PDB parser 30 | 31 | parsePDB: function ( text ) { 32 | 33 | function trim( text ) { 34 | 35 | return text.replace( /^\s\s*/, '' ).replace( /\s\s*$/, '' ); 36 | 37 | } 38 | 39 | function capitalize( text ) { 40 | 41 | return text.charAt( 0 ).toUpperCase() + text.substr( 1 ).toLowerCase(); 42 | 43 | } 44 | 45 | function hash( s, e ) { 46 | 47 | return "s" + Math.min( s, e ) + "e" + Math.max( s, e ); 48 | 49 | } 50 | 51 | function parseBond( start, length ) { 52 | 53 | var eatom = parseInt( lines[ i ].substr( start, length ) ); 54 | 55 | if ( eatom ) { 56 | 57 | var h = hash( satom, eatom ); 58 | 59 | if ( bhash[ h ] == undefined ) { 60 | 61 | bonds.push( [ satom - 1, eatom - 1, 1 ] ); 62 | bhash[ h ] = bonds.length - 1; 63 | 64 | } else { 65 | 66 | // doesn't really work as almost all PDBs 67 | // have just normal bonds appearing multiple 68 | // times instead of being double/triple bonds 69 | // bonds[bhash[h]][2] += 1; 70 | 71 | } 72 | 73 | } 74 | 75 | } 76 | 77 | var CPK = { "h": [ 255, 255, 255 ], "he": [ 217, 255, 255 ], "li": [ 204, 128, 255 ], "be": [ 194, 255, 0 ], "b": [ 255, 181, 181 ], "c": [ 144, 144, 144 ], "n": [ 48, 80, 248 ], "o": [ 255, 13, 13 ], "f": [ 144, 224, 80 ], "ne": [ 179, 227, 245 ], "na": [ 171, 92, 242 ], "mg": [ 138, 255, 0 ], "al": [ 191, 166, 166 ], "si": [ 240, 200, 160 ], "p": [ 255, 128, 0 ], "s": [ 255, 255, 48 ], "cl": [ 31, 240, 31 ], "ar": [ 128, 209, 227 ], "k": [ 143, 64, 212 ], "ca": [ 61, 255, 0 ], "sc": [ 230, 230, 230 ], "ti": [ 191, 194, 199 ], "v": [ 166, 166, 171 ], "cr": [ 138, 153, 199 ], "mn": [ 156, 122, 199 ], "fe": [ 224, 102, 51 ], "co": [ 240, 144, 160 ], "ni": [ 80, 208, 80 ], "cu": [ 200, 128, 51 ], "zn": [ 125, 128, 176 ], "ga": [ 194, 143, 143 ], "ge": [ 102, 143, 143 ], "as": [ 189, 128, 227 ], "se": [ 255, 161, 0 ], "br": [ 166, 41, 41 ], "kr": [ 92, 184, 209 ], "rb": [ 112, 46, 176 ], "sr": [ 0, 255, 0 ], "y": [ 148, 255, 255 ], "zr": [ 148, 224, 224 ], "nb": [ 115, 194, 201 ], "mo": [ 84, 181, 181 ], "tc": [ 59, 158, 158 ], "ru": [ 36, 143, 143 ], "rh": [ 10, 125, 140 ], "pd": [ 0, 105, 133 ], "ag": [ 192, 192, 192 ], "cd": [ 255, 217, 143 ], "in": [ 166, 117, 115 ], "sn": [ 102, 128, 128 ], "sb": [ 158, 99, 181 ], "te": [ 212, 122, 0 ], "i": [ 148, 0, 148 ], "xe": [ 66, 158, 176 ], "cs": [ 87, 23, 143 ], "ba": [ 0, 201, 0 ], "la": [ 112, 212, 255 ], "ce": [ 255, 255, 199 ], "pr": [ 217, 255, 199 ], "nd": [ 199, 255, 199 ], "pm": [ 163, 255, 199 ], "sm": [ 143, 255, 199 ], "eu": [ 97, 255, 199 ], "gd": [ 69, 255, 199 ], "tb": [ 48, 255, 199 ], "dy": [ 31, 255, 199 ], "ho": [ 0, 255, 156 ], "er": [ 0, 230, 117 ], "tm": [ 0, 212, 82 ], "yb": [ 0, 191, 56 ], "lu": [ 0, 171, 36 ], "hf": [ 77, 194, 255 ], "ta": [ 77, 166, 255 ], "w": [ 33, 148, 214 ], "re": [ 38, 125, 171 ], "os": [ 38, 102, 150 ], "ir": [ 23, 84, 135 ], "pt": [ 208, 208, 224 ], "au": [ 255, 209, 35 ], "hg": [ 184, 184, 208 ], "tl": [ 166, 84, 77 ], "pb": [ 87, 89, 97 ], "bi": [ 158, 79, 181 ], "po": [ 171, 92, 0 ], "at": [ 117, 79, 69 ], "rn": [ 66, 130, 150 ], "fr": [ 66, 0, 102 ], "ra": [ 0, 125, 0 ], "ac": [ 112, 171, 250 ], "th": [ 0, 186, 255 ], "pa": [ 0, 161, 255 ], "u": [ 0, 143, 255 ], "np": [ 0, 128, 255 ], "pu": [ 0, 107, 255 ], "am": [ 84, 92, 242 ], "cm": [ 120, 92, 227 ], "bk": [ 138, 79, 227 ], "cf": [ 161, 54, 212 ], "es": [ 179, 31, 212 ], "fm": [ 179, 31, 186 ], "md": [ 179, 13, 166 ], "no": [ 189, 13, 135 ], "lr": [ 199, 0, 102 ], "rf": [ 204, 0, 89 ], "db": [ 209, 0, 79 ], "sg": [ 217, 0, 69 ], "bh": [ 224, 0, 56 ], "hs": [ 230, 0, 46 ], "mt": [ 235, 0, 38 ], 78 | "ds": [ 235, 0, 38 ], "rg": [ 235, 0, 38 ], "cn": [ 235, 0, 38 ], "uut": [ 235, 0, 38 ], "uuq": [ 235, 0, 38 ], "uup": [ 235, 0, 38 ], "uuh": [ 235, 0, 38 ], "uus": [ 235, 0, 38 ], "uuo": [ 235, 0, 38 ] }; 79 | 80 | 81 | var atoms = []; 82 | var bonds = []; 83 | var histogram = {}; 84 | 85 | var bhash = {}; 86 | 87 | var lines = text.split( "\n" ); 88 | 89 | var x, y, z, e; 90 | 91 | for ( var i = 0, il = lines.length; i < il; ++ i ) { 92 | 93 | if ( lines[ i ].substr( 0, 4 ) == "ATOM" || lines[ i ].substr( 0, 6 ) == "HETATM" ) { 94 | 95 | x = parseFloat( lines[ i ].substr( 30, 7 ) ); 96 | y = parseFloat( lines[ i ].substr( 38, 7 ) ); 97 | z = parseFloat( lines[ i ].substr( 46, 7 ) ); 98 | 99 | e = trim( lines[ i ].substr( 76, 2 ) ).toLowerCase(); 100 | 101 | if ( e == "" ) e = trim( lines[ i ].substr( 12, 2 ) ).toLowerCase(); 102 | atoms.push( [ x, y, z, CPK[ e ], capitalize( e ) ] ); 103 | 104 | if ( histogram[ e ] == undefined ) histogram[ e ] = 1; 105 | else histogram[ e ] += 1; 106 | 107 | } else if ( lines[ i ].substr( 0, 6 ) == "CONECT" ) { 108 | 109 | var satom = parseInt( lines[ i ].substr( 6, 5 ) ); 110 | 111 | parseBond( 11, 5 ); 112 | parseBond( 16, 5 ); 113 | parseBond( 21, 5 ); 114 | parseBond( 26, 5 ); 115 | 116 | } 117 | 118 | } 119 | 120 | return { "ok": true, "atoms": atoms, "bonds": bonds, "histogram": histogram }; 121 | 122 | }, 123 | 124 | createModel: function ( json, callback ) { 125 | 126 | var scope = this, 127 | geometryAtoms = new THREE.Geometry(), 128 | geometryBonds = new THREE.Geometry(); 129 | 130 | geometryAtoms.elements = []; 131 | 132 | var atoms = json.atoms; 133 | var bonds = json.bonds; 134 | 135 | for ( var i = 0; i < atoms.length; i ++ ) { 136 | 137 | var atom = atoms[ i ]; 138 | 139 | var x = atom[ 0 ]; 140 | var y = atom[ 1 ]; 141 | var z = atom[ 2 ]; 142 | 143 | var position = new THREE.Vector3( x, y, z ); 144 | geometryAtoms.vertices.push( position ); 145 | 146 | var r = atom[ 3 ][ 0 ] / 255; 147 | var g = atom[ 3 ][ 1 ] / 255; 148 | var b = atom[ 3 ][ 2 ] / 255; 149 | 150 | var color = new THREE.Color(); 151 | color.setRGB( r, g, b ); 152 | 153 | geometryAtoms.colors.push( color ); 154 | 155 | geometryAtoms.elements.push( atom[ 4 ] ); 156 | 157 | } 158 | 159 | for ( var i = 0; i < bonds.length; i ++ ) { 160 | 161 | var bond = bonds[ i ]; 162 | 163 | var start = bond[ 0 ]; 164 | var end = bond[ 1 ]; 165 | 166 | var vertex1 = geometryAtoms.vertices[ start ]; 167 | var vertex2 = geometryAtoms.vertices[ end ]; 168 | 169 | geometryBonds.vertices.push( vertex1.clone() ); 170 | geometryBonds.vertices.push( vertex2.clone() ); 171 | 172 | } 173 | 174 | callback( geometryAtoms, geometryBonds, json ); 175 | 176 | } 177 | 178 | }; 179 | 180 | -------------------------------------------------------------------------------- /js/loaders/PVRLoader.js: -------------------------------------------------------------------------------- 1 | /* 2 | * PVRLoader 3 | * Author: pierre lepers 4 | * Date: 17/09/2014 11:09 5 | * 6 | * PVR v2 (legacy) parser 7 | * TODO : Add Support for PVR v3 format 8 | * TODO : implement loadMipmaps option 9 | */ 10 | 11 | THREE.PVRLoader = function ( manager ) { 12 | 13 | this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager; 14 | 15 | this._parser = THREE.PVRLoader.parse; 16 | 17 | }; 18 | 19 | THREE.PVRLoader.prototype = Object.create( THREE.CompressedTextureLoader.prototype ); 20 | THREE.PVRLoader.prototype.constructor = THREE.PVRLoader; 21 | 22 | 23 | THREE.PVRLoader.parse = function ( buffer, loadMipmaps ) { 24 | 25 | var headerLengthInt = 13; 26 | var header = new Uint32Array( buffer, 0, headerLengthInt ); 27 | 28 | var pvrDatas = { 29 | buffer: buffer, 30 | header : header, 31 | loadMipmaps : loadMipmaps 32 | }; 33 | 34 | // PVR v3 35 | if ( header[ 0 ] === 0x03525650 ) { 36 | 37 | return THREE.PVRLoader._parseV3( pvrDatas ); 38 | 39 | } 40 | // PVR v2 41 | else if ( header[ 11 ] === 0x21525650 ) { 42 | 43 | return THREE.PVRLoader._parseV2( pvrDatas ); 44 | 45 | } else { 46 | 47 | throw new Error( "[THREE.PVRLoader] Unknown PVR format" ); 48 | 49 | } 50 | 51 | }; 52 | 53 | THREE.PVRLoader._parseV3 = function ( pvrDatas ) { 54 | 55 | var header = pvrDatas.header; 56 | var bpp, format; 57 | 58 | 59 | var metaLen = header[ 12 ], 60 | pixelFormat = header[ 2 ], 61 | height = header[ 6 ], 62 | width = header[ 7 ], 63 | numSurfs = header[ 9 ], 64 | numFaces = header[ 10 ], 65 | numMipmaps = header[ 11 ]; 66 | 67 | switch ( pixelFormat ) { 68 | case 0 : // PVRTC 2bpp RGB 69 | bpp = 2; 70 | format = THREE.RGB_PVRTC_2BPPV1_Format; 71 | break; 72 | case 1 : // PVRTC 2bpp RGBA 73 | bpp = 2; 74 | format = THREE.RGBA_PVRTC_2BPPV1_Format; 75 | break; 76 | case 2 : // PVRTC 4bpp RGB 77 | bpp = 4; 78 | format = THREE.RGB_PVRTC_4BPPV1_Format; 79 | break; 80 | case 3 : // PVRTC 4bpp RGBA 81 | bpp = 4; 82 | format = THREE.RGBA_PVRTC_4BPPV1_Format; 83 | break; 84 | default : 85 | throw new Error( "pvrtc - unsupported PVR format " + pixelFormat ); 86 | } 87 | 88 | pvrDatas.dataPtr = 52 + metaLen; 89 | pvrDatas.bpp = bpp; 90 | pvrDatas.format = format; 91 | pvrDatas.width = width; 92 | pvrDatas.height = height; 93 | pvrDatas.numSurfaces = numFaces; 94 | pvrDatas.numMipmaps = numMipmaps; 95 | 96 | pvrDatas.isCubemap = ( numFaces === 6 ); 97 | 98 | return THREE.PVRLoader._extract( pvrDatas ); 99 | 100 | }; 101 | 102 | THREE.PVRLoader._parseV2 = function ( pvrDatas ) { 103 | 104 | var header = pvrDatas.header; 105 | 106 | var headerLength = header[ 0 ], 107 | height = header[ 1 ], 108 | width = header[ 2 ], 109 | numMipmaps = header[ 3 ], 110 | flags = header[ 4 ], 111 | dataLength = header[ 5 ], 112 | bpp = header[ 6 ], 113 | bitmaskRed = header[ 7 ], 114 | bitmaskGreen = header[ 8 ], 115 | bitmaskBlue = header[ 9 ], 116 | bitmaskAlpha = header[ 10 ], 117 | pvrTag = header[ 11 ], 118 | numSurfs = header[ 12 ]; 119 | 120 | 121 | var TYPE_MASK = 0xff; 122 | var PVRTC_2 = 24, 123 | PVRTC_4 = 25; 124 | 125 | var formatFlags = flags & TYPE_MASK; 126 | 127 | 128 | 129 | var bpp, format; 130 | var _hasAlpha = bitmaskAlpha > 0; 131 | 132 | if ( formatFlags === PVRTC_4 ) { 133 | 134 | format = _hasAlpha ? THREE.RGBA_PVRTC_4BPPV1_Format : THREE.RGB_PVRTC_4BPPV1_Format; 135 | bpp = 4; 136 | 137 | } else if ( formatFlags === PVRTC_2 ) { 138 | 139 | format = _hasAlpha ? THREE.RGBA_PVRTC_2BPPV1_Format : THREE.RGB_PVRTC_2BPPV1_Format; 140 | bpp = 2; 141 | 142 | } else 143 | throw new Error( "pvrtc - unknown format " + formatFlags ); 144 | 145 | 146 | 147 | pvrDatas.dataPtr = headerLength; 148 | pvrDatas.bpp = bpp; 149 | pvrDatas.format = format; 150 | pvrDatas.width = width; 151 | pvrDatas.height = height; 152 | pvrDatas.numSurfaces = numSurfs; 153 | pvrDatas.numMipmaps = numMipmaps + 1; 154 | 155 | // guess cubemap type seems tricky in v2 156 | // it juste a pvr containing 6 surface (no explicit cubemap type) 157 | pvrDatas.isCubemap = ( numSurfs === 6 ); 158 | 159 | return THREE.PVRLoader._extract( pvrDatas ); 160 | 161 | }; 162 | 163 | 164 | THREE.PVRLoader._extract = function ( pvrDatas ) { 165 | 166 | var pvr = { 167 | mipmaps: [], 168 | width: pvrDatas.width, 169 | height: pvrDatas.height, 170 | format: pvrDatas.format, 171 | mipmapCount: pvrDatas.numMipmaps, 172 | isCubemap : pvrDatas.isCubemap 173 | }; 174 | 175 | var buffer = pvrDatas.buffer; 176 | 177 | 178 | 179 | // console.log( "--------------------------" ); 180 | 181 | // console.log( "headerLength ", headerLength); 182 | // console.log( "height ", height ); 183 | // console.log( "width ", width ); 184 | // console.log( "numMipmaps ", numMipmaps ); 185 | // console.log( "flags ", flags ); 186 | // console.log( "dataLength ", dataLength ); 187 | // console.log( "bpp ", bpp ); 188 | // console.log( "bitmaskRed ", bitmaskRed ); 189 | // console.log( "bitmaskGreen ", bitmaskGreen); 190 | // console.log( "bitmaskBlue ", bitmaskBlue ); 191 | // console.log( "bitmaskAlpha ", bitmaskAlpha); 192 | // console.log( "pvrTag ", pvrTag ); 193 | // console.log( "numSurfs ", numSurfs ); 194 | 195 | 196 | 197 | 198 | var dataOffset = pvrDatas.dataPtr, 199 | bpp = pvrDatas.bpp, 200 | numSurfs = pvrDatas.numSurfaces, 201 | dataSize = 0, 202 | blockSize = 0, 203 | blockWidth = 0, 204 | blockHeight = 0, 205 | widthBlocks = 0, 206 | heightBlocks = 0; 207 | 208 | 209 | 210 | if ( bpp === 2 ) { 211 | 212 | blockWidth = 8; 213 | blockHeight = 4; 214 | 215 | } else { 216 | 217 | blockWidth = 4; 218 | blockHeight = 4; 219 | 220 | } 221 | 222 | blockSize = ( blockWidth * blockHeight ) * bpp / 8; 223 | 224 | pvr.mipmaps.length = pvrDatas.numMipmaps * numSurfs; 225 | 226 | var mipLevel = 0; 227 | 228 | while ( mipLevel < pvrDatas.numMipmaps ) { 229 | 230 | var sWidth = pvrDatas.width >> mipLevel, 231 | sHeight = pvrDatas.height >> mipLevel; 232 | 233 | widthBlocks = sWidth / blockWidth; 234 | heightBlocks = sHeight / blockHeight; 235 | 236 | // Clamp to minimum number of blocks 237 | if ( widthBlocks < 2 ) 238 | widthBlocks = 2; 239 | if ( heightBlocks < 2 ) 240 | heightBlocks = 2; 241 | 242 | dataSize = widthBlocks * heightBlocks * blockSize; 243 | 244 | 245 | for ( var surfIndex = 0; surfIndex < numSurfs; surfIndex ++ ) { 246 | 247 | var byteArray = new Uint8Array( buffer, dataOffset, dataSize ); 248 | 249 | var mipmap = { 250 | data: byteArray, 251 | width: sWidth, 252 | height: sHeight 253 | }; 254 | 255 | pvr.mipmaps[ surfIndex * pvrDatas.numMipmaps + mipLevel ] = mipmap; 256 | 257 | dataOffset += dataSize; 258 | 259 | 260 | } 261 | 262 | mipLevel ++; 263 | 264 | } 265 | 266 | 267 | return pvr; 268 | 269 | }; 270 | -------------------------------------------------------------------------------- /ts/zajal.ts: -------------------------------------------------------------------------------- 1 | namespace React { 2 | function mountTree(element: Element, container:HTMLElement) { 3 | if (container.firstChild) { 4 | var prevNode = container.firstChild; 5 | var prevRootComponent: Component = prevNode['_internalInstance']; 6 | var prevElement = prevRootComponent.currentElement; 7 | if (prevElement.type == element.type) { 8 | prevRootComponent.recieve(element); 9 | return; 10 | } 11 | // TODO unmountTree(container) 12 | } 13 | var rootComponent = instantiateComponent(element); 14 | var node = rootComponent.mount(); 15 | container.appendChild(node.getHtmlNode()); 16 | return node; 17 | } 18 | 19 | function isClass(type): boolean { 20 | return type.prototype && type.prototype.constructor; 21 | } 22 | 23 | function instantiateComponent(element: Element): Component { 24 | if (isClass(element.type)) { 25 | // Platform-specific components 26 | return new HostComponent(element); 27 | } else { 28 | // User-defined components 29 | return new CompositeComponent(element); 30 | } 31 | } 32 | 33 | 34 | 35 | // TODO cljs elements? 36 | class Element { 37 | type: any; 38 | props: any; 39 | 40 | constructor(type, props) { 41 | this.type = type; 42 | this.props = props; 43 | } 44 | } 45 | 46 | interface Component { 47 | mount(): HostNode; 48 | unmount(): void; 49 | getHostNode(): HostNode; 50 | recieve(nextElement: Element); 51 | currentElement: Element; 52 | } 53 | 54 | interface HostNode { 55 | removeProperty(prop:string); 56 | setProperty(prop:string, value); 57 | addChild(child:HostNode); 58 | replaceChild(child:HostNode,newChild:HostNode); 59 | removeChild(child:HostNode); 60 | getHtmlNode():HTMLElement; 61 | } 62 | 63 | class CompositeComponent implements Component { 64 | currentElement: Element; 65 | renderedComponent: Component; 66 | 67 | constructor(element: Element) { 68 | this.currentElement = element; 69 | this.renderedComponent = null; 70 | } 71 | 72 | getHostNode() { 73 | return this.renderedComponent.getHostNode(); 74 | } 75 | 76 | recieve(nextElement: Element) { 77 | var prevProps = this.currentElement.props; 78 | var prevRenderedComponent = this.renderedComponent; 79 | var prevRenderedElement = prevRenderedComponent.currentElement; 80 | 81 | this.currentElement = nextElement; 82 | var type = nextElement.type; 83 | var nextProps = nextElement.props; 84 | var nextRenderedElement: Element = type(nextProps); 85 | 86 | if (prevRenderedElement.type === nextRenderedElement.type) { 87 | prevRenderedComponent.recieve(nextRenderedElement); 88 | return; 89 | } 90 | 91 | var prevNode = prevRenderedComponent.getHostNode(); 92 | prevRenderedComponent.unmount(); 93 | var nextRenderedComponent = instantiateComponent(nextRenderedElement); 94 | var nextNode = nextRenderedComponent.mount(); 95 | 96 | this.renderedComponent = nextRenderedComponent; 97 | this.getHostNode().replaceChild(prevNode, nextNode); 98 | // TODO replace old node with new one, host specific 99 | } 100 | 101 | mount() { 102 | var element = this.currentElement; 103 | var type = this.currentElement.type; 104 | var props = this.currentElement.props; 105 | 106 | var renderedElement = type(props); 107 | this.renderedComponent = instantiateComponent(renderedElement); 108 | 109 | return this.renderedComponent.mount(); 110 | } 111 | 112 | unmount() { 113 | this.renderedComponent.unmount(); 114 | } 115 | } 116 | 117 | class HostComponent implements Component { 118 | currentElement: Element; 119 | renderedChildren: Component[]; 120 | node: HostNode; 121 | 122 | constructor(element: Element) { 123 | this.currentElement = element; 124 | this.node = null; 125 | } 126 | 127 | getHostNode() { 128 | return this.node; 129 | } 130 | 131 | recieve(nextElement: Element) { 132 | var prevChildNode = this.node; 133 | var prevElement = this.currentElement; 134 | var prevProps = prevElement.props; 135 | var nextProps = nextElement.props; 136 | this.currentElement = nextElement; 137 | 138 | Object.keys(prevProps).forEach(propName => { 139 | if (propName !== 'children' && !nextProps.hasOwnProperty(propName)) { 140 | prevChildNode.removeProperty(propName); 141 | } 142 | }); 143 | 144 | Object.keys(nextProps).forEach(propName => { 145 | if (propName !== 'children') { 146 | prevChildNode.setProperty(propName, nextProps[propName]); 147 | } 148 | }); 149 | 150 | var prevChildren: Element[] = prevProps.children || []; 151 | if (!Array.isArray(prevChildren)) { 152 | prevChildren = [prevChildren]; 153 | } 154 | var nextChildren: Element[] = nextProps.children || []; 155 | if (!Array.isArray(nextChildren)) { 156 | nextChildren = [nextChildren]; 157 | } 158 | 159 | var prevRenderedChildren = this.renderedChildren; 160 | for (var i = 0; i < nextChildren.length; ++i) { 161 | var prevChild = prevRenderedChildren[i]; 162 | if (!prevChild) { 163 | var nextChild = instantiateComponent(nextChildren[i]); 164 | var nextChildNode = nextChild.mount(); 165 | this.node.addChild(nextChildNode); // TODO ADD 166 | continue; 167 | } 168 | 169 | var canUpdate = prevChildren[i].type === nextChildren[i].type; 170 | if (!canUpdate) { 171 | var prevChildNode = prevChild.getHostNode(); 172 | prevChild.unmount(); 173 | var nextChild = instantiateComponent(nextChildren[i]); 174 | var nextChildNode = nextChild.mount(); 175 | this.node.replaceChild(prevChildNode, nextChildNode); // TODO REPLACE 176 | continue; 177 | } 178 | 179 | prevChild.recieve(nextChildren[i]); 180 | } 181 | 182 | for (var j = nextChildren.length; j < prevChildren.length; ++j) { 183 | var prevChild = prevRenderedChildren[j]; 184 | var prevChildNode = prevChild.getHostNode(); 185 | prevChild.unmount(); 186 | this.node.removeChild(prevChildNode); // TODO REMOVE 187 | } 188 | } 189 | 190 | mount() { 191 | var element = this.currentElement; 192 | var type = element.type; 193 | var props = element.props; 194 | var children: Element[] = props.children || []; 195 | 196 | if (!Array.isArray(children)) { 197 | children = [children]; 198 | } 199 | 200 | var node: HostNode = new type(); 201 | this.node = node; 202 | 203 | Object.keys(props).forEach(propName => { 204 | if (propName !== 'children') 205 | node.setProperty(propName, props[propName]); 206 | }); 207 | 208 | var renderedChildren = children.map(instantiateComponent); 209 | this.renderedChildren = renderedChildren; 210 | 211 | var childNodes = renderedChildren.map(child => child.mount()); 212 | childNodes.forEach(childNode => node.addChild(childNode)); 213 | 214 | return node; 215 | } 216 | 217 | unmount() { 218 | // TODO do more? 219 | this.renderedChildren.forEach(child => child.unmount()); 220 | } 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /js/loaders/gltf/glTFLoaderUtils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Tony Parisi / http://www.tonyparisi.com/ 3 | */ 4 | 5 | THREE.GLTFLoaderUtils = Object.create(Object, { 6 | 7 | // errors 8 | MISSING_DESCRIPTION: { value: "MISSING_DESCRIPTION" }, 9 | INVALID_PATH: { value: "INVALID_PATH" }, 10 | INVALID_TYPE: { value: "INVALID_TYPE" }, 11 | XMLHTTPREQUEST_STATUS_ERROR: { value: "XMLHTTPREQUEST_STATUS_ERROR" }, 12 | NOT_FOUND: { value: "NOT_FOUND" }, 13 | // misc constants 14 | ARRAY_BUFFER: { value: "ArrayBuffer" }, 15 | 16 | _streams : { value:{}, writable: true }, 17 | 18 | _streamsStatus: { value: {}, writable: true }, 19 | 20 | _resources: { value: {}, writable: true }, 21 | 22 | _resourcesStatus: { value: {}, writable: true }, 23 | 24 | // initialization 25 | init: { 26 | value: function() { 27 | this._streams = {}; 28 | this._streamsStatus = {}; 29 | this._resources = {}; 30 | this._resourcesStatus = {}; 31 | } 32 | }, 33 | 34 | //manage entries 35 | _containsResource: { 36 | enumerable: false, 37 | value: function(resourceID) { 38 | return this._resources[resourceID] ? true : false; 39 | } 40 | }, 41 | 42 | _storeResource: { 43 | enumerable: false, 44 | value: function(resourceID, resource) { 45 | if (!resourceID) { 46 | console.log("ERROR: entry does not contain id, cannot store"); 47 | return; 48 | } 49 | 50 | if (this._containsResource[resourceID]) { 51 | console.log("WARNING: resource:" + resourceID + " is already stored, overriding"); 52 | } 53 | 54 | this._resources[resourceID] = resource; 55 | } 56 | }, 57 | 58 | _getResource: { 59 | enumerable: false, 60 | value: function(resourceID) { 61 | return this._resources[resourceID]; 62 | } 63 | }, 64 | 65 | _loadStream: { 66 | value: function(path, type, delegate) { 67 | var self = this; 68 | 69 | if (!type) { 70 | delegate.handleError(THREE.GLTFLoaderUtils.INVALID_TYPE, null); 71 | return; 72 | } 73 | 74 | if (!path) { 75 | delegate.handleError(THREE.GLTFLoaderUtils.INVALID_PATH); 76 | return; 77 | } 78 | 79 | var xhr = new XMLHttpRequest(); 80 | xhr.open('GET', path, true); 81 | xhr.responseType = (type === this.ARRAY_BUFFER) ? "arraybuffer" : "text"; 82 | 83 | //if this is not specified, 1 "big blob" scenes fails to load. 84 | xhr.setRequestHeader("If-Modified-Since", "Sat, 01 Jan 1970 00:00:00 GMT"); 85 | xhr.addEventListener( 'load', function ( event ) { 86 | delegate.streamAvailable(path, xhr.response); 87 | }, false ); 88 | xhr.addEventListener( 'error', function ( event ) { 89 | delegate.handleError(THREE.GLTFLoaderUtils.XMLHTTPREQUEST_STATUS_ERROR, xhr.status); 90 | }, false ); 91 | xhr.send(null); 92 | } 93 | }, 94 | 95 | send: { value: 0, writable: true }, 96 | requested: { value: 0, writable: true }, 97 | 98 | _handleRequest: { 99 | value: function(request) { 100 | var resourceStatus = this._resourcesStatus[request.id]; 101 | if (resourceStatus) 102 | { 103 | this._resourcesStatus[request.id] ++; 104 | } 105 | else 106 | { 107 | this._resourcesStatus[request.id] = 1; 108 | } 109 | 110 | var streamStatus = this._streamsStatus[request.path]; 111 | if (streamStatus && streamStatus.status === "loading" ) 112 | { 113 | streamStatus.requests.push(request); 114 | return; 115 | } 116 | 117 | this._streamsStatus[request.path] = { status : "loading", requests : [ request ] }; 118 | 119 | var self = this; 120 | var processResourceDelegate = {}; 121 | 122 | processResourceDelegate.streamAvailable = function(path, res_) { 123 | var streamStatus = self._streamsStatus[path]; 124 | var requests = streamStatus.requests; 125 | requests.forEach( function(req_) { 126 | var subArray = res_.slice(req_.range[0], req_.range[1]); 127 | var convertedResource = req_.delegate.convert(subArray, req_.ctx); 128 | self._storeResource(req_.id, convertedResource); 129 | req_.delegate.resourceAvailable(convertedResource, req_.ctx); 130 | -- self._resourcesStatus[req_.id]; 131 | 132 | }, this); 133 | 134 | delete self._streamsStatus[path]; 135 | 136 | }; 137 | 138 | processResourceDelegate.handleError = function(errorCode, info) { 139 | request.delegate.handleError(errorCode, info); 140 | }; 141 | 142 | this._loadStream(request.path, request.type, processResourceDelegate); 143 | } 144 | }, 145 | 146 | 147 | _elementSizeForGLType: { 148 | value: function(glType) { 149 | switch (glType) { 150 | case WebGLRenderingContext.FLOAT : 151 | return Float32Array.BYTES_PER_ELEMENT; 152 | case WebGLRenderingContext.UNSIGNED_BYTE : 153 | return Uint8Array.BYTES_PER_ELEMENT; 154 | case WebGLRenderingContext.UNSIGNED_SHORT : 155 | return Uint16Array.BYTES_PER_ELEMENT; 156 | case WebGLRenderingContext.FLOAT_VEC2 : 157 | return Float32Array.BYTES_PER_ELEMENT * 2; 158 | case WebGLRenderingContext.FLOAT_VEC3 : 159 | return Float32Array.BYTES_PER_ELEMENT * 3; 160 | case WebGLRenderingContext.FLOAT_VEC4 : 161 | return Float32Array.BYTES_PER_ELEMENT * 4; 162 | case WebGLRenderingContext.FLOAT_MAT3 : 163 | return Float32Array.BYTES_PER_ELEMENT * 9; 164 | case WebGLRenderingContext.FLOAT_MAT4 : 165 | return Float32Array.BYTES_PER_ELEMENT * 16; 166 | default: 167 | return null; 168 | } 169 | } 170 | }, 171 | 172 | _handleWrappedBufferViewResourceLoading: { 173 | value: function(wrappedBufferView, delegate, ctx) { 174 | var bufferView = wrappedBufferView.bufferView; 175 | var buffer = bufferView.buffer; 176 | var byteOffset = wrappedBufferView.byteOffset + bufferView.description.byteOffset; 177 | var range = [ byteOffset, (this._elementSizeForGLType(wrappedBufferView.type) * wrappedBufferView.count) + byteOffset ]; 178 | 179 | this._handleRequest({ "id" : wrappedBufferView.id, 180 | "range" : range, 181 | "type" : buffer.description.type, 182 | "path" : buffer.description.path, 183 | "delegate" : delegate, 184 | "ctx" : ctx }, null); 185 | } 186 | }, 187 | 188 | getBuffer: { 189 | 190 | value: function(wrappedBufferView, delegate, ctx) { 191 | 192 | var savedBuffer = this._getResource(wrappedBufferView.id); 193 | if (savedBuffer) { 194 | return savedBuffer; 195 | } else { 196 | this._handleWrappedBufferViewResourceLoading(wrappedBufferView, delegate, ctx); 197 | } 198 | 199 | return null; 200 | } 201 | }, 202 | 203 | getFile: { 204 | 205 | value: function(request, delegate, ctx) { 206 | 207 | request.delegate = delegate; 208 | request.ctx = ctx; 209 | 210 | this._handleRequest({ "id" : request.id, 211 | "path" : request.path, 212 | "range" : [ 0 ], 213 | "type" : "text", 214 | "delegate" : delegate, 215 | "ctx" : ctx }, null); 216 | 217 | return null; 218 | } 219 | }, 220 | }); 221 | -------------------------------------------------------------------------------- /js/loaders/DDSLoader.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @author mrdoob / http://mrdoob.com/ 3 | */ 4 | 5 | THREE.DDSLoader = function () { 6 | 7 | this._parser = THREE.DDSLoader.parse; 8 | 9 | }; 10 | 11 | THREE.DDSLoader.prototype = Object.create( THREE.CompressedTextureLoader.prototype ); 12 | THREE.DDSLoader.prototype.constructor = THREE.DDSLoader; 13 | 14 | THREE.DDSLoader.parse = function ( buffer, loadMipmaps ) { 15 | 16 | var dds = { mipmaps: [], width: 0, height: 0, format: null, mipmapCount: 1 }; 17 | 18 | // Adapted from @toji's DDS utils 19 | // https://github.com/toji/webgl-texture-utils/blob/master/texture-util/dds.js 20 | 21 | // All values and structures referenced from: 22 | // http://msdn.microsoft.com/en-us/library/bb943991.aspx/ 23 | 24 | var DDS_MAGIC = 0x20534444; 25 | 26 | var DDSD_CAPS = 0x1, 27 | DDSD_HEIGHT = 0x2, 28 | DDSD_WIDTH = 0x4, 29 | DDSD_PITCH = 0x8, 30 | DDSD_PIXELFORMAT = 0x1000, 31 | DDSD_MIPMAPCOUNT = 0x20000, 32 | DDSD_LINEARSIZE = 0x80000, 33 | DDSD_DEPTH = 0x800000; 34 | 35 | var DDSCAPS_COMPLEX = 0x8, 36 | DDSCAPS_MIPMAP = 0x400000, 37 | DDSCAPS_TEXTURE = 0x1000; 38 | 39 | var DDSCAPS2_CUBEMAP = 0x200, 40 | DDSCAPS2_CUBEMAP_POSITIVEX = 0x400, 41 | DDSCAPS2_CUBEMAP_NEGATIVEX = 0x800, 42 | DDSCAPS2_CUBEMAP_POSITIVEY = 0x1000, 43 | DDSCAPS2_CUBEMAP_NEGATIVEY = 0x2000, 44 | DDSCAPS2_CUBEMAP_POSITIVEZ = 0x4000, 45 | DDSCAPS2_CUBEMAP_NEGATIVEZ = 0x8000, 46 | DDSCAPS2_VOLUME = 0x200000; 47 | 48 | var DDPF_ALPHAPIXELS = 0x1, 49 | DDPF_ALPHA = 0x2, 50 | DDPF_FOURCC = 0x4, 51 | DDPF_RGB = 0x40, 52 | DDPF_YUV = 0x200, 53 | DDPF_LUMINANCE = 0x20000; 54 | 55 | function fourCCToInt32( value ) { 56 | 57 | return value.charCodeAt( 0 ) + 58 | ( value.charCodeAt( 1 ) << 8 ) + 59 | ( value.charCodeAt( 2 ) << 16 ) + 60 | ( value.charCodeAt( 3 ) << 24 ); 61 | 62 | } 63 | 64 | function int32ToFourCC( value ) { 65 | 66 | return String.fromCharCode( 67 | value & 0xff, 68 | ( value >> 8 ) & 0xff, 69 | ( value >> 16 ) & 0xff, 70 | ( value >> 24 ) & 0xff 71 | ); 72 | 73 | } 74 | 75 | function loadARGBMip( buffer, dataOffset, width, height ) { 76 | 77 | var dataLength = width * height * 4; 78 | var srcBuffer = new Uint8Array( buffer, dataOffset, dataLength ); 79 | var byteArray = new Uint8Array( dataLength ); 80 | var dst = 0; 81 | var src = 0; 82 | for ( var y = 0; y < height; y ++ ) { 83 | 84 | for ( var x = 0; x < width; x ++ ) { 85 | 86 | var b = srcBuffer[ src ]; src ++; 87 | var g = srcBuffer[ src ]; src ++; 88 | var r = srcBuffer[ src ]; src ++; 89 | var a = srcBuffer[ src ]; src ++; 90 | byteArray[ dst ] = r; dst ++; //r 91 | byteArray[ dst ] = g; dst ++; //g 92 | byteArray[ dst ] = b; dst ++; //b 93 | byteArray[ dst ] = a; dst ++; //a 94 | 95 | } 96 | 97 | } 98 | return byteArray; 99 | 100 | } 101 | 102 | var FOURCC_DXT1 = fourCCToInt32( "DXT1" ); 103 | var FOURCC_DXT3 = fourCCToInt32( "DXT3" ); 104 | var FOURCC_DXT5 = fourCCToInt32( "DXT5" ); 105 | var FOURCC_ETC1 = fourCCToInt32( "ETC1" ); 106 | 107 | var headerLengthInt = 31; // The header length in 32 bit ints 108 | 109 | // Offsets into the header array 110 | 111 | var off_magic = 0; 112 | 113 | var off_size = 1; 114 | var off_flags = 2; 115 | var off_height = 3; 116 | var off_width = 4; 117 | 118 | var off_mipmapCount = 7; 119 | 120 | var off_pfFlags = 20; 121 | var off_pfFourCC = 21; 122 | var off_RGBBitCount = 22; 123 | var off_RBitMask = 23; 124 | var off_GBitMask = 24; 125 | var off_BBitMask = 25; 126 | var off_ABitMask = 26; 127 | 128 | var off_caps = 27; 129 | var off_caps2 = 28; 130 | var off_caps3 = 29; 131 | var off_caps4 = 30; 132 | 133 | // Parse header 134 | 135 | var header = new Int32Array( buffer, 0, headerLengthInt ); 136 | 137 | if ( header[ off_magic ] !== DDS_MAGIC ) { 138 | 139 | console.error( 'THREE.DDSLoader.parse: Invalid magic number in DDS header.' ); 140 | return dds; 141 | 142 | } 143 | 144 | if ( ! header[ off_pfFlags ] & DDPF_FOURCC ) { 145 | 146 | console.error( 'THREE.DDSLoader.parse: Unsupported format, must contain a FourCC code.' ); 147 | return dds; 148 | 149 | } 150 | 151 | var blockBytes; 152 | 153 | var fourCC = header[ off_pfFourCC ]; 154 | 155 | var isRGBAUncompressed = false; 156 | 157 | switch ( fourCC ) { 158 | 159 | case FOURCC_DXT1: 160 | 161 | blockBytes = 8; 162 | dds.format = THREE.RGB_S3TC_DXT1_Format; 163 | break; 164 | 165 | case FOURCC_DXT3: 166 | 167 | blockBytes = 16; 168 | dds.format = THREE.RGBA_S3TC_DXT3_Format; 169 | break; 170 | 171 | case FOURCC_DXT5: 172 | 173 | blockBytes = 16; 174 | dds.format = THREE.RGBA_S3TC_DXT5_Format; 175 | break; 176 | 177 | case FOURCC_ETC1: 178 | 179 | blockBytes = 8; 180 | dds.format = THREE.RGB_ETC1_Format; 181 | break; 182 | 183 | default: 184 | 185 | if ( header[ off_RGBBitCount ] === 32 186 | && header[ off_RBitMask ] & 0xff0000 187 | && header[ off_GBitMask ] & 0xff00 188 | && header[ off_BBitMask ] & 0xff 189 | && header[ off_ABitMask ] & 0xff000000 ) { 190 | 191 | isRGBAUncompressed = true; 192 | blockBytes = 64; 193 | dds.format = THREE.RGBAFormat; 194 | 195 | } else { 196 | 197 | console.error( 'THREE.DDSLoader.parse: Unsupported FourCC code ', int32ToFourCC( fourCC ) ); 198 | return dds; 199 | 200 | } 201 | } 202 | 203 | dds.mipmapCount = 1; 204 | 205 | if ( header[ off_flags ] & DDSD_MIPMAPCOUNT && loadMipmaps !== false ) { 206 | 207 | dds.mipmapCount = Math.max( 1, header[ off_mipmapCount ] ); 208 | 209 | } 210 | 211 | var caps2 = header[ off_caps2 ]; 212 | dds.isCubemap = caps2 & DDSCAPS2_CUBEMAP ? true : false; 213 | if ( dds.isCubemap && ( 214 | ! ( caps2 & DDSCAPS2_CUBEMAP_POSITIVEX ) || 215 | ! ( caps2 & DDSCAPS2_CUBEMAP_NEGATIVEX ) || 216 | ! ( caps2 & DDSCAPS2_CUBEMAP_POSITIVEY ) || 217 | ! ( caps2 & DDSCAPS2_CUBEMAP_NEGATIVEY ) || 218 | ! ( caps2 & DDSCAPS2_CUBEMAP_POSITIVEZ ) || 219 | ! ( caps2 & DDSCAPS2_CUBEMAP_NEGATIVEZ ) 220 | ) ) { 221 | 222 | console.error( 'THREE.DDSLoader.parse: Incomplete cubemap faces' ); 223 | return dds; 224 | 225 | } 226 | 227 | dds.width = header[ off_width ]; 228 | dds.height = header[ off_height ]; 229 | 230 | var dataOffset = header[ off_size ] + 4; 231 | 232 | // Extract mipmaps buffers 233 | 234 | var faces = dds.isCubemap ? 6 : 1; 235 | 236 | for ( var face = 0; face < faces; face ++ ) { 237 | 238 | var width = dds.width; 239 | var height = dds.height; 240 | 241 | for ( var i = 0; i < dds.mipmapCount; i ++ ) { 242 | 243 | if ( isRGBAUncompressed ) { 244 | 245 | var byteArray = loadARGBMip( buffer, dataOffset, width, height ); 246 | var dataLength = byteArray.length; 247 | 248 | } else { 249 | 250 | var dataLength = Math.max( 4, width ) / 4 * Math.max( 4, height ) / 4 * blockBytes; 251 | var byteArray = new Uint8Array( buffer, dataOffset, dataLength ); 252 | 253 | } 254 | 255 | var mipmap = { "data": byteArray, "width": width, "height": height }; 256 | dds.mipmaps.push( mipmap ); 257 | 258 | dataOffset += dataLength; 259 | 260 | width = Math.max( width >> 1, 1 ); 261 | height = Math.max( height >> 1, 1 ); 262 | 263 | } 264 | 265 | } 266 | 267 | return dds; 268 | 269 | }; 270 | -------------------------------------------------------------------------------- /js/loaders/VTKLoader.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author mrdoob / http://mrdoob.com/ 3 | * @author Alex Pletzer 4 | */ 5 | 6 | THREE.VTKLoader = function ( manager ) { 7 | 8 | this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager; 9 | 10 | }; 11 | 12 | THREE.VTKLoader.prototype = { 13 | 14 | constructor: THREE.VTKLoader, 15 | 16 | load: function ( url, onLoad, onProgress, onError ) { 17 | 18 | // Will we bump into trouble reading the whole file into memory? 19 | var scope = this; 20 | var loader = new THREE.XHRLoader( scope.manager ); 21 | loader.load( url, function ( text ) { 22 | 23 | onLoad( scope.parse( text ) ); 24 | 25 | }, 26 | 27 | onProgress, onError ); 28 | 29 | }, 30 | 31 | parse: function ( data ) { 32 | 33 | // connectivity of the triangles 34 | var indices = []; 35 | 36 | // triangles vertices 37 | var positions = []; 38 | 39 | // red, green, blue colors in the range 0 to 1 40 | var colors = []; 41 | 42 | // normal vector, one per vertex 43 | var normals = []; 44 | 45 | var result; 46 | 47 | // pattern for reading vertices, 3 floats or integers 48 | var pat3Floats = /(\-?\d+\.?[\d\-\+e]*)\s+(\-?\d+\.?[\d\-\+e]*)\s+(\-?\d+\.?[\d\-\+e]*)/g; 49 | 50 | // pattern for connectivity, an integer followed by any number of ints 51 | // the first integer is the number of polygon nodes 52 | var patConnectivity = /^(\d+)\s+([\s\d]*)/; 53 | 54 | // indicates start of vertex data section 55 | var patPOINTS = /^POINTS /; 56 | 57 | // indicates start of polygon connectivity section 58 | var patPOLYGONS = /^POLYGONS /; 59 | 60 | // POINT_DATA number_of_values 61 | var patPOINT_DATA = /^POINT_DATA[ ]+(\d+)/; 62 | 63 | // CELL_DATA number_of_polys 64 | var patCELL_DATA = /^CELL_DATA[ ]+(\d+)/; 65 | 66 | // Start of color section 67 | var patCOLOR_SCALARS = /^COLOR_SCALARS[ ]+(\w+)[ ]+3/; 68 | 69 | // NORMALS Normals float 70 | var patNORMALS = /^NORMALS[ ]+(\w+)[ ]+(\w+)/; 71 | 72 | var inPointsSection = false; 73 | var inPolygonsSection = false; 74 | var inPointDataSection = false; 75 | var inCellDataSection = false; 76 | var inColorSection = false; 77 | var inNormalsSection = false; 78 | 79 | var lines = data.split( '\n' ); 80 | 81 | for ( var i in lines ) { 82 | 83 | var line = lines[ i ]; 84 | 85 | if ( inPointsSection ) { 86 | 87 | // get the vertices 88 | while ( ( result = pat3Floats.exec( line ) ) !== null ) { 89 | 90 | var x = parseFloat( result[ 1 ] ); 91 | var y = parseFloat( result[ 2 ] ); 92 | var z = parseFloat( result[ 3 ] ); 93 | positions.push( x, y, z ); 94 | 95 | } 96 | 97 | } else if ( inPolygonsSection ) { 98 | 99 | if ( ( result = patConnectivity.exec( line ) ) !== null ) { 100 | 101 | // numVertices i0 i1 i2 ... 102 | var numVertices = parseInt( result[ 1 ] ); 103 | var inds = result[ 2 ].split( /\s+/ ); 104 | 105 | if ( numVertices >= 3 ) { 106 | 107 | var i0 = parseInt( inds[ 0 ] ); 108 | var i1, i2; 109 | var k = 1; 110 | // split the polygon in numVertices - 2 triangles 111 | for ( var j = 0; j < numVertices - 2; ++ j ) { 112 | 113 | i1 = parseInt( inds[ k ] ); 114 | i2 = parseInt( inds[ k + 1 ] ); 115 | indices.push( i0, i1, i2 ); 116 | k ++; 117 | 118 | } 119 | 120 | } 121 | 122 | } 123 | 124 | } else if ( inPointDataSection || inCellDataSection ) { 125 | 126 | if ( inColorSection ) { 127 | 128 | // Get the colors 129 | 130 | while ( ( result = pat3Floats.exec( line ) ) !== null ) { 131 | 132 | var r = parseFloat( result[ 1 ] ); 133 | var g = parseFloat( result[ 2 ] ); 134 | var b = parseFloat( result[ 3 ] ); 135 | colors.push( r, g, b ); 136 | 137 | } 138 | 139 | } else if ( inNormalsSection ) { 140 | 141 | // Get the normal vectors 142 | 143 | while ( ( result = pat3Floats.exec( line ) ) !== null ) { 144 | 145 | var nx = parseFloat( result[ 1 ] ); 146 | var ny = parseFloat( result[ 2 ] ); 147 | var nz = parseFloat( result[ 3 ] ); 148 | normals.push( nx, ny, nz ); 149 | 150 | } 151 | 152 | } 153 | 154 | } 155 | 156 | if ( patPOLYGONS.exec( line ) !== null ) { 157 | 158 | inPolygonsSection = true; 159 | inPointsSection = false; 160 | 161 | } else if ( patPOINTS.exec( line ) !== null ) { 162 | 163 | inPolygonsSection = false; 164 | inPointsSection = true; 165 | 166 | } else if ( patPOINT_DATA.exec( line ) !== null ) { 167 | 168 | inPointDataSection = true; 169 | inPointsSection = false; 170 | inPolygonsSection = false; 171 | 172 | } else if ( patCELL_DATA.exec( line ) !== null ) { 173 | 174 | inCellDataSection = true; 175 | inPointsSection = false; 176 | inPolygonsSection = false; 177 | 178 | } else if ( patCOLOR_SCALARS.exec( line ) !== null ) { 179 | 180 | inColorSection = true; 181 | inNormalsSection = false; 182 | inPointsSection = false; 183 | inPolygonsSection = false; 184 | 185 | } else if ( patNORMALS.exec( line ) !== null ) { 186 | 187 | inNormalsSection = true; 188 | inColorSection = false; 189 | inPointsSection = false; 190 | inPolygonsSection = false; 191 | 192 | } 193 | 194 | } 195 | 196 | var geometry; 197 | var stagger = 'point'; 198 | 199 | if ( colors.length == indices.length ) { 200 | 201 | stagger = 'cell'; 202 | 203 | } 204 | 205 | if ( stagger == 'point' ) { 206 | 207 | // Nodal. Use BufferGeometry 208 | geometry = new THREE.BufferGeometry(); 209 | geometry.setIndex( new THREE.BufferAttribute( new ( indices.length > 65535 ? Uint32Array : Uint16Array )( indices ), 1 ) ); 210 | geometry.addAttribute( 'position', new THREE.BufferAttribute( new Float32Array( positions ), 3 ) ); 211 | 212 | if ( colors.length == positions.length ) { 213 | 214 | geometry.addAttribute( 'color', new THREE.BufferAttribute( new Float32Array( colors ), 3 ) ); 215 | 216 | } 217 | 218 | if ( normals.length == positions.length ) { 219 | 220 | geometry.addAttribute( 'normal', new THREE.BufferAttribute( new Float32Array( normals ), 3 ) ); 221 | 222 | } 223 | 224 | } else { 225 | 226 | // Cell centered colors. The only way to attach a solid color to each triangle 227 | // is to use Geometry, which is less efficient than BufferGeometry 228 | geometry = new THREE.Geometry(); 229 | 230 | var numTriangles = indices.length / 3; 231 | var numPoints = positions.length / 3; 232 | var va, vb, vc; 233 | var face; 234 | var ia, ib, ic; 235 | var x, y, z; 236 | var r, g, b; 237 | 238 | for ( var j = 0; j < numPoints; ++ j ) { 239 | 240 | x = positions[ 3 * j + 0 ]; 241 | y = positions[ 3 * j + 1 ]; 242 | z = positions[ 3 * j + 2 ]; 243 | geometry.vertices.push( new THREE.Vector3( x, y, z ) ); 244 | 245 | } 246 | 247 | for ( var i = 0; i < numTriangles; ++ i ) { 248 | 249 | ia = indices[ 3 * i + 0 ]; 250 | ib = indices[ 3 * i + 1 ]; 251 | ic = indices[ 3 * i + 2 ]; 252 | geometry.faces.push( new THREE.Face3( ia, ib, ic ) ); 253 | 254 | } 255 | 256 | if ( colors.length == numTriangles * 3 ) { 257 | 258 | for ( var i = 0; i < numTriangles; ++ i ) { 259 | 260 | face = geometry.faces[ i ]; 261 | r = colors[ 3 * i + 0 ]; 262 | g = colors[ 3 * i + 1 ]; 263 | b = colors[ 3 * i + 2 ]; 264 | face.color = new THREE.Color().setRGB( r, g, b ); 265 | 266 | } 267 | 268 | } 269 | 270 | } 271 | 272 | return geometry; 273 | 274 | } 275 | 276 | }; 277 | 278 | THREE.EventDispatcher.prototype.apply( THREE.VTKLoader.prototype ); 279 | -------------------------------------------------------------------------------- /zajal/draw/three.cljs: -------------------------------------------------------------------------------- 1 | (ns virtual-three) 2 | 3 | (deftype RendererNode [props camera scene ^:mutable _cache] 4 | Object 5 | (apply-property 6 | [self property value] 7 | (.render self) 8 | (case property 9 | :viewport 10 | (.. _cache 11 | (setViewport (:x value) 12 | (:y value) 13 | (:width value) 14 | (:height value))) 15 | :size 16 | (.. _cache 17 | (setSize (:width value) 18 | (:height value))) 19 | ;; TODO the rest 20 | nil)) 21 | (render 22 | [self] 23 | (when-not _cache 24 | (set! _cache (new js/THREE.WebGLRenderer 25 | (clj->js props))) 26 | (doseq [prop (keys props)] 27 | (.apply-property self prop (props prop))) 28 | (.render camera) 29 | (.render scene)) 30 | _cache) 31 | (reconcile 32 | [self other] 33 | (when-not (identical? self other) 34 | (when (not (identical? (type self) (type other))) 35 | (throw "Different types not supported")) 36 | (let [other-props (.-props other)] 37 | (doseq [prop (keys other-props)] 38 | (.apply-property self prop (other-props prop)))) 39 | ;; TODO removing props 40 | (.reconcile camera (.-camera other)) 41 | (.reconcile scene (.-scene other)) 42 | ;; propagate the cache 43 | (set! (.-_cache other) _cache)))) 44 | 45 | (deftype Object3DNode [ctor props children ^:mutable _cache] 46 | Object 47 | (apply-property 48 | [self property value] 49 | (.render self) 50 | (case property 51 | :rotation 52 | (.. _cache -rotation (copy value)) 53 | :position 54 | (.. _cache -position (copy value)) 55 | :scale 56 | (.. _cache -scale (copy value)) 57 | ;; TODO the rest 58 | (aset _cache (.-fqn property) value))) 59 | (render 60 | [self] 61 | (when-not _cache 62 | (set! _cache (js/Object.create (.-prototype ctor))) 63 | (.call ctor _cache) 64 | (doseq [prop (keys props)] 65 | (.apply-property self prop (props prop))) 66 | (when children 67 | (doseq [child children] 68 | (.add _cache 69 | (if (.-uuid child) 70 | child 71 | (.render child)))))) 72 | _cache) 73 | (reconcile 74 | [self other] 75 | (when-not (identical? self other) 76 | (when (not (identical? (type self) (type other))) 77 | (throw "Different types not supported")) 78 | ;; TODO removing props 79 | (when (not (identical? props (.-props other))) 80 | (let [other-props (.-props other)] 81 | (doseq [prop (keys other-props)] 82 | (.apply-property self prop (other-props prop))))) 83 | ;; children 84 | (when (and (not (nil? children)) 85 | (not (nil? (.-children other)))) 86 | (cond 87 | (seq? children) 88 | (loop [self-children children 89 | other-children (.-children other)] 90 | (let [self-child (first self-children) 91 | other-child (first other-children) 92 | self-next (next self-children) 93 | other-next (next other-children)] 94 | (cond (nil? self-child) 95 | (.. _cache (add (.render other-child))) 96 | (nil? other-child) 97 | (.. _cache (remove (.render self-child))) 98 | :else 99 | (.reconcile self-child other-child)) 100 | (when (or (not (nil? self-next)) 101 | (not (nil? other-next))) 102 | (recur self-next other-next)))) 103 | :else 104 | (let [self-children children 105 | other-children (.-children other) 106 | self-children-count (count self-children) 107 | other-children-count (count other-children) 108 | min-length (js/Math.min self-children-count other-children-count)] 109 | ;; TODO what is the fastest way to iterate children? 110 | (loop [i 0] 111 | (let [self-child (nth self-children i) 112 | other-child (nth other-children i)] 113 | (when (not (identical? self-child other-child)) 114 | (.reconcile (nth self-children i) 115 | (nth other-children i)))) 116 | (when (< i (dec min-length)) 117 | (recur (inc i)))) 118 | (if (> other-children-count 119 | self-children-count) 120 | ;; more elements 121 | (loop [i min-length] 122 | (.. _cache (add (.render (nth other-children i)))) 123 | (when (< i (dec other-children-count)) 124 | (recur (inc i)))) 125 | (if (> self-children-count 126 | other-children-count) 127 | ;; fewer elements 128 | (loop [i min-length] 129 | (.. _cache (remove (.-_cache (nth self-children i)))) 130 | (when (< i (dec (count self-children))) 131 | (recur (inc i))))))))) 132 | ;; propagate the cache 133 | (set! (.-_cache other) _cache)))) 134 | 135 | (defn renderer [props camera scene] 136 | (RendererNode. props camera scene nil)) 137 | 138 | (defn camera [props children] 139 | (Object3DNode. js/THREE.PerspectiveCamera props children nil)) 140 | 141 | (defn scene [props children] 142 | (Object3DNode. js/THREE.Scene props children nil)) 143 | 144 | (defn mesh [props children] 145 | (Object3DNode. js/THREE.Mesh props children nil)) 146 | 147 | (defn line [props children] 148 | (Object3DNode. js/THREE.Line props children nil)) 149 | 150 | (defn group [props children] 151 | (Object3DNode. js/THREE.Group props children nil)) 152 | 153 | (def box-geo (js/THREE.BoxGeometry. 1 1 1)) 154 | (def mesh-mat (js/THREE.MeshBasicMaterial. 155 | #js {:wireframe true 156 | :color "white"})) 157 | 158 | (defn v3 [x y z] (js/THREE.Vector3. x y z)) 159 | (defn euler [x y z] (js/THREE.Euler. x y z)) 160 | 161 | (defn input [] nil) 162 | 163 | (defn sketch [start step draw] 164 | (let [state (atom start) 165 | renderer (draw start) 166 | current (atom renderer)] 167 | (js/document.body.appendChild (.. renderer render -domElement)) 168 | (letfn [(render-loop 169 | [t] 170 | (.requestAnimationFrame js/window render-loop) 171 | (swap! state step (input)) 172 | (let [next (draw @state)] 173 | (when (not (identical? @current next)) 174 | (.reconcile @current next) 175 | (reset! current next) 176 | (.. @current render (render (.. @current -scene render) 177 | (.. @current -camera render) )))))] 178 | (render-loop 0) 179 | (.render renderer))) 180 | :ok) 181 | 182 | (def resources (atom {})) 183 | 184 | (.. (new js/THREE.OBJLoader) 185 | (load "models/watercraftPack_003.obj" 186 | (fn [obj] 187 | (doseq [child (.-children obj)] 188 | (js/console.log child) 189 | (aset child "material" mesh-mat)) 190 | (swap! resources assoc :boat obj)))) 191 | 192 | (defn step [x] (inc x)) 193 | 194 | (defn draw [t] 195 | (renderer 196 | {:size {:width 500 197 | :height 500}} 198 | (camera {:position (v3 0 5 5) 199 | :rotation (euler -1 0 0)}) 200 | (scene 201 | {} 202 | [(group {:position (v3 (* 0.1 1) (* 0.1 3) (* 0.5 4)) 203 | :rotation (euler (* 0.1 t) 0 (* 0.05 t)) 204 | :scale (v3 1 1 1)} 205 | #_ 206 | [(mesh {:geometry box-geo 207 | :material mesh-mat})] 208 | (when (@resources :boat) 209 | [(@resources :boat)]))]))) 210 | 211 | (sketch 0 #'step #'draw) -------------------------------------------------------------------------------- /js/loaders/PCDLoader.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Filipe Caixeta / http://filipecaixeta.com.br 3 | * 4 | * Description: A THREE loader for PCD ascii and binary files. 5 | * 6 | * Limitations: Compressed binary files are not supported. 7 | * 8 | */ 9 | 10 | THREE.PCDLoader = function( manager ) { 11 | 12 | this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager; 13 | this.littleEndian = true; 14 | 15 | }; 16 | THREE.PCDLoader.prototype = { 17 | 18 | constructor: THREE.PCDLoader, 19 | 20 | load: function( url, onLoad, onProgress, onError ) { 21 | 22 | var scope = this; 23 | 24 | var loader = new THREE.XHRLoader( scope.manager ); 25 | loader.setResponseType( 'arraybuffer' ); 26 | loader.load( url, function( data ) { 27 | 28 | onLoad( scope.parse( data, url ) ); 29 | 30 | }, onProgress, onError ); 31 | 32 | }, 33 | 34 | binarryToStr: function( data ) { 35 | 36 | text = ""; 37 | var charArray = new Uint8Array( data ); 38 | for ( var i = 0; i < data.byteLength; i ++ ) { 39 | 40 | text += String.fromCharCode( charArray[ i ] ); 41 | 42 | } 43 | return text; 44 | 45 | }, 46 | 47 | parseHeader: function( data ) { 48 | 49 | var PCDheader = {}; 50 | var result1 = data.search( /[\r\n]DATA\s(\S*)\s/i ); 51 | var result2 = /[\r\n]DATA\s(\S*)\s/i.exec( data.substr( result1 - 1 ) ); 52 | PCDheader.data = result2[ 1 ]; 53 | PCDheader.headerLen = result2[ 0 ].length + result1; 54 | PCDheader.str = data.substr( 0, PCDheader.headerLen ); 55 | // Remove comments 56 | PCDheader.str = PCDheader.str.replace( /\#.*/gi, "" ); 57 | PCDheader.version = /VERSION (.*)/i.exec( PCDheader.str ); 58 | if ( PCDheader.version != null ) 59 | PCDheader.version = parseFloat( PCDheader.version[ 1 ] ); 60 | PCDheader.fields = /FIELDS (.*)/i.exec( PCDheader.str ); 61 | if ( PCDheader.fields != null ) 62 | PCDheader.fields = PCDheader.fields[ 1 ].split( " " ); 63 | PCDheader.size = /SIZE (.*)/i.exec( PCDheader.str ); 64 | if ( PCDheader.size != null ) 65 | PCDheader.size = PCDheader.size[ 1 ].split( " " ).map( function( x ) { 66 | 67 | return parseInt( x, 10 ); 68 | 69 | } ); 70 | PCDheader.type = /TYPE (.*)/i.exec( PCDheader.str ); 71 | if ( PCDheader.type != null ) 72 | PCDheader.type = PCDheader.type[ 1 ].split( " " ); 73 | PCDheader.count = /COUNT (.*)/i.exec( PCDheader.str ); 74 | if ( PCDheader.count != null ) 75 | PCDheader.count = PCDheader.count[ 1 ].split( " " ).map( function( x ) { 76 | 77 | return parseInt( x, 10 ); 78 | 79 | } ); 80 | PCDheader.width = /WIDTH (.*)/i.exec( PCDheader.str ); 81 | if ( PCDheader.width != null ) 82 | PCDheader.width = parseInt( PCDheader.width[ 1 ] ); 83 | PCDheader.height = /HEIGHT (.*)/i.exec( PCDheader.str ); 84 | if ( PCDheader.height != null ) 85 | PCDheader.height = parseInt( PCDheader.height[ 1 ] ); 86 | PCDheader.viewpoint = /VIEWPOINT (.*)/i.exec( PCDheader.str ); 87 | if ( PCDheader.viewpoint != null ) 88 | PCDheader.viewpoint = PCDheader.viewpoint[ 1 ]; 89 | PCDheader.points = /POINTS (.*)/i.exec( PCDheader.str ); 90 | if ( PCDheader.points != null ) 91 | PCDheader.points = parseInt( PCDheader.points[ 1 ], 10 ); 92 | if ( PCDheader.points == null ) 93 | PCDheader.points = PCDheader.width * PCDheader.height; 94 | 95 | if ( PCDheader.count == null ) { 96 | 97 | PCDheader.count = []; 98 | for ( var i = 0; i < PCDheader.fields; i ++ ) 99 | PCDheader.count.push( 1 ); 100 | 101 | } 102 | 103 | PCDheader.offset = {} 104 | var sizeSum = 0; 105 | for ( var i = 0; i < PCDheader.fields.length; i ++ ) { 106 | 107 | if ( PCDheader.data == "ascii" ) { 108 | 109 | PCDheader.offset[ PCDheader.fields[ i ]] = i; 110 | 111 | } else { 112 | 113 | PCDheader.offset[ PCDheader.fields[ i ]] = sizeSum; 114 | sizeSum += PCDheader.size[ i ]; 115 | 116 | } 117 | 118 | } 119 | // For binary only 120 | PCDheader.rowSize = sizeSum; 121 | 122 | return PCDheader; 123 | 124 | }, 125 | 126 | parse: function( data, url ) { 127 | 128 | textData = this.binarryToStr( data ); 129 | 130 | // Parse the header 131 | // Header is always ascii format 132 | var PCDheader = this.parseHeader( textData ); 133 | 134 | // Parse the data 135 | var position = false; 136 | if ( PCDheader.offset.x != undefined ) 137 | position = new Float32Array( PCDheader.points * 3 ); 138 | var color = false; 139 | if ( PCDheader.offset.rgb != undefined) 140 | color = new Float32Array( PCDheader.points * 3 ); 141 | var normal = false; 142 | if ( PCDheader.offset.normal_x != undefined ) 143 | normal = new Float32Array( PCDheader.points * 3 ); 144 | 145 | if ( PCDheader.data == "ascii" ) { 146 | 147 | var offset = PCDheader.offset; 148 | var pcdData = textData.substr( PCDheader.headerLen ); 149 | var lines = pcdData.split( '\n' ); 150 | var i3 = 0; 151 | for ( var i = 0; i < lines.length; i ++, i3 += 3 ) { 152 | 153 | var line = lines[ i ].split( " " ); 154 | if ( offset.x != undefined ) { 155 | 156 | position[ i3 + 0 ] = parseFloat( line[ offset.x ] ); 157 | position[ i3 + 1 ] = parseFloat( line[ offset.y ] ); 158 | position[ i3 + 2 ] = parseFloat( line[ offset.z ] ); 159 | 160 | } 161 | if ( offset.rgb != undefined ) { 162 | 163 | var c = new Float32Array([parseFloat( line[ offset.rgb ] )]); 164 | var dataview = new DataView( c.buffer, 0 ); 165 | color[ i3 + 0 ] = dataview.getUint8(0)/255.0; 166 | color[ i3 + 1 ] = dataview.getUint8(1)/255.0; 167 | color[ i3 + 2 ] = dataview.getUint8(2)/255.0; 168 | 169 | } 170 | if ( offset.normal_x != undefined ) { 171 | 172 | normal[ i3 + 0 ] = parseFloat( line[ offset.normal_x ] ); 173 | normal[ i3 + 1 ] = parseFloat( line[ offset.normal_y ] ); 174 | normal[ i3 + 2 ] = parseFloat( line[ offset.normal_z ] ); 175 | 176 | } 177 | 178 | } 179 | 180 | } 181 | 182 | if ( PCDheader.data == "binary_compressed" ) { 183 | 184 | console.error( 'THREE.PCDLoader: binary_compressed files are not supported' ); 185 | return; 186 | 187 | } 188 | 189 | if ( PCDheader.data == "binary" ) { 190 | 191 | var row = 0; 192 | var dataview = new DataView( data, PCDheader.headerLen ); 193 | var i = 0; 194 | var offset = PCDheader.offset; 195 | for ( var i3 = 0; i < PCDheader.points; i3 += 3, row += PCDheader.rowSize, i ++ ) { 196 | 197 | if ( offset.x != undefined ) { 198 | 199 | position[ i3 + 0 ] = dataview.getFloat32( row + offset.x, this.littleEndian ); 200 | position[ i3 + 1 ] = dataview.getFloat32( row + offset.y, this.littleEndian ); 201 | position[ i3 + 2 ] = dataview.getFloat32( row + offset.z, this.littleEndian ); 202 | 203 | } 204 | if ( offset.rgb != undefined ) { 205 | 206 | color[ i3 + 0 ] = dataview.getUint8( row + offset.rgb + 0 ) / 255.0; 207 | color[ i3 + 1 ] = dataview.getUint8( row + offset.rgb + 1 ) / 255.0; 208 | color[ i3 + 2 ] = dataview.getUint8( row + offset.rgb + 2 ) / 255.0; 209 | 210 | } 211 | if ( offset.normal_x != undefined ) { 212 | 213 | normal[ i3 + 0 ] = dataview.getFloat32( row + offset.normal_x, this.littleEndian ); 214 | normal[ i3 + 1 ] = dataview.getFloat32( row + offset.normal_y, this.littleEndian ); 215 | normal[ i3 + 2 ] = dataview.getFloat32( row + offset.normal_z, this.littleEndian ); 216 | 217 | } 218 | 219 | } 220 | 221 | } 222 | 223 | var geometry = new THREE.BufferGeometry(); 224 | if ( position != false ) 225 | geometry.addAttribute( 'position', new THREE.BufferAttribute( position, 3 ) ); 226 | if ( color != false ) 227 | geometry.addAttribute( 'color', new THREE.BufferAttribute( color, 3 ) ); 228 | if ( normal != false ) 229 | geometry.addAttribute( 'normal', new THREE.BufferAttribute( normal, 3 ) ); 230 | 231 | geometry.computeBoundingSphere(); 232 | 233 | var material = new THREE.PointsMaterial( { size: 0.005, 234 | vertexColors: !(color == false) } ); 235 | if ( color == false ) 236 | material.color.setHex( Math.random() * 0xffffff ); 237 | 238 | var mesh = new THREE.Points( geometry, material ); 239 | var name = url.split( '' ).reverse().join( '' ); 240 | name = /([^\/]*)/.exec( name ); 241 | name = name[ 1 ].split( '' ).reverse().join( '' ); 242 | mesh.name = name; 243 | mesh.PCDheader = PCDheader; 244 | 245 | return mesh; 246 | 247 | }, 248 | 249 | }; 250 | 251 | THREE.EventDispatcher.prototype.apply( THREE.PCDLoader.prototype ); 252 | -------------------------------------------------------------------------------- /scratch/virtual-three.js: -------------------------------------------------------------------------------- 1 | //////// THREE //////// 2 | 3 | function mergeProps(obj, fromProps, toProps) { 4 | // check for changed/removed props 5 | for (var fromKey in fromProps) { 6 | // key missing, remove attribute it 7 | if (toProps[fromKey] === undefined) { 8 | delete obj[fromKey]; 9 | 10 | } else { 11 | var fromValue = fromProps[fromKey]; 12 | var toValue = toProps[fromKey]; 13 | 14 | // same value, move on 15 | if(fromValue === toValue) 16 | continue; 17 | 18 | // TODO deep prop diff? 19 | // changed value, update attribute 20 | 21 | switch(fromKey) { 22 | case "rotation": 23 | obj.rotation.set(toValue.x, toValue.y, toValue.z); 24 | break; 25 | case "position": 26 | obj.position.set(toValue.x, toValue.y, toValue.z); 27 | break; 28 | default: 29 | obj[fromKey] = toValue; 30 | } 31 | } 32 | } 33 | 34 | // check for new props 35 | for (var toKey in toProps) { 36 | if (!(toKey in fromProps)) { 37 | obj[toKey] = toProps[toValue]; 38 | } 39 | } 40 | } 41 | 42 | // TODO multiple cameras 43 | function ThreeRendererNode(props, camera, scene) { 44 | this.type = "three-renderer" 45 | this.props = props; 46 | this.camera = camera; 47 | this.scene = scene; 48 | this._cache = undefined; 49 | } 50 | 51 | ThreeRendererNode.prototype.render = function() { 52 | if(!this._cache) { 53 | this._cache = new THREE.WebGLRenderer(this.props); 54 | for(k in this.props) { 55 | this._cache[k] = this.props[k]; 56 | } 57 | this.camera.render(); 58 | this.scene.render(); 59 | } 60 | 61 | return this._cache; 62 | }; 63 | 64 | ThreeRendererNode.prototype.reconcile = function(other) { 65 | if(this === other) 66 | return; 67 | 68 | if(this.type !== other.type) 69 | throw "Cannot reconcile " + this.type + " and " + other.type; 70 | 71 | if(this.props !== other.props) 72 | mergeProps(this._cache, this.props, other.props); 73 | 74 | this.camera.reconcile(other.camera); 75 | this.scene.reconcile(other.scene); 76 | 77 | // propagate the cache 78 | other._cache = this._cache; 79 | }; 80 | 81 | 82 | var ThreeNode = function(type, ctor) { 83 | var nodeCtor = function(props, children) { 84 | this.type = "three-" + type; 85 | this.props = props; 86 | this.children = children; 87 | this._cache = undefined; 88 | } 89 | 90 | nodeCtor.prototype.render = function() { 91 | if(!this._cache) { 92 | this._cache = Object.create(ctor.prototype); 93 | ctor.call(this._cache); // TODO ctor args? 94 | 95 | // if(parent) 96 | // parent.add(this._cache); 97 | 98 | for(k in this.props) { 99 | switch(k) { 100 | case "rotation": 101 | this._cache.rotation.set(this.props[k].x, this.props[k].y, this.props[k].z); 102 | break; 103 | case "position": 104 | this._cache.position.set(this.props[k].x, this.props[k].y, this.props[k].z); 105 | break; 106 | default: 107 | this._cache[k] = this.props[k]; 108 | } 109 | } 110 | 111 | if(this.children) 112 | for (var i = 0; i < this.children.length; i++) { 113 | this._cache.add(this.children[i].render()); 114 | }; 115 | } 116 | 117 | return this._cache; 118 | }; 119 | 120 | nodeCtor.prototype.reconcile = function(other) { 121 | if(this === other) 122 | return; 123 | 124 | if(this.type !== other.type) 125 | throw "Cannot reconcile " + this.type + " and " + other.type; 126 | 127 | if(this.props !== other.props) 128 | mergeProps(this._cache, this.props, other.props) 129 | 130 | if(this.children && other.children) 131 | this.reconcileChildren(other); 132 | 133 | // propagate the cache 134 | other._cache = this._cache; 135 | }; 136 | 137 | nodeCtor.prototype.reconcileChildren = function(other) { 138 | var thisChildren = this.children; 139 | var otherChildren = other.children; 140 | var minLength = Math.min(thisChildren.length, otherChildren.length); 141 | 142 | for (var i = 0; i < minLength; i++) { 143 | thisChildren[i].reconcile(otherChildren[i]); 144 | }; 145 | 146 | // new elements 147 | if(otherChildren.length > thisChildren.length) { 148 | for (var i = minLength; i < otherChildren.length; i++) { 149 | this._cache.add(otherChildren[i].render()); 150 | }; 151 | 152 | // fewer elements 153 | } else if(thisChildren.length > otherChildren.length) { 154 | for (var i = minLength; i < thisChildren.length; i++) { 155 | this._cache.remove(thisChildren[i]._cache); 156 | }; 157 | } 158 | }; 159 | 160 | return nodeCtor; 161 | } 162 | 163 | var ThreeOrthographicCameraNode = ThreeNode("orthographic-camera", THREE.OrthographicCamera); 164 | var ThreePerspectiveCameraNode = ThreeNode("perspective-camera", THREE.PerspectiveCamera); 165 | var ThreeSceneNode = ThreeNode("scene", THREE.Scene); 166 | var ThreeObject3DNode = ThreeNode("node", THREE.Object3D); 167 | var ThreeMeshNode = ThreeNode("node", THREE.Mesh); 168 | var ThreeLineNode = ThreeNode("node", THREE.Line); 169 | 170 | //////// API //////// 171 | 172 | goog.provide('zajal.three'); 173 | 174 | zajal.three.rednerer = function(props, camera, scene) { return new ThreeRendererNode(props, camera, scene); } 175 | zajal.three.perspectiveCamera = function(props) { return new ThreePerspectiveCameraNode(props); } 176 | zajal.three.scene = function(props, children) { return new ThreeSceneNode(props, children); } 177 | zajal.three.mesh = function(props, children) { return new ThreeMeshNode(props, children); } 178 | zajal.three.line = function(props, children) { return new ThreeLineNode(props, children); } 179 | 180 | //////// TEST //////// 181 | 182 | 183 | // var boxGeo = new THREE.BoxGeometry(1, 1, 1) 184 | // var meshMat = new THREE.MeshBasicMaterial({wireframe:true}) 185 | 186 | 187 | // var box = function(t, children) { 188 | // if(!children) 189 | // return new ThreeMeshNode({ 190 | // rotation: {x: 0, y: 0, z:+t/1000}, 191 | // position: new THREE.Vector3(3, 0, 0), 192 | // geometry:boxGeo, 193 | // material:meshMat}) 194 | // else 195 | // return new ThreeMeshNode({ 196 | // rotation: {x: 0, y: 0, z:+t * 0.001}, 197 | // position: new THREE.Vector3(1, 0, 0), 198 | // geometry:boxGeo, 199 | // material:meshMat}, 200 | // children) 201 | // } 202 | 203 | // var boxes = function(t, i) { 204 | // if(i > 0) 205 | // return box(t * i * 0.001, [boxes(t, i - 1)]); 206 | // else 207 | // return box(t) 208 | // } 209 | 210 | // var threeTest = function(t) { 211 | // return new ThreeRendererNode( 212 | // {}, 213 | // new ThreePerspectiveCameraNode({fov:60, aspect: 1, near:1, far:1000}), 214 | // new ThreeSceneNode({}, 215 | // [boxes(0, 5), 216 | // boxes(t * 2, 5), 217 | // boxes(t * 4, 5), 218 | // boxes(t * 6, 5), 219 | // boxes(t * 8, 5), 220 | // boxes(t * 10, 5), 221 | // boxes(t * 20, 5), 222 | // boxes(t * 30, 5), 223 | // boxes(t * 40, 5), 224 | // boxes(t * 50, 5), 225 | // boxes(t * 60, 5), 226 | // boxes(t * 70, 5), 227 | // boxes(t * 80, 5), 228 | // boxes(t * 90, 5), 229 | // boxes(t * 100, 5), 230 | // boxes(t * 110, 5), 231 | // boxes(t * 120, 5), 232 | // boxes(t * 130, 5), 233 | // boxes(t * 140, 5), 234 | // boxes(t * 150, 5), 235 | // boxes(t * 2, 5), 236 | // boxes(t * 4, 5), 237 | // boxes(t * 6, 5), 238 | // boxes(t * 8, 5), 239 | // boxes(t * 10, 5), 240 | // boxes(t * 20, 5), 241 | // boxes(t * 30, 5), 242 | // boxes(t * 40, 5), 243 | // boxes(t * 50, 5), 244 | // boxes(t * 60, 5), 245 | // boxes(t * 70, 5), 246 | // boxes(t * 80, 5), 247 | // boxes(t * 90, 5), 248 | // boxes(t * 100, 5), 249 | // boxes(t * 110, 5), 250 | // boxes(t * 120, 5), 251 | // boxes(t * 130, 5), 252 | // boxes(t * 140, 5), 253 | // boxes(t * 150, 5), 254 | // boxes(t * 2, 5), 255 | // boxes(t * 4, 5), 256 | // boxes(t * 6, 5), 257 | // boxes(t * 8, 5), 258 | // boxes(t * 10, 5), 259 | // boxes(t * 20, 5), 260 | // boxes(t * 30, 5), 261 | // boxes(t * 40, 5), 262 | // boxes(t * 50, 5), 263 | // boxes(t * 60, 5), 264 | // boxes(t * 70, 5), 265 | // boxes(t * 80, 5), 266 | // boxes(t * 90, 5), 267 | // boxes(t * 100, 5), 268 | // boxes(t * 110, 5), 269 | // boxes(t * 120, 5), 270 | // boxes(t * 130, 5), 271 | // boxes(t * 140, 5), 272 | // boxes(t * 150, 5), 273 | // boxes(t * 2, 5), 274 | // boxes(t * 4, 5), 275 | // boxes(t * 6, 5), 276 | // boxes(t * 8, 5), 277 | // boxes(t * 10, 5), 278 | // boxes(t * 20, 5), 279 | // boxes(t * 30, 5), 280 | // boxes(t * 40, 5), 281 | // boxes(t * 50, 5), 282 | // boxes(t * 60, 5), 283 | // boxes(t * 70, 5), 284 | // boxes(t * 80, 5), 285 | // boxes(t * 90, 5), 286 | // boxes(t * 100, 5), 287 | // boxes(t * 110, 5), 288 | // boxes(t * 120, 5), 289 | // boxes(t * 130, 5), 290 | // boxes(t * 140, 5), 291 | // boxes(t * 150, 5), 292 | // ])); 293 | // } -------------------------------------------------------------------------------- /scratch/virtual-three-clj.js: -------------------------------------------------------------------------------- 1 | function cachingFn(f) { 2 | var _gensym = cljs.core.gensym().toString(); 3 | return function(a) { 4 | if(!a[_gensym]) 5 | a[_gensym] = f(a); 6 | return a[_gensym]; 7 | } 8 | } 9 | 10 | //////// THREE //////// 11 | 12 | function mergeProps(obj, fromProps, toProps) { 13 | // check for changed/removed props 14 | for (var fromKey in fromProps) { 15 | // key missing, remove attribute it 16 | if (toProps[fromKey] === undefined) { 17 | delete obj[fromKey]; 18 | 19 | } else { 20 | var fromValue = fromProps[fromKey]; 21 | var toValue = toProps[fromKey]; 22 | 23 | // same value, move on 24 | if(fromValue === toValue) 25 | continue; 26 | 27 | // TODO deep prop diff? 28 | // changed value, update attribute 29 | 30 | switch(fromKey) { 31 | case "rotation": 32 | obj.rotation.set(toValue.x, toValue.y, toValue.z); 33 | break; 34 | case "position": 35 | obj.position.set(toValue.x, toValue.y, toValue.z); 36 | break; 37 | default: 38 | obj[fromKey] = toValue; 39 | } 40 | } 41 | } 42 | 43 | // check for new props 44 | for (var toKey in toProps) { 45 | if (!(toKey in fromProps)) { 46 | obj[toKey] = toProps[toValue]; 47 | } 48 | } 49 | } 50 | 51 | // TODO multiple cameras 52 | function ThreeRendererNode(props, camera, scene) { 53 | this.type = "three-renderer" 54 | this.props = props; 55 | this.camera = camera; 56 | this.scene = scene; 57 | this._cache = undefined; 58 | } 59 | 60 | ThreeRendererNode.prototype.render = function() { 61 | if(!this._cache) { 62 | this._cache = new THREE.WebGLRenderer(this.props); 63 | for(k in this.props) { 64 | this._cache[k] = this.props[k]; 65 | } 66 | this.camera.render(); 67 | this.scene.render(); 68 | } 69 | 70 | return this._cache; 71 | }; 72 | 73 | ThreeRendererNode.prototype.reconcile = function(other) { 74 | if(this === other) 75 | return; 76 | 77 | if(this.type !== other.type) 78 | throw "Cannot reconcile " + this.type + " and " + other.type; 79 | 80 | if(this.props !== other.props) 81 | mergeProps(this._cache, this.props, other.props); 82 | 83 | this.camera.reconcile(other.camera); 84 | this.scene.reconcile(other.scene); 85 | 86 | // propagate the cache 87 | other._cache = this._cache; 88 | }; 89 | 90 | 91 | var ThreeNode = function(type, ctor) { 92 | var nodeCtor = function(props, children) { 93 | this.type = "three-" + type; 94 | this.props = props; 95 | this.children = children; 96 | this._cache = undefined; 97 | } 98 | 99 | nodeCtor.prototype.render = function() { 100 | if(!this._cache) { 101 | this._cache = Object.create(ctor.prototype); 102 | ctor.call(this._cache); // TODO ctor args? 103 | 104 | // if(parent) 105 | // parent.add(this._cache); 106 | 107 | for(k in this.props) { 108 | switch(k) { 109 | case "rotation": 110 | this._cache.rotation.set(this.props[k].x, this.props[k].y, this.props[k].z); 111 | break; 112 | case "position": 113 | this._cache.position.set(this.props[k].x, this.props[k].y, this.props[k].z); 114 | break; 115 | default: 116 | this._cache[k] = this.props[k]; 117 | } 118 | } 119 | 120 | if(this.children) 121 | for (var i = 0; i < this.children.length; i++) { 122 | this._cache.add(this.children[i].render()); 123 | }; 124 | } 125 | 126 | return this._cache; 127 | }; 128 | 129 | nodeCtor.prototype.reconcile = function(other) { 130 | if(this === other) 131 | return; 132 | 133 | if(this.type !== other.type) 134 | throw "Cannot reconcile " + this.type + " and " + other.type; 135 | 136 | if(this.props !== other.props) 137 | mergeProps(this._cache, this.props, other.props) 138 | 139 | if(this.children && other.children) 140 | this.reconcileChildren(other); 141 | 142 | // propagate the cache 143 | other._cache = this._cache; 144 | }; 145 | 146 | nodeCtor.prototype.reconcileChildren = function(other) { 147 | var thisChildren = this.children; 148 | var otherChildren = other.children; 149 | var minLength = Math.min(thisChildren.length, otherChildren.length); 150 | 151 | for (var i = 0; i < minLength; i++) { 152 | thisChildren[i].reconcile(otherChildren[i]); 153 | }; 154 | 155 | // new elements 156 | if(otherChildren.length > thisChildren.length) { 157 | for (var i = minLength; i < otherChildren.length; i++) { 158 | this._cache.add(otherChildren[i].render()); 159 | }; 160 | 161 | // fewer elements 162 | } else if(thisChildren.length > otherChildren.length) { 163 | for (var i = minLength; i < thisChildren.length; i++) { 164 | this._cache.remove(thisChildren[i]._cache); 165 | }; 166 | } 167 | }; 168 | 169 | return nodeCtor; 170 | } 171 | 172 | var ThreeOrthographicCameraNode = ThreeNode("orthographic-camera", THREE.OrthographicCamera); 173 | var ThreePerspectiveCameraNode = ThreeNode("perspective-camera", THREE.PerspectiveCamera); 174 | var ThreeSceneNode = ThreeNode("scene", THREE.Scene); 175 | var ThreeObject3DNode = ThreeNode("node", THREE.Object3D); 176 | var ThreeMeshNode = ThreeNode("node", THREE.Mesh); 177 | var ThreeLineNode = ThreeNode("node", THREE.Line); 178 | 179 | //////// API //////// 180 | 181 | goog.provide('zajal.three'); 182 | 183 | zajal.three.rednerer = function(props, camera, scene) { return new ThreeRendererNode(props, camera, scene); } 184 | zajal.three.perspectiveCamera = function(props) { return new ThreePerspectiveCameraNode(props); } 185 | zajal.three.scene = function(props, children) { return new ThreeSceneNode(props, children); } 186 | zajal.three.mesh = function(props, children) { return new ThreeMeshNode(props, children); } 187 | zajal.three.line = function(props, children) { return new ThreeLineNode(props, children); } 188 | 189 | //////// TEST //////// 190 | 191 | 192 | // var boxGeo = new THREE.BoxGeometry(1, 1, 1) 193 | // var meshMat = new THREE.MeshBasicMaterial({wireframe:true}) 194 | 195 | 196 | // var box = function(t, children) { 197 | // if(!children) 198 | // return new ThreeMeshNode({ 199 | // rotation: {x: 0, y: 0, z:+t/1000}, 200 | // position: new THREE.Vector3(3, 0, 0), 201 | // geometry:boxGeo, 202 | // material:meshMat}) 203 | // else 204 | // return new ThreeMeshNode({ 205 | // rotation: {x: 0, y: 0, z:+t * 0.001}, 206 | // position: new THREE.Vector3(1, 0, 0), 207 | // geometry:boxGeo, 208 | // material:meshMat}, 209 | // children) 210 | // } 211 | 212 | // var boxes = function(t, i) { 213 | // if(i > 0) 214 | // return box(t * i * 0.001, [boxes(t, i - 1)]); 215 | // else 216 | // return box(t) 217 | // } 218 | 219 | // var threeTest = function(t) { 220 | // return new ThreeRendererNode( 221 | // {}, 222 | // new ThreePerspectiveCameraNode({fov:60, aspect: 1, near:1, far:1000}), 223 | // new ThreeSceneNode({}, 224 | // [boxes(0, 5), 225 | // boxes(t * 2, 5), 226 | // boxes(t * 4, 5), 227 | // boxes(t * 6, 5), 228 | // boxes(t * 8, 5), 229 | // boxes(t * 10, 5), 230 | // boxes(t * 20, 5), 231 | // boxes(t * 30, 5), 232 | // boxes(t * 40, 5), 233 | // boxes(t * 50, 5), 234 | // boxes(t * 60, 5), 235 | // boxes(t * 70, 5), 236 | // boxes(t * 80, 5), 237 | // boxes(t * 90, 5), 238 | // boxes(t * 100, 5), 239 | // boxes(t * 110, 5), 240 | // boxes(t * 120, 5), 241 | // boxes(t * 130, 5), 242 | // boxes(t * 140, 5), 243 | // boxes(t * 150, 5), 244 | // boxes(t * 2, 5), 245 | // boxes(t * 4, 5), 246 | // boxes(t * 6, 5), 247 | // boxes(t * 8, 5), 248 | // boxes(t * 10, 5), 249 | // boxes(t * 20, 5), 250 | // boxes(t * 30, 5), 251 | // boxes(t * 40, 5), 252 | // boxes(t * 50, 5), 253 | // boxes(t * 60, 5), 254 | // boxes(t * 70, 5), 255 | // boxes(t * 80, 5), 256 | // boxes(t * 90, 5), 257 | // boxes(t * 100, 5), 258 | // boxes(t * 110, 5), 259 | // boxes(t * 120, 5), 260 | // boxes(t * 130, 5), 261 | // boxes(t * 140, 5), 262 | // boxes(t * 150, 5), 263 | // boxes(t * 2, 5), 264 | // boxes(t * 4, 5), 265 | // boxes(t * 6, 5), 266 | // boxes(t * 8, 5), 267 | // boxes(t * 10, 5), 268 | // boxes(t * 20, 5), 269 | // boxes(t * 30, 5), 270 | // boxes(t * 40, 5), 271 | // boxes(t * 50, 5), 272 | // boxes(t * 60, 5), 273 | // boxes(t * 70, 5), 274 | // boxes(t * 80, 5), 275 | // boxes(t * 90, 5), 276 | // boxes(t * 100, 5), 277 | // boxes(t * 110, 5), 278 | // boxes(t * 120, 5), 279 | // boxes(t * 130, 5), 280 | // boxes(t * 140, 5), 281 | // boxes(t * 150, 5), 282 | // boxes(t * 2, 5), 283 | // boxes(t * 4, 5), 284 | // boxes(t * 6, 5), 285 | // boxes(t * 8, 5), 286 | // boxes(t * 10, 5), 287 | // boxes(t * 20, 5), 288 | // boxes(t * 30, 5), 289 | // boxes(t * 40, 5), 290 | // boxes(t * 50, 5), 291 | // boxes(t * 60, 5), 292 | // boxes(t * 70, 5), 293 | // boxes(t * 80, 5), 294 | // boxes(t * 90, 5), 295 | // boxes(t * 100, 5), 296 | // boxes(t * 110, 5), 297 | // boxes(t * 120, 5), 298 | // boxes(t * 130, 5), 299 | // boxes(t * 140, 5), 300 | // boxes(t * 150, 5), 301 | // ])); 302 | // } -------------------------------------------------------------------------------- /js/loaders/MTLLoader.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Loads a Wavefront .mtl file specifying materials 3 | * 4 | * @author angelxuanchang 5 | */ 6 | 7 | THREE.MTLLoader = function( manager ) { 8 | 9 | this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager; 10 | 11 | }; 12 | 13 | THREE.MTLLoader.prototype = { 14 | 15 | constructor: THREE.MTLLoader, 16 | 17 | load: function ( url, onLoad, onProgress, onError ) { 18 | 19 | var scope = this; 20 | 21 | var loader = new THREE.XHRLoader( this.manager ); 22 | loader.setPath( this.path ); 23 | loader.load( url, function ( text ) { 24 | 25 | onLoad( scope.parse( text ) ); 26 | 27 | }, onProgress, onError ); 28 | 29 | }, 30 | 31 | setPath: function ( value ) { 32 | 33 | this.path = value; 34 | 35 | }, 36 | 37 | setBaseUrl: function( value ) { 38 | 39 | // TODO: Merge with setPath()? Or rename to setTexturePath? 40 | 41 | this.baseUrl = value; 42 | 43 | }, 44 | 45 | setCrossOrigin: function ( value ) { 46 | 47 | this.crossOrigin = value; 48 | 49 | }, 50 | 51 | setMaterialOptions: function ( value ) { 52 | 53 | this.materialOptions = value; 54 | 55 | }, 56 | 57 | /** 58 | * Parses loaded MTL file 59 | * @param text - Content of MTL file 60 | * @return {THREE.MTLLoader.MaterialCreator} 61 | */ 62 | parse: function ( text ) { 63 | 64 | var lines = text.split( "\n" ); 65 | var info = {}; 66 | var delimiter_pattern = /\s+/; 67 | var materialsInfo = {}; 68 | 69 | for ( var i = 0; i < lines.length; i ++ ) { 70 | 71 | var line = lines[ i ]; 72 | line = line.trim(); 73 | 74 | if ( line.length === 0 || line.charAt( 0 ) === '#' ) { 75 | 76 | // Blank line or comment ignore 77 | continue; 78 | 79 | } 80 | 81 | var pos = line.indexOf( ' ' ); 82 | 83 | var key = ( pos >= 0 ) ? line.substring( 0, pos ) : line; 84 | key = key.toLowerCase(); 85 | 86 | var value = ( pos >= 0 ) ? line.substring( pos + 1 ) : ""; 87 | value = value.trim(); 88 | 89 | if ( key === "newmtl" ) { 90 | 91 | // New material 92 | 93 | info = { name: value }; 94 | materialsInfo[ value ] = info; 95 | 96 | } else if ( info ) { 97 | 98 | if ( key === "ka" || key === "kd" || key === "ks" ) { 99 | 100 | var ss = value.split( delimiter_pattern, 3 ); 101 | info[ key ] = [ parseFloat( ss[ 0 ] ), parseFloat( ss[ 1 ] ), parseFloat( ss[ 2 ] ) ]; 102 | 103 | } else { 104 | 105 | info[ key ] = value; 106 | 107 | } 108 | 109 | } 110 | 111 | } 112 | 113 | var materialCreator = new THREE.MTLLoader.MaterialCreator( this.baseUrl, this.materialOptions ); 114 | materialCreator.setCrossOrigin( this.crossOrigin ); 115 | materialCreator.setManager( this.manager ); 116 | materialCreator.setMaterials( materialsInfo ); 117 | return materialCreator; 118 | 119 | } 120 | 121 | }; 122 | 123 | /** 124 | * Create a new THREE-MTLLoader.MaterialCreator 125 | * @param baseUrl - Url relative to which textures are loaded 126 | * @param options - Set of options on how to construct the materials 127 | * side: Which side to apply the material 128 | * THREE.FrontSide (default), THREE.BackSide, THREE.DoubleSide 129 | * wrap: What type of wrapping to apply for textures 130 | * THREE.RepeatWrapping (default), THREE.ClampToEdgeWrapping, THREE.MirroredRepeatWrapping 131 | * normalizeRGB: RGBs need to be normalized to 0-1 from 0-255 132 | * Default: false, assumed to be already normalized 133 | * ignoreZeroRGBs: Ignore values of RGBs (Ka,Kd,Ks) that are all 0's 134 | * Default: false 135 | * @constructor 136 | */ 137 | 138 | THREE.MTLLoader.MaterialCreator = function( baseUrl, options ) { 139 | 140 | this.baseUrl = baseUrl; 141 | this.options = options; 142 | this.materialsInfo = {}; 143 | this.materials = {}; 144 | this.materialsArray = []; 145 | this.nameLookup = {}; 146 | 147 | this.side = ( this.options && this.options.side ) ? this.options.side : THREE.FrontSide; 148 | this.wrap = ( this.options && this.options.wrap ) ? this.options.wrap : THREE.RepeatWrapping; 149 | 150 | }; 151 | 152 | THREE.MTLLoader.MaterialCreator.prototype = { 153 | 154 | constructor: THREE.MTLLoader.MaterialCreator, 155 | 156 | setCrossOrigin: function ( value ) { 157 | 158 | this.crossOrigin = value; 159 | 160 | }, 161 | 162 | setManager: function ( value ) { 163 | 164 | this.manager = value; 165 | 166 | }, 167 | 168 | setMaterials: function( materialsInfo ) { 169 | 170 | this.materialsInfo = this.convert( materialsInfo ); 171 | this.materials = {}; 172 | this.materialsArray = []; 173 | this.nameLookup = {}; 174 | 175 | }, 176 | 177 | convert: function( materialsInfo ) { 178 | 179 | if ( ! this.options ) return materialsInfo; 180 | 181 | var converted = {}; 182 | 183 | for ( var mn in materialsInfo ) { 184 | 185 | // Convert materials info into normalized form based on options 186 | 187 | var mat = materialsInfo[ mn ]; 188 | 189 | var covmat = {}; 190 | 191 | converted[ mn ] = covmat; 192 | 193 | for ( var prop in mat ) { 194 | 195 | var save = true; 196 | var value = mat[ prop ]; 197 | var lprop = prop.toLowerCase(); 198 | 199 | switch ( lprop ) { 200 | 201 | case 'kd': 202 | case 'ka': 203 | case 'ks': 204 | 205 | // Diffuse color (color under white light) using RGB values 206 | 207 | if ( this.options && this.options.normalizeRGB ) { 208 | 209 | value = [ value[ 0 ] / 255, value[ 1 ] / 255, value[ 2 ] / 255 ]; 210 | 211 | } 212 | 213 | if ( this.options && this.options.ignoreZeroRGBs ) { 214 | 215 | if ( value[ 0 ] === 0 && value[ 1 ] === 0 && value[ 1 ] === 0 ) { 216 | 217 | // ignore 218 | 219 | save = false; 220 | 221 | } 222 | 223 | } 224 | 225 | break; 226 | 227 | default: 228 | 229 | break; 230 | } 231 | 232 | if ( save ) { 233 | 234 | covmat[ lprop ] = value; 235 | 236 | } 237 | 238 | } 239 | 240 | } 241 | 242 | return converted; 243 | 244 | }, 245 | 246 | preload: function () { 247 | 248 | for ( var mn in this.materialsInfo ) { 249 | 250 | this.create( mn ); 251 | 252 | } 253 | 254 | }, 255 | 256 | getIndex: function( materialName ) { 257 | 258 | return this.nameLookup[ materialName ]; 259 | 260 | }, 261 | 262 | getAsArray: function() { 263 | 264 | var index = 0; 265 | 266 | for ( var mn in this.materialsInfo ) { 267 | 268 | this.materialsArray[ index ] = this.create( mn ); 269 | this.nameLookup[ mn ] = index; 270 | index ++; 271 | 272 | } 273 | 274 | return this.materialsArray; 275 | 276 | }, 277 | 278 | create: function ( materialName ) { 279 | 280 | if ( this.materials[ materialName ] === undefined ) { 281 | 282 | this.createMaterial_( materialName ); 283 | 284 | } 285 | 286 | return this.materials[ materialName ]; 287 | 288 | }, 289 | 290 | createMaterial_: function ( materialName ) { 291 | 292 | // Create material 293 | 294 | var mat = this.materialsInfo[ materialName ]; 295 | var params = { 296 | 297 | name: materialName, 298 | side: this.side 299 | 300 | }; 301 | 302 | for ( var prop in mat ) { 303 | 304 | var value = mat[ prop ]; 305 | 306 | if ( value === '' ) { 307 | continue; 308 | } 309 | 310 | switch ( prop.toLowerCase() ) { 311 | 312 | // Ns is material specular exponent 313 | 314 | case 'kd': 315 | 316 | // Diffuse color (color under white light) using RGB values 317 | 318 | params[ 'color' ] = new THREE.Color().fromArray( value ); 319 | 320 | break; 321 | 322 | case 'ks': 323 | 324 | // Specular color (color when light is reflected from shiny surface) using RGB values 325 | params[ 'specular' ] = new THREE.Color().fromArray( value ); 326 | 327 | break; 328 | 329 | case 'map_kd': 330 | 331 | // Diffuse texture map 332 | 333 | params[ 'map' ] = this.loadTexture( this.baseUrl + value ); 334 | params[ 'map' ].wrapS = this.wrap; 335 | params[ 'map' ].wrapT = this.wrap; 336 | 337 | break; 338 | 339 | case 'ns': 340 | 341 | // The specular exponent (defines the focus of the specular highlight) 342 | // A high exponent results in a tight, concentrated highlight. Ns values normally range from 0 to 1000. 343 | 344 | params[ 'shininess' ] = parseFloat( value ); 345 | 346 | break; 347 | 348 | case 'd': 349 | 350 | if ( value < 1 ) { 351 | 352 | params[ 'opacity' ] = value; 353 | params[ 'transparent' ] = true; 354 | 355 | } 356 | 357 | break; 358 | 359 | case 'Tr': 360 | 361 | if ( value > 0 ) { 362 | 363 | params[ 'opacity' ] = 1 - value; 364 | params[ 'transparent' ] = true; 365 | 366 | } 367 | 368 | break; 369 | 370 | case 'map_bump': 371 | case 'bump': 372 | 373 | // Bump texture map 374 | 375 | if ( params[ 'bumpMap' ] ) break; // Avoid loading twice. 376 | 377 | params[ 'bumpMap' ] = this.loadTexture( this.baseUrl + value ); 378 | params[ 'bumpMap' ].wrapS = this.wrap; 379 | params[ 'bumpMap' ].wrapT = this.wrap; 380 | 381 | break; 382 | 383 | default: 384 | break; 385 | 386 | } 387 | 388 | } 389 | 390 | this.materials[ materialName ] = new THREE.MeshPhongMaterial( params ); 391 | return this.materials[ materialName ]; 392 | 393 | }, 394 | 395 | 396 | loadTexture: function ( url, mapping, onLoad, onProgress, onError ) { 397 | 398 | var texture; 399 | var loader = THREE.Loader.Handlers.get( url ); 400 | var manager = ( this.manager !== undefined ) ? this.manager : THREE.DefaultLoadingManager; 401 | 402 | if ( loader === null ) { 403 | 404 | loader = new THREE.TextureLoader( manager ); 405 | 406 | } 407 | 408 | if ( loader.setCrossOrigin ) loader.setCrossOrigin( this.crossOrigin ); 409 | texture = loader.load( url, onLoad, onProgress, onError ); 410 | 411 | if ( mapping !== undefined ) texture.mapping = mapping; 412 | 413 | return texture; 414 | 415 | } 416 | 417 | }; 418 | 419 | THREE.EventDispatcher.prototype.apply( THREE.MTLLoader.prototype ); 420 | -------------------------------------------------------------------------------- /js/loaders/OBJLoader.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author mrdoob / http://mrdoob.com/ 3 | */ 4 | 5 | THREE.OBJLoader = function ( manager ) { 6 | 7 | this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager; 8 | 9 | this.materials = null; 10 | 11 | }; 12 | 13 | THREE.OBJLoader.prototype = { 14 | 15 | constructor: THREE.OBJLoader, 16 | 17 | load: function ( url, onLoad, onProgress, onError ) { 18 | 19 | var scope = this; 20 | 21 | var loader = new THREE.XHRLoader( scope.manager ); 22 | loader.setPath( this.path ); 23 | loader.load( url, function ( text ) { 24 | 25 | onLoad( scope.parse( text ) ); 26 | 27 | }, onProgress, onError ); 28 | 29 | }, 30 | 31 | setPath: function ( value ) { 32 | 33 | this.path = value; 34 | 35 | }, 36 | 37 | setMaterials: function ( materials ) { 38 | 39 | this.materials = materials; 40 | 41 | }, 42 | 43 | parse: function ( text ) { 44 | 45 | console.time( 'OBJLoader' ); 46 | 47 | var objects = []; 48 | var object; 49 | var foundObjects = false; 50 | var vertices = []; 51 | var normals = []; 52 | var uvs = []; 53 | 54 | function addObject( name ) { 55 | 56 | var geometry = { 57 | vertices: [], 58 | normals: [], 59 | uvs: [] 60 | }; 61 | 62 | var material = { 63 | name: '', 64 | smooth: true 65 | }; 66 | 67 | object = { 68 | name: name, 69 | geometry: geometry, 70 | material: material 71 | }; 72 | 73 | objects.push( object ); 74 | 75 | } 76 | 77 | function parseVertexIndex( value ) { 78 | 79 | var index = parseInt( value ); 80 | 81 | return ( index >= 0 ? index - 1 : index + vertices.length / 3 ) * 3; 82 | 83 | } 84 | 85 | function parseNormalIndex( value ) { 86 | 87 | var index = parseInt( value ); 88 | 89 | return ( index >= 0 ? index - 1 : index + normals.length / 3 ) * 3; 90 | 91 | } 92 | 93 | function parseUVIndex( value ) { 94 | 95 | var index = parseInt( value ); 96 | 97 | return ( index >= 0 ? index - 1 : index + uvs.length / 2 ) * 2; 98 | 99 | } 100 | 101 | function addVertex( a, b, c ) { 102 | 103 | object.geometry.vertices.push( 104 | vertices[ a ], vertices[ a + 1 ], vertices[ a + 2 ], 105 | vertices[ b ], vertices[ b + 1 ], vertices[ b + 2 ], 106 | vertices[ c ], vertices[ c + 1 ], vertices[ c + 2 ] 107 | ); 108 | 109 | } 110 | 111 | function addNormal( a, b, c ) { 112 | 113 | object.geometry.normals.push( 114 | normals[ a ], normals[ a + 1 ], normals[ a + 2 ], 115 | normals[ b ], normals[ b + 1 ], normals[ b + 2 ], 116 | normals[ c ], normals[ c + 1 ], normals[ c + 2 ] 117 | ); 118 | 119 | } 120 | 121 | function addUV( a, b, c ) { 122 | 123 | object.geometry.uvs.push( 124 | uvs[ a ], uvs[ a + 1 ], 125 | uvs[ b ], uvs[ b + 1 ], 126 | uvs[ c ], uvs[ c + 1 ] 127 | ); 128 | 129 | } 130 | 131 | function addFace( a, b, c, d, ua, ub, uc, ud, na, nb, nc, nd ) { 132 | 133 | var ia = parseVertexIndex( a ); 134 | var ib = parseVertexIndex( b ); 135 | var ic = parseVertexIndex( c ); 136 | var id; 137 | 138 | if ( d === undefined ) { 139 | 140 | addVertex( ia, ib, ic ); 141 | 142 | } else { 143 | 144 | id = parseVertexIndex( d ); 145 | 146 | addVertex( ia, ib, id ); 147 | addVertex( ib, ic, id ); 148 | 149 | } 150 | 151 | if ( ua !== undefined ) { 152 | 153 | ia = parseUVIndex( ua ); 154 | ib = parseUVIndex( ub ); 155 | ic = parseUVIndex( uc ); 156 | 157 | if ( d === undefined ) { 158 | 159 | addUV( ia, ib, ic ); 160 | 161 | } else { 162 | 163 | id = parseUVIndex( ud ); 164 | 165 | addUV( ia, ib, id ); 166 | addUV( ib, ic, id ); 167 | 168 | } 169 | 170 | } 171 | 172 | if ( na !== undefined ) { 173 | 174 | ia = parseNormalIndex( na ); 175 | ib = parseNormalIndex( nb ); 176 | ic = parseNormalIndex( nc ); 177 | 178 | if ( d === undefined ) { 179 | 180 | addNormal( ia, ib, ic ); 181 | 182 | } else { 183 | 184 | id = parseNormalIndex( nd ); 185 | 186 | addNormal( ia, ib, id ); 187 | addNormal( ib, ic, id ); 188 | 189 | } 190 | 191 | } 192 | 193 | } 194 | 195 | addObject( '' ); 196 | 197 | // v float float float 198 | var vertex_pattern = /^v\s+([\d|\.|\+|\-|e|E]+)\s+([\d|\.|\+|\-|e|E]+)\s+([\d|\.|\+|\-|e|E]+)/; 199 | 200 | // vn float float float 201 | var normal_pattern = /^vn\s+([\d|\.|\+|\-|e|E]+)\s+([\d|\.|\+|\-|e|E]+)\s+([\d|\.|\+|\-|e|E]+)/; 202 | 203 | // vt float float 204 | var uv_pattern = /^vt\s+([\d|\.|\+|\-|e|E]+)\s+([\d|\.|\+|\-|e|E]+)/; 205 | 206 | // f vertex vertex vertex ... 207 | var face_pattern1 = /^f\s+(-?\d+)\s+(-?\d+)\s+(-?\d+)(?:\s+(-?\d+))?/; 208 | 209 | // f vertex/uv vertex/uv vertex/uv ... 210 | var face_pattern2 = /^f\s+((-?\d+)\/(-?\d+))\s+((-?\d+)\/(-?\d+))\s+((-?\d+)\/(-?\d+))(?:\s+((-?\d+)\/(-?\d+)))?/; 211 | 212 | // f vertex/uv/normal vertex/uv/normal vertex/uv/normal ... 213 | var face_pattern3 = /^f\s+((-?\d+)\/(-?\d+)\/(-?\d+))\s+((-?\d+)\/(-?\d+)\/(-?\d+))\s+((-?\d+)\/(-?\d+)\/(-?\d+))(?:\s+((-?\d+)\/(-?\d+)\/(-?\d+)))?/; 214 | 215 | // f vertex//normal vertex//normal vertex//normal ... 216 | var face_pattern4 = /^f\s+((-?\d+)\/\/(-?\d+))\s+((-?\d+)\/\/(-?\d+))\s+((-?\d+)\/\/(-?\d+))(?:\s+((-?\d+)\/\/(-?\d+)))?/; 217 | 218 | var object_pattern = /^[og]\s+(.+)/; 219 | 220 | var smoothing_pattern = /^s\s+([01]|on|off)/; 221 | 222 | // 223 | 224 | var lines = text.split( '\n' ); 225 | 226 | for ( var i = 0; i < lines.length; i ++ ) { 227 | 228 | var line = lines[ i ]; 229 | line = line.trim(); 230 | 231 | var result; 232 | 233 | if ( line.length === 0 || line.charAt( 0 ) === '#' ) { 234 | 235 | continue; 236 | 237 | } else if ( ( result = vertex_pattern.exec( line ) ) !== null ) { 238 | 239 | // ["v 1.0 2.0 3.0", "1.0", "2.0", "3.0"] 240 | 241 | vertices.push( 242 | parseFloat( result[ 1 ] ), 243 | parseFloat( result[ 2 ] ), 244 | parseFloat( result[ 3 ] ) 245 | ); 246 | 247 | } else if ( ( result = normal_pattern.exec( line ) ) !== null ) { 248 | 249 | // ["vn 1.0 2.0 3.0", "1.0", "2.0", "3.0"] 250 | 251 | normals.push( 252 | parseFloat( result[ 1 ] ), 253 | parseFloat( result[ 2 ] ), 254 | parseFloat( result[ 3 ] ) 255 | ); 256 | 257 | } else if ( ( result = uv_pattern.exec( line ) ) !== null ) { 258 | 259 | // ["vt 0.1 0.2", "0.1", "0.2"] 260 | 261 | uvs.push( 262 | parseFloat( result[ 1 ] ), 263 | parseFloat( result[ 2 ] ) 264 | ); 265 | 266 | } else if ( ( result = face_pattern1.exec( line ) ) !== null ) { 267 | 268 | // ["f 1 2 3", "1", "2", "3", undefined] 269 | 270 | addFace( 271 | result[ 1 ], result[ 2 ], result[ 3 ], result[ 4 ] 272 | ); 273 | 274 | } else if ( ( result = face_pattern2.exec( line ) ) !== null ) { 275 | 276 | // ["f 1/1 2/2 3/3", " 1/1", "1", "1", " 2/2", "2", "2", " 3/3", "3", "3", undefined, undefined, undefined] 277 | 278 | addFace( 279 | result[ 2 ], result[ 5 ], result[ 8 ], result[ 11 ], 280 | result[ 3 ], result[ 6 ], result[ 9 ], result[ 12 ] 281 | ); 282 | 283 | } else if ( ( result = face_pattern3.exec( line ) ) !== null ) { 284 | 285 | // ["f 1/1/1 2/2/2 3/3/3", " 1/1/1", "1", "1", "1", " 2/2/2", "2", "2", "2", " 3/3/3", "3", "3", "3", undefined, undefined, undefined, undefined] 286 | 287 | addFace( 288 | result[ 2 ], result[ 6 ], result[ 10 ], result[ 14 ], 289 | result[ 3 ], result[ 7 ], result[ 11 ], result[ 15 ], 290 | result[ 4 ], result[ 8 ], result[ 12 ], result[ 16 ] 291 | ); 292 | 293 | } else if ( ( result = face_pattern4.exec( line ) ) !== null ) { 294 | 295 | // ["f 1//1 2//2 3//3", " 1//1", "1", "1", " 2//2", "2", "2", " 3//3", "3", "3", undefined, undefined, undefined] 296 | 297 | addFace( 298 | result[ 2 ], result[ 5 ], result[ 8 ], result[ 11 ], 299 | undefined, undefined, undefined, undefined, 300 | result[ 3 ], result[ 6 ], result[ 9 ], result[ 12 ] 301 | ); 302 | 303 | } else if ( ( result = object_pattern.exec( line ) ) !== null ) { 304 | 305 | // o object_name 306 | // or 307 | // g group_name 308 | 309 | var name = result[ 1 ].trim(); 310 | 311 | if ( foundObjects === false ) { 312 | 313 | foundObjects = true; 314 | object.name = name; 315 | 316 | } else { 317 | 318 | addObject( name ); 319 | 320 | } 321 | 322 | } else if ( /^usemtl /.test( line ) ) { 323 | 324 | // material 325 | 326 | object.material.name = line.substring( 7 ).trim(); 327 | 328 | } else if ( /^mtllib /.test( line ) ) { 329 | 330 | // mtl file 331 | 332 | } else if ( ( result = smoothing_pattern.exec( line ) ) !== null ) { 333 | 334 | // smooth shading 335 | 336 | object.material.smooth = result[ 1 ] === "1" || result[ 1 ] === "on"; 337 | 338 | } else { 339 | 340 | throw new Error( "Unexpected line: " + line ); 341 | 342 | } 343 | 344 | } 345 | 346 | var container = new THREE.Group(); 347 | 348 | for ( var i = 0, l = objects.length; i < l; i ++ ) { 349 | 350 | object = objects[ i ]; 351 | var geometry = object.geometry; 352 | 353 | var buffergeometry = new THREE.BufferGeometry(); 354 | 355 | buffergeometry.addAttribute( 'position', new THREE.BufferAttribute( new Float32Array( geometry.vertices ), 3 ) ); 356 | 357 | if ( geometry.normals.length > 0 ) { 358 | 359 | buffergeometry.addAttribute( 'normal', new THREE.BufferAttribute( new Float32Array( geometry.normals ), 3 ) ); 360 | 361 | } else { 362 | 363 | buffergeometry.computeVertexNormals(); 364 | 365 | } 366 | 367 | if ( geometry.uvs.length > 0 ) { 368 | 369 | buffergeometry.addAttribute( 'uv', new THREE.BufferAttribute( new Float32Array( geometry.uvs ), 2 ) ); 370 | 371 | } 372 | 373 | var material; 374 | 375 | if ( this.materials !== null ) { 376 | 377 | material = this.materials.create( object.material.name ); 378 | 379 | } 380 | 381 | if ( !material ) { 382 | 383 | material = new THREE.MeshPhongMaterial(); 384 | material.name = object.material.name; 385 | 386 | } 387 | 388 | material.shading = object.material.smooth ? THREE.SmoothShading : THREE.FlatShading; 389 | 390 | var mesh = new THREE.Mesh( buffergeometry, material ); 391 | mesh.name = object.name; 392 | 393 | container.add( mesh ); 394 | 395 | } 396 | 397 | console.timeEnd( 'OBJLoader' ); 398 | 399 | return container; 400 | 401 | } 402 | 403 | }; 404 | -------------------------------------------------------------------------------- /js/loaders/AssimpJSONLoader.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Alexander Gessler / http://www.greentoken.de/ 3 | * https://github.com/acgessler 4 | * 5 | * Loader for models imported with Open Asset Import Library (http://assimp.sf.net) 6 | * through assimp2json (https://github.com/acgessler/assimp2json). 7 | * 8 | * Supports any input format that assimp supports, including 3ds, obj, dae, blend, 9 | * fbx, x, ms3d, lwo (and many more). 10 | * 11 | * See webgl_loader_assimp2json example. 12 | */ 13 | 14 | THREE.AssimpJSONLoader = function ( manager ) { 15 | 16 | this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager; 17 | 18 | }; 19 | 20 | THREE.AssimpJSONLoader.prototype = { 21 | 22 | constructor: THREE.AssimpJSONLoader, 23 | 24 | load: function ( url, onLoad, onProgress, onError ) { 25 | 26 | var scope = this; 27 | 28 | this.texturePath = this.texturePath && ( typeof this.texturePath === "string" ) ? this.texturePath : this.extractUrlBase( url ); 29 | 30 | var loader = new THREE.XHRLoader( this.manager ); 31 | loader.load( url, function ( text ) { 32 | 33 | var json = JSON.parse( text ), scene, metadata; 34 | 35 | // Check __metadata__ meta header if present 36 | // This header is used to disambiguate between 37 | // different JSON-based file formats. 38 | metadata = json.__metadata__; 39 | if ( typeof metadata !== 'undefined' ) { 40 | 41 | // Check if assimp2json at all 42 | if ( metadata.format !== 'assimp2json' ) { 43 | 44 | onError( 'Not an assimp2json scene' ); 45 | return; 46 | 47 | } 48 | // Check major format version 49 | else if ( metadata.version < 100 && metadata.version >= 200 ) { 50 | 51 | onError( 'Unsupported assimp2json file format version' ); 52 | return; 53 | 54 | } 55 | 56 | } 57 | 58 | scene = scope.parse( json ); 59 | onLoad( scene ); 60 | 61 | }, onProgress, onError ); 62 | 63 | }, 64 | 65 | setCrossOrigin: function ( value ) { 66 | 67 | this.crossOrigin = value; 68 | 69 | }, 70 | 71 | setTexturePath: function ( value ) { 72 | 73 | this.texturePath = value; 74 | 75 | }, 76 | 77 | extractUrlBase: function ( url ) { 78 | 79 | // from three/src/loaders/Loader.js 80 | var parts = url.split( '/' ); 81 | parts.pop(); 82 | return ( parts.length < 1 ? '.' : parts.join( '/' ) ) + '/'; 83 | 84 | }, 85 | 86 | parse: function ( json ) { 87 | 88 | var meshes = this.parseList ( json.meshes, this.parseMesh ); 89 | var materials = this.parseList ( json.materials, this.parseMaterial ); 90 | return this.parseObject( json, json.rootnode, meshes, materials ); 91 | 92 | }, 93 | 94 | parseList : function( json, handler ) { 95 | 96 | var meshes = new Array( json.length ); 97 | for ( var i = 0; i < json.length; ++ i ) { 98 | 99 | meshes[ i ] = handler.call( this, json[ i ] ); 100 | 101 | } 102 | return meshes; 103 | 104 | }, 105 | 106 | parseMesh : function( json ) { 107 | 108 | var vertex, geometry, i, e, in_data, src; 109 | 110 | 111 | geometry = new THREE.Geometry(); 112 | 113 | // read vertex positions 114 | for ( in_data = json.vertices, i = 0, e = in_data.length; i < e; ) { 115 | 116 | geometry.vertices.push( new THREE.Vector3( in_data[ i ++ ], in_data[ i ++ ], in_data[ i ++ ] ) ); 117 | 118 | } 119 | 120 | // read faces 121 | var cnt = 0; 122 | for ( in_data = json.faces, i = 0, e = in_data.length; i < e; ++ i ) { 123 | 124 | src = in_data[ i ]; 125 | face = new THREE.Face3( src[ 0 ], src[ 1 ], src[ 2 ] ); 126 | geometry.faces.push( face ); 127 | 128 | } 129 | 130 | // read texture coordinates - three.js attaches them to its faces 131 | json.texturecoords = json.texturecoords || []; 132 | for ( i = 0, e = json.texturecoords.length; i < e; ++ i ) { 133 | 134 | function convertTextureCoords( in_uv, out_faces, out_vertex_uvs ) { 135 | 136 | var i, e, face, a, b, c; 137 | 138 | for ( i = 0, e = out_faces.length; i < e; ++ i ) { 139 | 140 | face = out_faces[ i ]; 141 | a = face.a * 2; 142 | b = face.b * 2; 143 | c = face.c * 2; 144 | out_vertex_uvs.push( [ 145 | new THREE.Vector2( in_uv[ a ], in_uv[ a + 1 ] ), 146 | new THREE.Vector2( in_uv[ b ], in_uv[ b + 1 ] ), 147 | new THREE.Vector2( in_uv[ c ], in_uv[ c + 1 ] ) 148 | ] ); 149 | 150 | } 151 | 152 | } 153 | 154 | convertTextureCoords( json.texturecoords[ i ], geometry.faces, geometry.faceVertexUvs[ i ] ); 155 | 156 | } 157 | 158 | // read normals - three.js also attaches them to its faces 159 | if ( json.normals ) { 160 | 161 | function convertNormals( in_nor, out_faces ) { 162 | 163 | var i, e, face, a, b, c; 164 | 165 | for ( i = 0, e = out_faces.length; i < e; ++ i ) { 166 | 167 | face = out_faces[ i ]; 168 | a = face.a * 3; 169 | b = face.b * 3; 170 | c = face.c * 3; 171 | face.vertexNormals = [ 172 | new THREE.Vector3( in_nor[ a ], in_nor[ a + 1 ], in_nor[ a + 2 ] ), 173 | new THREE.Vector3( in_nor[ b ], in_nor[ b + 1 ], in_nor[ b + 2 ] ), 174 | new THREE.Vector3( in_nor[ c ], in_nor[ c + 1 ], in_nor[ c + 2 ] ) 175 | ]; 176 | 177 | } 178 | 179 | } 180 | 181 | convertNormals( json.normals, geometry.faces ); 182 | 183 | } 184 | 185 | // read vertex colors - three.js also attaches them to its faces 186 | if ( json.colors && json.colors[ 0 ] ) { 187 | 188 | function convertColors( in_color, out_faces ) { 189 | 190 | for ( var i = 0, e = out_faces.length; i < e; ++ i ) { 191 | 192 | var face = out_faces[ i ]; 193 | var a = face.a * 4; 194 | var b = face.b * 4; 195 | var c = face.c * 4; 196 | 197 | face.vertexColors = [ 198 | new THREE.Color().fromArray( a ), 199 | new THREE.Color().fromArray( b ), 200 | new THREE.Color().fromArray( c ) 201 | ]; 202 | 203 | } 204 | 205 | } 206 | 207 | convertColors( json.colors[ 0 ], geometry.faces ); 208 | 209 | } 210 | 211 | 212 | //geometry.computeFaceNormals(); 213 | //geometry.computeVertexNormals(); 214 | geometry.computeBoundingSphere(); 215 | 216 | return geometry; 217 | 218 | }, 219 | 220 | parseMaterial : function( json ) { 221 | 222 | var mat = null, 223 | scope = this, i, prop, has_textures = [], 224 | 225 | init_props = { 226 | shading : THREE.SmoothShading 227 | }; 228 | 229 | function toColor( value_arr ) { 230 | 231 | var col = new THREE.Color(); 232 | col.setRGB( value_arr[ 0 ], value_arr[ 1 ], value_arr[ 2 ] ); 233 | return col; 234 | 235 | } 236 | 237 | function defaultTexture() { 238 | 239 | var im = new Image(); 240 | im.width = 1; 241 | im.height = 1; 242 | return new THREE.Texture( im ); 243 | 244 | } 245 | 246 | for ( var i in json.properties ) { 247 | 248 | prop = json.properties[ i ]; 249 | 250 | if ( prop.key === '$tex.file' ) { 251 | 252 | // prop.semantic gives the type of the texture 253 | // 1: diffuse 254 | // 2: specular mao 255 | // 5: height map (bumps) 256 | // 6: normal map 257 | // more values (i.e. emissive, environment) are known by assimp and may be relevant 258 | if ( prop.semantic === 1 || prop.semantic === 5 || prop.semantic === 6 || prop.semantic === 2 ) { 259 | 260 | ( function( semantic ) { 261 | 262 | var loader = new THREE.TextureLoader( scope.manager ), 263 | keyname; 264 | 265 | if ( semantic === 1 ) { 266 | 267 | keyname = 'map'; 268 | 269 | } else if ( semantic === 5 ) { 270 | 271 | keyname = 'bumpMap'; 272 | 273 | } else if ( semantic === 6 ) { 274 | 275 | keyname = 'normalMap'; 276 | 277 | } else if ( semantic === 2 ) { 278 | 279 | keyname = 'specularMap'; 280 | 281 | } 282 | 283 | has_textures.push( keyname ); 284 | 285 | loader.setCrossOrigin( this.crossOrigin ); 286 | var material_url = scope.texturePath + '/' + prop.value; 287 | material_url = material_url.replace( /\\/g, '/' ); 288 | loader.load( material_url, function( tex ) { 289 | 290 | if ( tex ) { 291 | 292 | // TODO: read texture settings from assimp. 293 | // Wrapping is the default, though. 294 | tex.wrapS = tex.wrapT = THREE.RepeatWrapping; 295 | 296 | mat[ keyname ] = tex; 297 | mat.needsUpdate = true; 298 | 299 | } 300 | 301 | } ); 302 | 303 | } )( prop.semantic ); 304 | 305 | } 306 | 307 | } else if ( prop.key === '?mat.name' ) { 308 | 309 | init_props.name = prop.value; 310 | 311 | } else if ( prop.key === '$clr.diffuse' ) { 312 | 313 | init_props.color = toColor( prop.value ); 314 | 315 | } else if ( prop.key === '$clr.specular' ) { 316 | 317 | init_props.specular = toColor( prop.value ); 318 | 319 | } else if ( prop.key === '$clr.emissive' ) { 320 | 321 | init_props.emissive = toColor( prop.value ); 322 | 323 | } else if ( prop.key === '$mat.shadingm' ) { 324 | 325 | // aiShadingMode_Flat 326 | if ( prop.value === 1 ) { 327 | 328 | init_props.shading = THREE.FlatShading; 329 | 330 | } 331 | 332 | } else if ( prop.key === '$mat.shininess' ) { 333 | 334 | init_props.shininess = prop.value; 335 | 336 | } 337 | 338 | } 339 | 340 | // note: three.js does not like it when a texture is added after the geometry 341 | // has been rendered once, see http://stackoverflow.com/questions/16531759/. 342 | // for this reason we fill all slots upfront with default textures 343 | if ( has_textures.length ) { 344 | 345 | for ( i = has_textures.length - 1; i >= 0; -- i ) { 346 | 347 | init_props[ has_textures[ i ]] = defaultTexture(); 348 | 349 | } 350 | 351 | } 352 | 353 | mat = new THREE.MeshPhongMaterial( init_props ); 354 | return mat; 355 | 356 | }, 357 | 358 | parseObject : function( json, node, meshes, materials ) { 359 | 360 | var obj = new THREE.Object3D() 361 | , i 362 | , idx 363 | ; 364 | 365 | obj.name = node.name || ""; 366 | obj.matrix = new THREE.Matrix4().fromArray( node.transformation ).transpose(); 367 | obj.matrix.decompose( obj.position, obj.quaternion, obj.scale ); 368 | 369 | for ( i = 0; node.meshes && i < node.meshes.length; ++ i ) { 370 | 371 | idx = node.meshes[ i ]; 372 | obj.add( new THREE.Mesh( meshes[ idx ], materials[ json.meshes[ idx ].materialindex ] ) ); 373 | 374 | } 375 | 376 | for ( i = 0; node.children && i < node.children.length; ++ i ) { 377 | 378 | obj.add( this.parseObject( json, node.children[ i ], meshes, materials ) ); 379 | 380 | } 381 | 382 | return obj; 383 | 384 | }, 385 | }; 386 | -------------------------------------------------------------------------------- /js/loaders/RGBELoader.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Nikos M. / https://github.com/foo123/ 3 | */ 4 | 5 | // https://github.com/mrdoob/three.js/issues/5552 6 | // http://en.wikipedia.org/wiki/RGBE_image_format 7 | 8 | THREE.HDRLoader = THREE.RGBELoader = function ( manager ) { 9 | 10 | this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager; 11 | 12 | }; 13 | 14 | // extend THREE.BinaryTextureLoader 15 | THREE.RGBELoader.prototype = Object.create( THREE.BinaryTextureLoader.prototype ); 16 | 17 | // adapted from http://www.graphics.cornell.edu/~bjw/rgbe.html 18 | THREE.RGBELoader.prototype._parser = function( buffer ) { 19 | 20 | var 21 | /* return codes for rgbe routines */ 22 | RGBE_RETURN_SUCCESS = 0, 23 | RGBE_RETURN_FAILURE = - 1, 24 | 25 | /* default error routine. change this to change error handling */ 26 | rgbe_read_error = 1, 27 | rgbe_write_error = 2, 28 | rgbe_format_error = 3, 29 | rgbe_memory_error = 4, 30 | rgbe_error = function( rgbe_error_code, msg ) { 31 | 32 | switch ( rgbe_error_code ) { 33 | case rgbe_read_error: console.error( "THREE.RGBELoader Read Error: " + ( msg || '' ) ); 34 | break; 35 | case rgbe_write_error: console.error( "THREE.RGBELoader Write Error: " + ( msg || '' ) ); 36 | break; 37 | case rgbe_format_error: console.error( "THREE.RGBELoader Bad File Format: " + ( msg || '' ) ); 38 | break; 39 | default: 40 | case rgbe_memory_error: console.error( "THREE.RGBELoader: Error: " + ( msg || '' ) ); 41 | } 42 | return RGBE_RETURN_FAILURE; 43 | 44 | }, 45 | 46 | /* offsets to red, green, and blue components in a data (float) pixel */ 47 | RGBE_DATA_RED = 0, 48 | RGBE_DATA_GREEN = 1, 49 | RGBE_DATA_BLUE = 2, 50 | 51 | /* number of floats per pixel, use 4 since stored in rgba image format */ 52 | RGBE_DATA_SIZE = 4, 53 | 54 | /* flags indicating which fields in an rgbe_header_info are valid */ 55 | RGBE_VALID_PROGRAMTYPE = 1, 56 | RGBE_VALID_FORMAT = 2, 57 | RGBE_VALID_DIMENSIONS = 4, 58 | 59 | NEWLINE = "\n", 60 | 61 | fgets = function( buffer, lineLimit, consume ) { 62 | 63 | lineLimit = ! lineLimit ? 1024 : lineLimit; 64 | var p = buffer.pos, 65 | i = - 1, len = 0, s = '', chunkSize = 128, 66 | chunk = String.fromCharCode.apply( null, new Uint16Array( buffer.subarray( p, p + chunkSize ) ) ) 67 | ; 68 | while ( ( 0 > ( i = chunk.indexOf( NEWLINE ) ) ) && ( len < lineLimit ) && ( p < buffer.byteLength ) ) { 69 | 70 | s += chunk; len += chunk.length; 71 | p += chunkSize; 72 | chunk += String.fromCharCode.apply( null, new Uint16Array( buffer.subarray( p, p + chunkSize ) ) ); 73 | 74 | } 75 | 76 | if ( - 1 < i ) { 77 | 78 | /*for (i=l-1; i>=0; i--) { 79 | byteCode = m.charCodeAt(i); 80 | if (byteCode > 0x7f && byteCode <= 0x7ff) byteLen++; 81 | else if (byteCode > 0x7ff && byteCode <= 0xffff) byteLen += 2; 82 | if (byteCode >= 0xDC00 && byteCode <= 0xDFFF) i--; //trail surrogate 83 | }*/ 84 | if ( false !== consume ) buffer.pos += len + i + 1; 85 | return s + chunk.slice( 0, i ); 86 | 87 | } 88 | return false; 89 | 90 | }, 91 | 92 | /* minimal header reading. modify if you want to parse more information */ 93 | RGBE_ReadHeader = function( buffer ) { 94 | 95 | var line, match, 96 | 97 | // regexes to parse header info fields 98 | magic_token_re = /^#\?(\S+)$/, 99 | gamma_re = /^\s*GAMMA\s*=\s*(\d+(\.\d+)?)\s*$/, 100 | exposure_re = /^\s*EXPOSURE\s*=\s*(\d+(\.\d+)?)\s*$/, 101 | format_re = /^\s*FORMAT=(\S+)\s*$/, 102 | dimensions_re = /^\s*\-Y\s+(\d+)\s+\+X\s+(\d+)\s*$/, 103 | 104 | // RGBE format header struct 105 | header = { 106 | 107 | valid: 0, /* indicate which fields are valid */ 108 | 109 | string: '', /* the actual header string */ 110 | 111 | comments: '', /* comments found in header */ 112 | 113 | programtype: 'RGBE', /* listed at beginning of file to identify it 114 | * after "#?". defaults to "RGBE" */ 115 | 116 | format: '', /* RGBE format, default 32-bit_rle_rgbe */ 117 | 118 | gamma: 1.0, /* image has already been gamma corrected with 119 | * given gamma. defaults to 1.0 (no correction) */ 120 | 121 | exposure: 1.0, /* a value of 1.0 in an image corresponds to 122 | * watts/steradian/m^2. 123 | * defaults to 1.0 */ 124 | 125 | width: 0, height: 0 /* image dimensions, width/height */ 126 | 127 | } 128 | ; 129 | 130 | if ( buffer.pos >= buffer.byteLength || ! ( line = fgets( buffer ) ) ) { 131 | 132 | return rgbe_error( rgbe_read_error, "no header found" ); 133 | 134 | } 135 | /* if you want to require the magic token then uncomment the next line */ 136 | if ( ! ( match = line.match( magic_token_re ) ) ) { 137 | 138 | return rgbe_error( rgbe_format_error, "bad initial token" ); 139 | 140 | } 141 | header.valid |= RGBE_VALID_PROGRAMTYPE; 142 | header.programtype = match[ 1 ]; 143 | header.string += line + "\n"; 144 | 145 | while ( true ) { 146 | 147 | line = fgets( buffer ); 148 | if ( false === line ) break; 149 | header.string += line + "\n"; 150 | 151 | if ( '#' === line.charAt( 0 ) ) { 152 | 153 | header.comments += line + "\n"; 154 | continue; // comment line 155 | 156 | } 157 | 158 | if ( match = line.match( gamma_re ) ) { 159 | 160 | header.gamma = parseFloat( match[ 1 ], 10 ); 161 | 162 | } 163 | if ( match = line.match( exposure_re ) ) { 164 | 165 | header.exposure = parseFloat( match[ 1 ], 10 ); 166 | 167 | } 168 | if ( match = line.match( format_re ) ) { 169 | 170 | header.valid |= RGBE_VALID_FORMAT; 171 | header.format = match[ 1 ];//'32-bit_rle_rgbe'; 172 | 173 | } 174 | if ( match = line.match( dimensions_re ) ) { 175 | 176 | header.valid |= RGBE_VALID_DIMENSIONS; 177 | header.height = parseInt( match[ 1 ], 10 ); 178 | header.width = parseInt( match[ 2 ], 10 ); 179 | 180 | } 181 | 182 | if ( ( header.valid & RGBE_VALID_FORMAT ) && ( header.valid & RGBE_VALID_DIMENSIONS ) ) break; 183 | 184 | } 185 | 186 | if ( ! ( header.valid & RGBE_VALID_FORMAT ) ) { 187 | 188 | return rgbe_error( rgbe_format_error, "missing format specifier" ); 189 | 190 | } 191 | if ( ! ( header.valid & RGBE_VALID_DIMENSIONS ) ) { 192 | 193 | return rgbe_error( rgbe_format_error, "missing image size specifier" ); 194 | 195 | } 196 | 197 | return header; 198 | 199 | }, 200 | 201 | RGBE_ReadPixels_RLE = function( buffer, w, h ) { 202 | 203 | var data_rgba, offset, pos, count, byteValue, 204 | scanline_buffer, ptr, ptr_end, i, l, off, isEncodedRun, 205 | scanline_width = w, num_scanlines = h, rgbeStart 206 | ; 207 | 208 | if ( 209 | // run length encoding is not allowed so read flat 210 | ( ( scanline_width < 8 ) || ( scanline_width > 0x7fff ) ) || 211 | // this file is not run length encoded 212 | ( ( 2 !== buffer[ 0 ] ) || ( 2 !== buffer[ 1 ] ) || ( buffer[ 2 ] & 0x80 ) ) 213 | ) { 214 | 215 | // return the flat buffer 216 | return new Uint8Array( buffer ); 217 | 218 | } 219 | 220 | if ( scanline_width !== ( ( buffer[ 2 ] << 8 ) | buffer[ 3 ] ) ) { 221 | 222 | return rgbe_error( rgbe_format_error, "wrong scanline width" ); 223 | 224 | } 225 | 226 | data_rgba = new Uint8Array( 4 * w * h ); 227 | 228 | if ( ! data_rgba || ! data_rgba.length ) { 229 | 230 | return rgbe_error( rgbe_memory_error, "unable to allocate buffer space" ); 231 | 232 | } 233 | 234 | offset = 0; pos = 0; ptr_end = 4 * scanline_width; 235 | rgbeStart = new Uint8Array( 4 ); 236 | scanline_buffer = new Uint8Array( ptr_end ); 237 | 238 | // read in each successive scanline 239 | while ( ( num_scanlines > 0 ) && ( pos < buffer.byteLength ) ) { 240 | 241 | if ( pos + 4 > buffer.byteLength ) { 242 | 243 | return rgbe_error( rgbe_read_error ); 244 | 245 | } 246 | 247 | rgbeStart[ 0 ] = buffer[ pos ++ ]; 248 | rgbeStart[ 1 ] = buffer[ pos ++ ]; 249 | rgbeStart[ 2 ] = buffer[ pos ++ ]; 250 | rgbeStart[ 3 ] = buffer[ pos ++ ]; 251 | 252 | if ( ( 2 != rgbeStart[ 0 ] ) || ( 2 != rgbeStart[ 1 ] ) || ( ( ( rgbeStart[ 2 ] << 8 ) | rgbeStart[ 3 ] ) != scanline_width ) ) { 253 | 254 | return rgbe_error( rgbe_format_error, "bad rgbe scanline format" ); 255 | 256 | } 257 | 258 | // read each of the four channels for the scanline into the buffer 259 | // first red, then green, then blue, then exponent 260 | ptr = 0; 261 | while ( ( ptr < ptr_end ) && ( pos < buffer.byteLength ) ) { 262 | 263 | count = buffer[ pos ++ ]; 264 | isEncodedRun = count > 128; 265 | if ( isEncodedRun ) count -= 128; 266 | 267 | if ( ( 0 === count ) || ( ptr + count > ptr_end ) ) { 268 | 269 | return rgbe_error( rgbe_format_error, "bad scanline data" ); 270 | 271 | } 272 | 273 | if ( isEncodedRun ) { 274 | 275 | // a (encoded) run of the same value 276 | byteValue = buffer[ pos ++ ]; 277 | for ( i = 0; i < count; i ++ ) { 278 | 279 | scanline_buffer[ ptr ++ ] = byteValue; 280 | 281 | } 282 | //ptr += count; 283 | 284 | } else { 285 | 286 | // a literal-run 287 | scanline_buffer.set( buffer.subarray( pos, pos + count ), ptr ); 288 | ptr += count; pos += count; 289 | 290 | } 291 | 292 | } 293 | 294 | 295 | // now convert data from buffer into rgba 296 | // first red, then green, then blue, then exponent (alpha) 297 | l = scanline_width; //scanline_buffer.byteLength; 298 | for ( i = 0; i < l; i ++ ) { 299 | 300 | off = 0; 301 | data_rgba[ offset ] = scanline_buffer[ i + off ]; 302 | off += scanline_width; //1; 303 | data_rgba[ offset + 1 ] = scanline_buffer[ i + off ]; 304 | off += scanline_width; //1; 305 | data_rgba[ offset + 2 ] = scanline_buffer[ i + off ]; 306 | off += scanline_width; //1; 307 | data_rgba[ offset + 3 ] = scanline_buffer[ i + off ]; 308 | offset += 4; 309 | 310 | } 311 | 312 | num_scanlines --; 313 | 314 | } 315 | 316 | return data_rgba; 317 | 318 | } 319 | ; 320 | 321 | var byteArray = new Uint8Array( buffer ), 322 | byteLength = byteArray.byteLength; 323 | byteArray.pos = 0; 324 | var rgbe_header_info = RGBE_ReadHeader( byteArray ); 325 | 326 | if ( RGBE_RETURN_FAILURE !== rgbe_header_info ) { 327 | 328 | var w = rgbe_header_info.width, 329 | h = rgbe_header_info.height 330 | , image_rgba_data = RGBE_ReadPixels_RLE( byteArray.subarray( byteArray.pos ), w, h ) 331 | ; 332 | if ( RGBE_RETURN_FAILURE !== image_rgba_data ) { 333 | 334 | return { 335 | width: w, height: h, 336 | data: image_rgba_data, 337 | header: rgbe_header_info.string, 338 | gamma: rgbe_header_info.gamma, 339 | exposure: rgbe_header_info.exposure, 340 | format: THREE.RGBEFormat, // handled as THREE.RGBAFormat in shaders 341 | type: THREE.UnsignedByteType 342 | }; 343 | 344 | } 345 | 346 | } 347 | return null; 348 | 349 | }; 350 | -------------------------------------------------------------------------------- /js/loaders/collada/Animation.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author mikael emtinger / http://gomo.se/ 3 | * @author mrdoob / http://mrdoob.com/ 4 | * @author alteredq / http://alteredqualia.com/ 5 | */ 6 | 7 | THREE.Animation = function ( root, data ) { 8 | 9 | this.root = root; 10 | this.data = THREE.AnimationHandler.init( data ); 11 | this.hierarchy = THREE.AnimationHandler.parse( root ); 12 | 13 | this.currentTime = 0; 14 | this.timeScale = 1; 15 | 16 | this.isPlaying = false; 17 | this.loop = true; 18 | this.weight = 0; 19 | 20 | this.interpolationType = THREE.AnimationHandler.LINEAR; 21 | 22 | }; 23 | 24 | THREE.Animation.prototype = { 25 | 26 | constructor: THREE.Animation, 27 | 28 | keyTypes: [ "pos", "rot", "scl" ], 29 | 30 | play: function ( startTime, weight ) { 31 | 32 | this.currentTime = startTime !== undefined ? startTime : 0; 33 | this.weight = weight !== undefined ? weight : 1; 34 | 35 | this.isPlaying = true; 36 | 37 | this.reset(); 38 | 39 | THREE.AnimationHandler.play( this ); 40 | 41 | }, 42 | 43 | stop: function() { 44 | 45 | this.isPlaying = false; 46 | 47 | THREE.AnimationHandler.stop( this ); 48 | 49 | }, 50 | 51 | reset: function () { 52 | 53 | for ( var h = 0, hl = this.hierarchy.length; h < hl; h ++ ) { 54 | 55 | var object = this.hierarchy[ h ]; 56 | 57 | if ( object.animationCache === undefined ) { 58 | 59 | object.animationCache = { 60 | animations: {}, 61 | blending: { 62 | positionWeight: 0.0, 63 | quaternionWeight: 0.0, 64 | scaleWeight: 0.0 65 | } 66 | }; 67 | 68 | } 69 | 70 | var name = this.data.name; 71 | var animations = object.animationCache.animations; 72 | var animationCache = animations[ name ]; 73 | 74 | if ( animationCache === undefined ) { 75 | 76 | animationCache = { 77 | prevKey: { pos: 0, rot: 0, scl: 0 }, 78 | nextKey: { pos: 0, rot: 0, scl: 0 }, 79 | originalMatrix: object.matrix 80 | }; 81 | 82 | animations[ name ] = animationCache; 83 | 84 | } 85 | 86 | // Get keys to match our current time 87 | 88 | for ( var t = 0; t < 3; t ++ ) { 89 | 90 | var type = this.keyTypes[ t ]; 91 | 92 | var prevKey = this.data.hierarchy[ h ].keys[ 0 ]; 93 | var nextKey = this.getNextKeyWith( type, h, 1 ); 94 | 95 | while ( nextKey.time < this.currentTime && nextKey.index > prevKey.index ) { 96 | 97 | prevKey = nextKey; 98 | nextKey = this.getNextKeyWith( type, h, nextKey.index + 1 ); 99 | 100 | } 101 | 102 | animationCache.prevKey[ type ] = prevKey; 103 | animationCache.nextKey[ type ] = nextKey; 104 | 105 | } 106 | 107 | } 108 | 109 | }, 110 | 111 | resetBlendWeights: function () { 112 | 113 | for ( var h = 0, hl = this.hierarchy.length; h < hl; h ++ ) { 114 | 115 | var object = this.hierarchy[ h ]; 116 | var animationCache = object.animationCache; 117 | 118 | if ( animationCache !== undefined ) { 119 | 120 | var blending = animationCache.blending; 121 | 122 | blending.positionWeight = 0.0; 123 | blending.quaternionWeight = 0.0; 124 | blending.scaleWeight = 0.0; 125 | 126 | } 127 | 128 | } 129 | 130 | }, 131 | 132 | update: ( function() { 133 | 134 | var points = []; 135 | var target = new THREE.Vector3(); 136 | var newVector = new THREE.Vector3(); 137 | var newQuat = new THREE.Quaternion(); 138 | 139 | // Catmull-Rom spline 140 | 141 | var interpolateCatmullRom = function ( points, scale ) { 142 | 143 | var c = [], v3 = [], 144 | point, intPoint, weight, w2, w3, 145 | pa, pb, pc, pd; 146 | 147 | point = ( points.length - 1 ) * scale; 148 | intPoint = Math.floor( point ); 149 | weight = point - intPoint; 150 | 151 | c[ 0 ] = intPoint === 0 ? intPoint : intPoint - 1; 152 | c[ 1 ] = intPoint; 153 | c[ 2 ] = intPoint > points.length - 2 ? intPoint : intPoint + 1; 154 | c[ 3 ] = intPoint > points.length - 3 ? intPoint : intPoint + 2; 155 | 156 | pa = points[ c[ 0 ] ]; 157 | pb = points[ c[ 1 ] ]; 158 | pc = points[ c[ 2 ] ]; 159 | pd = points[ c[ 3 ] ]; 160 | 161 | w2 = weight * weight; 162 | w3 = weight * w2; 163 | 164 | v3[ 0 ] = interpolate( pa[ 0 ], pb[ 0 ], pc[ 0 ], pd[ 0 ], weight, w2, w3 ); 165 | v3[ 1 ] = interpolate( pa[ 1 ], pb[ 1 ], pc[ 1 ], pd[ 1 ], weight, w2, w3 ); 166 | v3[ 2 ] = interpolate( pa[ 2 ], pb[ 2 ], pc[ 2 ], pd[ 2 ], weight, w2, w3 ); 167 | 168 | return v3; 169 | 170 | }; 171 | 172 | var interpolate = function ( p0, p1, p2, p3, t, t2, t3 ) { 173 | 174 | var v0 = ( p2 - p0 ) * 0.5, 175 | v1 = ( p3 - p1 ) * 0.5; 176 | 177 | return ( 2 * ( p1 - p2 ) + v0 + v1 ) * t3 + ( - 3 * ( p1 - p2 ) - 2 * v0 - v1 ) * t2 + v0 * t + p1; 178 | 179 | }; 180 | 181 | return function ( delta ) { 182 | 183 | if ( this.isPlaying === false ) return; 184 | 185 | this.currentTime += delta * this.timeScale; 186 | 187 | if ( this.weight === 0 ) 188 | return; 189 | 190 | // 191 | 192 | var duration = this.data.length; 193 | 194 | if ( this.currentTime > duration || this.currentTime < 0 ) { 195 | 196 | if ( this.loop ) { 197 | 198 | this.currentTime %= duration; 199 | 200 | if ( this.currentTime < 0 ) 201 | this.currentTime += duration; 202 | 203 | this.reset(); 204 | 205 | } else { 206 | 207 | this.stop(); 208 | 209 | } 210 | 211 | } 212 | 213 | for ( var h = 0, hl = this.hierarchy.length; h < hl; h ++ ) { 214 | 215 | var object = this.hierarchy[ h ]; 216 | var animationCache = object.animationCache.animations[ this.data.name ]; 217 | var blending = object.animationCache.blending; 218 | 219 | // loop through pos/rot/scl 220 | 221 | for ( var t = 0; t < 3; t ++ ) { 222 | 223 | // get keys 224 | 225 | var type = this.keyTypes[ t ]; 226 | var prevKey = animationCache.prevKey[ type ]; 227 | var nextKey = animationCache.nextKey[ type ]; 228 | 229 | if ( ( this.timeScale > 0 && nextKey.time <= this.currentTime ) || 230 | ( this.timeScale < 0 && prevKey.time >= this.currentTime ) ) { 231 | 232 | prevKey = this.data.hierarchy[ h ].keys[ 0 ]; 233 | nextKey = this.getNextKeyWith( type, h, 1 ); 234 | 235 | while ( nextKey.time < this.currentTime && nextKey.index > prevKey.index ) { 236 | 237 | prevKey = nextKey; 238 | nextKey = this.getNextKeyWith( type, h, nextKey.index + 1 ); 239 | 240 | } 241 | 242 | animationCache.prevKey[ type ] = prevKey; 243 | animationCache.nextKey[ type ] = nextKey; 244 | 245 | } 246 | 247 | var scale = ( this.currentTime - prevKey.time ) / ( nextKey.time - prevKey.time ); 248 | 249 | var prevXYZ = prevKey[ type ]; 250 | var nextXYZ = nextKey[ type ]; 251 | 252 | if ( scale < 0 ) scale = 0; 253 | if ( scale > 1 ) scale = 1; 254 | 255 | // interpolate 256 | 257 | if ( type === "pos" ) { 258 | 259 | if ( this.interpolationType === THREE.AnimationHandler.LINEAR ) { 260 | 261 | newVector.x = prevXYZ[ 0 ] + ( nextXYZ[ 0 ] - prevXYZ[ 0 ] ) * scale; 262 | newVector.y = prevXYZ[ 1 ] + ( nextXYZ[ 1 ] - prevXYZ[ 1 ] ) * scale; 263 | newVector.z = prevXYZ[ 2 ] + ( nextXYZ[ 2 ] - prevXYZ[ 2 ] ) * scale; 264 | 265 | // blend 266 | var proportionalWeight = this.weight / ( this.weight + blending.positionWeight ); 267 | object.position.lerp( newVector, proportionalWeight ); 268 | blending.positionWeight += this.weight; 269 | 270 | } else if ( this.interpolationType === THREE.AnimationHandler.CATMULLROM || 271 | this.interpolationType === THREE.AnimationHandler.CATMULLROM_FORWARD ) { 272 | 273 | points[ 0 ] = this.getPrevKeyWith( "pos", h, prevKey.index - 1 )[ "pos" ]; 274 | points[ 1 ] = prevXYZ; 275 | points[ 2 ] = nextXYZ; 276 | points[ 3 ] = this.getNextKeyWith( "pos", h, nextKey.index + 1 )[ "pos" ]; 277 | 278 | scale = scale * 0.33 + 0.33; 279 | 280 | var currentPoint = interpolateCatmullRom( points, scale ); 281 | var proportionalWeight = this.weight / ( this.weight + blending.positionWeight ); 282 | blending.positionWeight += this.weight; 283 | 284 | // blend 285 | 286 | var vector = object.position; 287 | 288 | vector.x = vector.x + ( currentPoint[ 0 ] - vector.x ) * proportionalWeight; 289 | vector.y = vector.y + ( currentPoint[ 1 ] - vector.y ) * proportionalWeight; 290 | vector.z = vector.z + ( currentPoint[ 2 ] - vector.z ) * proportionalWeight; 291 | 292 | if ( this.interpolationType === THREE.AnimationHandler.CATMULLROM_FORWARD ) { 293 | 294 | var forwardPoint = interpolateCatmullRom( points, scale * 1.01 ); 295 | 296 | target.set( forwardPoint[ 0 ], forwardPoint[ 1 ], forwardPoint[ 2 ] ); 297 | target.sub( vector ); 298 | target.y = 0; 299 | target.normalize(); 300 | 301 | var angle = Math.atan2( target.x, target.z ); 302 | object.rotation.set( 0, angle, 0 ); 303 | 304 | } 305 | 306 | } 307 | 308 | } else if ( type === "rot" ) { 309 | 310 | THREE.Quaternion.slerp( prevXYZ, nextXYZ, newQuat, scale ); 311 | 312 | // Avoid paying the cost of an additional slerp if we don't have to 313 | if ( blending.quaternionWeight === 0 ) { 314 | 315 | object.quaternion.copy( newQuat ); 316 | blending.quaternionWeight = this.weight; 317 | 318 | } else { 319 | 320 | var proportionalWeight = this.weight / ( this.weight + blending.quaternionWeight ); 321 | THREE.Quaternion.slerp( object.quaternion, newQuat, object.quaternion, proportionalWeight ); 322 | blending.quaternionWeight += this.weight; 323 | 324 | } 325 | 326 | } else if ( type === "scl" ) { 327 | 328 | newVector.x = prevXYZ[ 0 ] + ( nextXYZ[ 0 ] - prevXYZ[ 0 ] ) * scale; 329 | newVector.y = prevXYZ[ 1 ] + ( nextXYZ[ 1 ] - prevXYZ[ 1 ] ) * scale; 330 | newVector.z = prevXYZ[ 2 ] + ( nextXYZ[ 2 ] - prevXYZ[ 2 ] ) * scale; 331 | 332 | var proportionalWeight = this.weight / ( this.weight + blending.scaleWeight ); 333 | object.scale.lerp( newVector, proportionalWeight ); 334 | blending.scaleWeight += this.weight; 335 | 336 | } 337 | 338 | } 339 | 340 | } 341 | 342 | return true; 343 | 344 | }; 345 | 346 | } )(), 347 | 348 | getNextKeyWith: function ( type, h, key ) { 349 | 350 | var keys = this.data.hierarchy[ h ].keys; 351 | 352 | if ( this.interpolationType === THREE.AnimationHandler.CATMULLROM || 353 | this.interpolationType === THREE.AnimationHandler.CATMULLROM_FORWARD ) { 354 | 355 | key = key < keys.length - 1 ? key : keys.length - 1; 356 | 357 | } else { 358 | 359 | key = key % keys.length; 360 | 361 | } 362 | 363 | for ( ; key < keys.length; key ++ ) { 364 | 365 | if ( keys[ key ][ type ] !== undefined ) { 366 | 367 | return keys[ key ]; 368 | 369 | } 370 | 371 | } 372 | 373 | return this.data.hierarchy[ h ].keys[ 0 ]; 374 | 375 | }, 376 | 377 | getPrevKeyWith: function ( type, h, key ) { 378 | 379 | var keys = this.data.hierarchy[ h ].keys; 380 | 381 | if ( this.interpolationType === THREE.AnimationHandler.CATMULLROM || 382 | this.interpolationType === THREE.AnimationHandler.CATMULLROM_FORWARD ) { 383 | 384 | key = key > 0 ? key : 0; 385 | 386 | } else { 387 | 388 | key = key >= 0 ? key : key + keys.length; 389 | 390 | } 391 | 392 | 393 | for ( ; key >= 0; key -- ) { 394 | 395 | if ( keys[ key ][ type ] !== undefined ) { 396 | 397 | return keys[ key ]; 398 | 399 | } 400 | 401 | } 402 | 403 | return this.data.hierarchy[ h ].keys[ keys.length - 1 ]; 404 | 405 | } 406 | 407 | }; 408 | --------------------------------------------------------------------------------