├── .gitignore ├── config.json ├── static ├── js │ ├── bootloaderOutdoors.js │ ├── bootloaderArtGallery.js │ ├── bootloaderUnionSquare.js │ ├── webRTCComponents │ │ ├── .floo │ │ ├── .flooignore │ │ ├── simpleWebRTCOverwrites.js │ │ ├── chatMain.js │ │ └── webRTCMain.js │ ├── object.js │ ├── setPrivateURL.js │ ├── audioHelpers │ │ └── volumeDistanceModifier.js │ ├── 3DComponents │ │ ├── skyboxes.js │ │ ├── galleryPictures.js │ │ ├── ceiling.js │ │ ├── yourPlayer.js │ │ ├── collisionDetection.js │ │ ├── otherPlayerUpdates.js │ │ ├── walls.js │ │ ├── avatars.js │ │ └── threeMain.js │ ├── vendor │ │ ├── UnpackDepthRGBAShader.js │ │ ├── stats.js │ │ ├── tween.min.js │ │ ├── PointerLockControls.js │ │ ├── Mirror.js │ │ ├── ThreeCSG.js │ │ └── EventEmitter.js │ ├── socketMain.js │ └── main.js ├── images │ ├── grid.png │ ├── grass.jpg │ ├── grass2.jpg │ ├── smiley.png │ ├── Applestone.jpg │ ├── checkerboard.jpg │ ├── offwhitewall.jpeg │ ├── galleryPictures │ │ ├── DOMTo3D.png │ │ ├── WebRTC.png │ │ ├── logoText.jpg │ │ └── initialSetup.png │ ├── skyBoxes │ │ ├── Sorsele3 │ │ │ ├── negx.jpg │ │ │ ├── negy.jpg │ │ │ ├── negz.jpg │ │ │ ├── posx.jpg │ │ │ ├── posy.jpg │ │ │ ├── posz.jpg │ │ │ └── readme.txt │ │ ├── Tantolunden │ │ │ ├── negx.jpg │ │ │ ├── negy.jpg │ │ │ ├── negz.jpg │ │ │ ├── posx.jpg │ │ │ ├── posy.jpg │ │ │ ├── posz.jpg │ │ │ └── readme.txt │ │ └── UnionSquare │ │ │ ├── negx.jpg │ │ │ ├── negy.jpg │ │ │ ├── negz.jpg │ │ │ ├── posx.jpg │ │ │ ├── posy.jpg │ │ │ ├── posz.jpg │ │ │ └── readme.txt │ └── bodyTextures │ │ └── defaultPerson │ │ ├── arm.png │ │ ├── body.png │ │ ├── hand.png │ │ ├── leg.png │ │ ├── shoe.png │ │ ├── side.png │ │ ├── bottom.png │ │ └── shoulder.png └── css │ └── style.css ├── .floo ├── views ├── 404.jade ├── 500.jade ├── pages │ ├── Contact.jade │ ├── Outdoors.jade │ ├── ArtGallery.jade │ ├── UnionSquare.jade │ ├── Problems.jade │ ├── layout.jade │ ├── About.jade │ ├── OutdoorsLayout.jade │ ├── UnionSquareLayout.jade │ └── ArtGalleryLayout.jade ├── layout.jade └── index.jade ├── .flooignore ├── config ├── stunservers.json └── turnservers.json ├── Makefile ├── test ├── object.js └── object.test.js ├── docs ├── FileStructure.md ├── ApplicationFlow.md └── FutureImprovements.md ├── test-main.js ├── package.json ├── README.md ├── sockets ├── translations.js └── signalmaster.js ├── karma.conf.js ├── PRESS-RELEASE.md └── server.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /config.json: -------------------------------------------------------------------------------- 1 | { "version": "v0.6.6" } -------------------------------------------------------------------------------- /static/js/bootloaderOutdoors.js: -------------------------------------------------------------------------------- 1 | var realFaces = new RealFaces("Outdoors"); 2 | -------------------------------------------------------------------------------- /.floo: -------------------------------------------------------------------------------- 1 | { 2 | "url": "https://floobits.com/HackReactor/realTalkEvents" 3 | } -------------------------------------------------------------------------------- /static/js/bootloaderArtGallery.js: -------------------------------------------------------------------------------- 1 | var realFaces = new RealFaces("ArtGallery"); 2 | -------------------------------------------------------------------------------- /static/js/bootloaderUnionSquare.js: -------------------------------------------------------------------------------- 1 | var realFaces = new RealFaces("UnionSquare"); 2 | -------------------------------------------------------------------------------- /views/404.jade: -------------------------------------------------------------------------------- 1 | h1='Not Found' 2 | div='Sorry, the page you are looking for does not exist.' -------------------------------------------------------------------------------- /.flooignore: -------------------------------------------------------------------------------- 1 | #* 2 | *.o 3 | *.pyc 4 | *~ 5 | extern/ 6 | node_modules/ 7 | tmp 8 | vendor/ -------------------------------------------------------------------------------- /static/images/grid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roryc89/Real-Faces/HEAD/static/images/grid.png -------------------------------------------------------------------------------- /static/images/grass.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roryc89/Real-Faces/HEAD/static/images/grass.jpg -------------------------------------------------------------------------------- /static/images/grass2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roryc89/Real-Faces/HEAD/static/images/grass2.jpg -------------------------------------------------------------------------------- /static/images/smiley.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roryc89/Real-Faces/HEAD/static/images/smiley.png -------------------------------------------------------------------------------- /static/js/webRTCComponents/.floo: -------------------------------------------------------------------------------- 1 | { 2 | "url": "https://floobits.com/HackReactor/webRTCComponents" 3 | } -------------------------------------------------------------------------------- /static/images/Applestone.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roryc89/Real-Faces/HEAD/static/images/Applestone.jpg -------------------------------------------------------------------------------- /static/images/checkerboard.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roryc89/Real-Faces/HEAD/static/images/checkerboard.jpg -------------------------------------------------------------------------------- /static/images/offwhitewall.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roryc89/Real-Faces/HEAD/static/images/offwhitewall.jpeg -------------------------------------------------------------------------------- /static/js/webRTCComponents/.flooignore: -------------------------------------------------------------------------------- 1 | #* 2 | *.o 3 | *.pyc 4 | *~ 5 | extern/ 6 | node_modules/ 7 | tmp 8 | vendor/ -------------------------------------------------------------------------------- /static/images/galleryPictures/DOMTo3D.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roryc89/Real-Faces/HEAD/static/images/galleryPictures/DOMTo3D.png -------------------------------------------------------------------------------- /static/images/galleryPictures/WebRTC.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roryc89/Real-Faces/HEAD/static/images/galleryPictures/WebRTC.png -------------------------------------------------------------------------------- /static/images/galleryPictures/logoText.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roryc89/Real-Faces/HEAD/static/images/galleryPictures/logoText.jpg -------------------------------------------------------------------------------- /static/images/skyBoxes/Sorsele3/negx.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roryc89/Real-Faces/HEAD/static/images/skyBoxes/Sorsele3/negx.jpg -------------------------------------------------------------------------------- /static/images/skyBoxes/Sorsele3/negy.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roryc89/Real-Faces/HEAD/static/images/skyBoxes/Sorsele3/negy.jpg -------------------------------------------------------------------------------- /static/images/skyBoxes/Sorsele3/negz.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roryc89/Real-Faces/HEAD/static/images/skyBoxes/Sorsele3/negz.jpg -------------------------------------------------------------------------------- /static/images/skyBoxes/Sorsele3/posx.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roryc89/Real-Faces/HEAD/static/images/skyBoxes/Sorsele3/posx.jpg -------------------------------------------------------------------------------- /static/images/skyBoxes/Sorsele3/posy.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roryc89/Real-Faces/HEAD/static/images/skyBoxes/Sorsele3/posy.jpg -------------------------------------------------------------------------------- /static/images/skyBoxes/Sorsele3/posz.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roryc89/Real-Faces/HEAD/static/images/skyBoxes/Sorsele3/posz.jpg -------------------------------------------------------------------------------- /static/images/skyBoxes/Tantolunden/negx.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roryc89/Real-Faces/HEAD/static/images/skyBoxes/Tantolunden/negx.jpg -------------------------------------------------------------------------------- /static/images/skyBoxes/Tantolunden/negy.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roryc89/Real-Faces/HEAD/static/images/skyBoxes/Tantolunden/negy.jpg -------------------------------------------------------------------------------- /static/images/skyBoxes/Tantolunden/negz.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roryc89/Real-Faces/HEAD/static/images/skyBoxes/Tantolunden/negz.jpg -------------------------------------------------------------------------------- /static/images/skyBoxes/Tantolunden/posx.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roryc89/Real-Faces/HEAD/static/images/skyBoxes/Tantolunden/posx.jpg -------------------------------------------------------------------------------- /static/images/skyBoxes/Tantolunden/posy.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roryc89/Real-Faces/HEAD/static/images/skyBoxes/Tantolunden/posy.jpg -------------------------------------------------------------------------------- /static/images/skyBoxes/Tantolunden/posz.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roryc89/Real-Faces/HEAD/static/images/skyBoxes/Tantolunden/posz.jpg -------------------------------------------------------------------------------- /static/images/skyBoxes/UnionSquare/negx.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roryc89/Real-Faces/HEAD/static/images/skyBoxes/UnionSquare/negx.jpg -------------------------------------------------------------------------------- /static/images/skyBoxes/UnionSquare/negy.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roryc89/Real-Faces/HEAD/static/images/skyBoxes/UnionSquare/negy.jpg -------------------------------------------------------------------------------- /static/images/skyBoxes/UnionSquare/negz.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roryc89/Real-Faces/HEAD/static/images/skyBoxes/UnionSquare/negz.jpg -------------------------------------------------------------------------------- /static/images/skyBoxes/UnionSquare/posx.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roryc89/Real-Faces/HEAD/static/images/skyBoxes/UnionSquare/posx.jpg -------------------------------------------------------------------------------- /static/images/skyBoxes/UnionSquare/posy.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roryc89/Real-Faces/HEAD/static/images/skyBoxes/UnionSquare/posy.jpg -------------------------------------------------------------------------------- /static/images/skyBoxes/UnionSquare/posz.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roryc89/Real-Faces/HEAD/static/images/skyBoxes/UnionSquare/posz.jpg -------------------------------------------------------------------------------- /static/images/galleryPictures/initialSetup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roryc89/Real-Faces/HEAD/static/images/galleryPictures/initialSetup.png -------------------------------------------------------------------------------- /views/500.jade: -------------------------------------------------------------------------------- 1 | !!! 5 2 | html 3 | head 4 | title='500 Error' 5 | body 6 | h1='The Server Encountered and Error' 7 | div=error -------------------------------------------------------------------------------- /static/images/bodyTextures/defaultPerson/arm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roryc89/Real-Faces/HEAD/static/images/bodyTextures/defaultPerson/arm.png -------------------------------------------------------------------------------- /static/images/bodyTextures/defaultPerson/body.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roryc89/Real-Faces/HEAD/static/images/bodyTextures/defaultPerson/body.png -------------------------------------------------------------------------------- /static/images/bodyTextures/defaultPerson/hand.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roryc89/Real-Faces/HEAD/static/images/bodyTextures/defaultPerson/hand.png -------------------------------------------------------------------------------- /static/images/bodyTextures/defaultPerson/leg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roryc89/Real-Faces/HEAD/static/images/bodyTextures/defaultPerson/leg.png -------------------------------------------------------------------------------- /static/images/bodyTextures/defaultPerson/shoe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roryc89/Real-Faces/HEAD/static/images/bodyTextures/defaultPerson/shoe.png -------------------------------------------------------------------------------- /static/images/bodyTextures/defaultPerson/side.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roryc89/Real-Faces/HEAD/static/images/bodyTextures/defaultPerson/side.png -------------------------------------------------------------------------------- /static/images/bodyTextures/defaultPerson/bottom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roryc89/Real-Faces/HEAD/static/images/bodyTextures/defaultPerson/bottom.png -------------------------------------------------------------------------------- /static/images/bodyTextures/defaultPerson/shoulder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roryc89/Real-Faces/HEAD/static/images/bodyTextures/defaultPerson/shoulder.png -------------------------------------------------------------------------------- /config/stunservers.json: -------------------------------------------------------------------------------- 1 | { 2 | "servers": [ 3 | {"url": "stun:stun.l.google.com:19302"}, 4 | {"url": "stun:stun.anyfirewall.com:3478"} 5 | ] 6 | } -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | TESTS = test/*.js 2 | 3 | test: 4 | @NODE_ENV=test ./node_modules/.bin/mocha \ 5 | --require should \ 6 | --reporter list \ 7 | --slow 20 \ 8 | --growl \ 9 | $(TESTS) 10 | .PHONY: test -------------------------------------------------------------------------------- /static/images/skyBoxes/Sorsele3/readme.txt: -------------------------------------------------------------------------------- 1 | Author 2 | ====== 3 | 4 | This is the work of Emil Persson, aka Humus. 5 | http://www.humus.name 6 | 7 | 8 | 9 | License 10 | ======= 11 | 12 | This work is licensed under a Creative Commons Attribution 3.0 Unported License. 13 | http://creativecommons.org/licenses/by/3.0/ 14 | -------------------------------------------------------------------------------- /static/images/skyBoxes/Tantolunden/readme.txt: -------------------------------------------------------------------------------- 1 | Author 2 | ====== 3 | 4 | This is the work of Emil Persson, aka Humus. 5 | http://www.humus.name 6 | 7 | 8 | 9 | License 10 | ======= 11 | 12 | This work is licensed under a Creative Commons Attribution 3.0 Unported License. 13 | http://creativecommons.org/licenses/by/3.0/ 14 | -------------------------------------------------------------------------------- /static/images/skyBoxes/UnionSquare/readme.txt: -------------------------------------------------------------------------------- 1 | Author 2 | ====== 3 | 4 | This is the work of Emil Persson, aka Humus. 5 | http://www.humus.name 6 | 7 | 8 | 9 | License 10 | ======= 11 | 12 | This work is licensed under a Creative Commons Attribution 3.0 Unported License. 13 | http://creativecommons.org/licenses/by/3.0/ 14 | -------------------------------------------------------------------------------- /test/object.js: -------------------------------------------------------------------------------- 1 | if (typeof NS == 'undefined') { NS = {}; } 2 | 3 | NS.myFunction = { 4 | //empty stuff array, filled during initialization 5 | stuff: [], 6 | 7 | init: function init() { 8 | this.stuff.push('Testing'); 9 | }, 10 | reset: function reset() { 11 | this.stuff = []; 12 | }, 13 | //will add new functionality here later 14 | }; 15 | 16 | NS.myFunction.init(); -------------------------------------------------------------------------------- /static/js/object.js: -------------------------------------------------------------------------------- 1 | if (typeof NS == 'undefined') { NS = {}; } 2 | 3 | NS.myFunction = { 4 | //empty stuff array, filled during initialization 5 | stuff: [], 6 | 7 | init: function init() { 8 | this.stuff.push('Testing'); 9 | }, 10 | reset: function reset() { 11 | this.stuff = []; 12 | }, 13 | //will add new functionality here later 14 | }; 15 | 16 | NS.myFunction.init(); -------------------------------------------------------------------------------- /docs/FileStructure.md: -------------------------------------------------------------------------------- 1 | # File Structure 2 | 3 | ## config 4 | 5 | Configuration files for STUN and TURN server, used to aid NAT traversal. 6 | 7 | ## sockets 8 | 9 | SocketIO servers which handle WebRTC signalling and communicating player translation updates between clients. 10 | 11 | ## static 12 | 13 | Static files that the express server sends and are used to build the 3D environments. 14 | 15 | ## views 16 | 17 | Jade template files that are used to generate the HTML pages. 18 | 19 | -------------------------------------------------------------------------------- /static/js/setPrivateURL.js: -------------------------------------------------------------------------------- 1 | var makeRandomString = function(){ 2 | var text = ""; 3 | var possible = "abcdefghijklmnopqrstuvwxyz0123456789"; 4 | 5 | for( var i=0; i < 7; i++ ) 6 | text += possible.charAt(Math.floor(Math.random() * possible.length)); 7 | 8 | return text; 9 | } 10 | 11 | var setPrivateURL = function(id, base){ 12 | 13 | var element = document.getElementById(id); 14 | 15 | element.href = base + makeRandomString(); 16 | 17 | } 18 | 19 | setPrivateURL('UnionSquarePrivate', 'UnionSquare-'); 20 | setPrivateURL('OutdoorsPrivate', 'Outdoors-'); 21 | setPrivateURL('ArtGalleryPrivate', 'ArtGallery-'); 22 | -------------------------------------------------------------------------------- /config/turnservers.json: -------------------------------------------------------------------------------- 1 | { 2 | "servers": [ 3 | { 4 | "url": "turn:54.187.203.135", 5 | "username": "a", 6 | "credential": "b" 7 | }, 8 | { 9 | "url":"turn:turn.anyfirewall.com:3478?transport=udp", 10 | "username":"1421510621:KellyApp", 11 | "credential":"a2w1Z2kwZ2Y" 12 | }, 13 | { 14 | "url":"turn:turn.anyfirewall.com:443?transport=tcp", 15 | "username":"1421510621:KellyApp", 16 | "credential":"a2w1Z2kwZ2Y" 17 | } 18 | ] 19 | } -------------------------------------------------------------------------------- /test-main.js: -------------------------------------------------------------------------------- 1 | var allTestFiles = []; 2 | var TEST_REGEXP = /(spec|test)\.js$/i; 3 | 4 | var pathToModule = function(path) { 5 | return path.replace(/^\/base\//, '').replace(/\.js$/, ''); 6 | }; 7 | 8 | Object.keys(window.__karma__.files).forEach(function(file) { 9 | if (TEST_REGEXP.test(file)) { 10 | // Normalize paths to RequireJS module names. 11 | allTestFiles.push(pathToModule(file)); 12 | } 13 | }); 14 | 15 | require.config({ 16 | // Karma serves files under /base, which is the basePath from your config file 17 | baseUrl: '/base', 18 | 19 | // dynamically load all test files 20 | deps: allTestFiles, 21 | 22 | // we have to kickoff jasmine, as it is asynchronous 23 | callback: window.__karma__.start 24 | }); 25 | -------------------------------------------------------------------------------- /static/js/audioHelpers/volumeDistanceModifier.js: -------------------------------------------------------------------------------- 1 | var volumeDistanceModifier = function(clientID){ 2 | var min = 15; 3 | var max = 90; 4 | var otherTranslation = realFaces.socket.lastRecordedPlayerTranslations[clientID]; 5 | if(!otherTranslation){ 6 | return 'does not exist' 7 | } 8 | 9 | var ox = otherTranslation.position.x; 10 | var yourx = realFaces.socket.yourPlayerTranslation.position.x; 11 | 12 | var xDistance = Math.abs(otherTranslation.position.x - realFaces.socket.yourPlayerTranslation.position.x); 13 | var yDistance = Math.abs(otherTranslation.position.y - realFaces.socket.yourPlayerTranslation.position.y); 14 | 15 | var totalDistance = Math.sqrt((xDistance * xDistance)+(yDistance * yDistance)); 16 | 17 | if (totalDistance < min) 18 | return 1; 19 | 20 | if (totalDistance > max) 21 | return 0; 22 | 23 | return ((max-min)-(totalDistance - min))/max; 24 | 25 | }; 26 | 27 | 28 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "realTalk", 3 | "description": "A 3D webGL videoconferencing tool using webRTC.", 4 | "author": "Rory Campbell ", 5 | "contributors": [ 6 | "Dave Mun " 7 | ], 8 | "version": "1.0.0", 9 | "dependencies": { 10 | "body-parser": "^1.10.2", 11 | "config.json": "0.0.3", 12 | "connect": "^1.8.5", 13 | "express": "4.11.1", 14 | "jade": "^0.20.0", 15 | "node-uuid": "^1.2.0", 16 | "socket.io": "^0.9.6" 17 | }, 18 | "start": "node server.js", 19 | "devDependencies": { 20 | "jasmine-core": "^2.1.3", 21 | "karma": "^0.12.31", 22 | "karma-chrome-launcher": "^0.1.7", 23 | "karma-firefox-launcher": "^0.1.4", 24 | "karma-jasmine": "^0.3.4", 25 | "karma-phantomjs-launcher": "^0.1.4", 26 | "karma-requirejs": "^0.2.2", 27 | "mocha": "*", 28 | "requirejs": "^2.1.15", 29 | "should": "*", 30 | "vows": "0.5.x" 31 | }, 32 | "engine": "node 0.10.0" 33 | } 34 | -------------------------------------------------------------------------------- /views/pages/Contact.jade: -------------------------------------------------------------------------------- 1 | extends ./layout 2 | 3 | block content 4 | .container 5 | .header 6 | ul.nav.nav-pills.pull-right 7 | li 8 | a(href='./') Home 9 | li 10 | a(href='./About') About 11 | li 12 | a(href='./Problems') Problems 13 | li.active 14 | a(href='./Contact') Contact 15 | h3.text-muted Real Faces 16 | .jumbotron 17 | h2 Contact 18 | .col-lg-6 19 | p.lead 20 | | Rory: 21 | p.lead 22 | a(href='mailto:roryc89@gmail.com') roryc89@gmail.com
23 | a(href='https://www.linkedin.com/in/roryjcampbell') LinkedIn
24 | a(href='https://github.com/roryc89') GitHub 25 | .col-lg-6 26 | p.lead 27 | | Dave: 28 | p.lead 29 | a(href='mailto:mun.dave@gmail.com') mun.dave@gmail.com
30 | a(href='https://www.linkedin.com/in/davemun') LinkedIn
31 | a(href='https://github.com/davemun') GitHub 32 | 33 | -------------------------------------------------------------------------------- /static/js/3DComponents/skyboxes.js: -------------------------------------------------------------------------------- 1 | var createSkybox = function(sceneName, size, context){ 2 | 3 | var skyBoxDir = sceneName; 4 | 5 | var path = "images/skyBoxes/" + skyBoxDir + "/"; 6 | var format = '.jpg'; 7 | var urls = [ 8 | path + 'posx' + format, path + 'negx' + format, 9 | path + 'posy' + format, path + 'negy' + format, 10 | path + 'posz' + format, path + 'negz' + format 11 | ]; 12 | 13 | var reflectionCube = THREE.ImageUtils.loadTextureCube( urls ); 14 | reflectionCube.format = THREE.RGBFormat; 15 | 16 | var shader = THREE.ShaderLib[ "cube" ]; 17 | shader.uniforms[ "tCube" ].value = reflectionCube; 18 | 19 | var material = new THREE.ShaderMaterial( { 20 | 21 | fragmentShader: shader.fragmentShader, 22 | vertexShader: shader.vertexShader, 23 | uniforms: shader.uniforms, 24 | depthWrite: false, 25 | side: THREE.BackSide 26 | 27 | } ), 28 | 29 | skyBox = new THREE.Mesh( new THREE.BoxGeometry( size, size, size ), material ); 30 | skyBox.position.set(0, size * 0.4, 0); 31 | context.scene.add( skyBox ); 32 | } 33 | -------------------------------------------------------------------------------- /views/pages/Outdoors.jade: -------------------------------------------------------------------------------- 1 | extends OutdoorsLayout 2 | 3 | block content 4 | div(id='game') 5 | div(id='blocker') 6 | h3(id='instructions') 7 | span(style="font-size:40px")='Welcome to Real Faces' 8 | |
9 | |To work properly the website requires access to your webcam and to disable your cursor.
10 | |When the browser asks if you want to allow access (at the top of the window), click allow.
11 | |To move use W,A,S,D or the arrows. To look around use your mouse. Press Esc to pause.
12 | |Click to start! 13 | 14 | video(id='localVideo') 15 | span(id='remotesVideos') 16 | div(id='roomURL') 17 | | Your room URL: Not yet defined 18 | div(id='controls')='Pause/Help: Esc Move:W,A,S,D / Arrows Jump:Space Rotate:Mouse Chatbox:T, then click once to leave' 19 | //- div(id='webcamWarning') Looks like you haven't enabled your webcam! Please re-enable it by first pressing "Esc" to pause, then press "P" on your keyboard, and finally, click "Accept" ! You will not be able to see other videos until you do so! 20 | div(id='chatBox') 21 | input(id="chatInput") 22 | button(id="chatInputButton") Send! 23 | -------------------------------------------------------------------------------- /views/pages/ArtGallery.jade: -------------------------------------------------------------------------------- 1 | extends ArtGalleryLayout 2 | 3 | block content 4 | div(id='game') 5 | div(id='blocker') 6 | h3(id='instructions') 7 | span(style="font-size:40px")='Welcome to Real Faces' 8 | |
9 | |To work properly the website requires access to your webcam and to disable your cursor.
10 | |When the browser asks if you want to allow access (at the top of the window), click allow.
11 | |To move use W,A,S,D or the arrows. To look around use your mouse. Press Esc to pause.
12 | |Click to start! 13 | 14 | video(id='localVideo') 15 | span(id='remotesVideos') 16 | div(id='roomURL') 17 | | Your room URL: Not yet defined 18 | div(id='controls')='Pause/Help: Esc Move:W,A,S,D / Arrows Jump:Space Rotate:Mouse Chatbox:T, then click once to leave' 19 | //- div(id='webcamWarning') Looks like you haven't enabled your webcam! Please re-enable it by first pressing "Esc" to pause, then press "P" on your keyboard, and finally, click "Accept" ! You will not be able to see other videos until you do so! 20 | div(id='chatBox') 21 | input(id="chatInput") 22 | button(id="chatInputButton") Send! 23 | -------------------------------------------------------------------------------- /views/pages/UnionSquare.jade: -------------------------------------------------------------------------------- 1 | extends UnionSquareLayout 2 | 3 | block content 4 | div(id='game') 5 | div(id='blocker') 6 | h3(id='instructions') 7 | span(style="font-size:40px")='Welcome to Real Faces' 8 | |
9 | |To work properly the website requires access to your webcam and to disable your cursor.
10 | |When the browser asks if you want to allow access (at the top of the window), click allow.
11 | |To move use W,A,S,D or the arrows. To look around use your mouse. Press Esc to pause.
12 | |Click to start! 13 | 14 | video(id='localVideo') 15 | span(id='remotesVideos') 16 | div(id='roomURL') 17 | | Your room URL: Not yet defined 18 | div(id='controls')='Pause/Help: Esc Move:W,A,S,D / Arrows Jump:Space Rotate:Mouse Chatbox:T, then click once to leave' 19 | //- div(id='webcamWarning') Looks like you haven't enabled your webcam! Please re-enable it by first pressing "Esc" to pause, then press "P" on your keyboard, and finally, click "Accept" ! You will not be able to see other videos until you do so! 20 | div(id='chatBox') 21 | input(id="chatInput") 22 | button(id="chatInputButton") Send! 23 | -------------------------------------------------------------------------------- /static/js/vendor/UnpackDepthRGBAShader.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author alteredq / http://alteredqualia.com/ 3 | * 4 | * Unpack RGBA depth shader 5 | * - show RGBA encoded depth as monochrome color 6 | */ 7 | 8 | THREE.UnpackDepthRGBAShader = { 9 | 10 | uniforms: { 11 | 12 | "tDiffuse": { type: "t", value: null }, 13 | "opacity": { type: "f", value: 1.0 } 14 | 15 | }, 16 | 17 | vertexShader: [ 18 | 19 | "varying vec2 vUv;", 20 | 21 | "void main() {", 22 | 23 | "vUv = uv;", 24 | "gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );", 25 | 26 | "}" 27 | 28 | ].join("\n"), 29 | 30 | fragmentShader: [ 31 | 32 | "uniform float opacity;", 33 | 34 | "uniform sampler2D tDiffuse;", 35 | 36 | "varying vec2 vUv;", 37 | 38 | // RGBA depth 39 | 40 | "float unpackDepth( const in vec4 rgba_depth ) {", 41 | 42 | "const vec4 bit_shift = vec4( 1.0 / ( 256.0 * 256.0 * 256.0 ), 1.0 / ( 256.0 * 256.0 ), 1.0 / 256.0, 1.0 );", 43 | "float depth = dot( rgba_depth, bit_shift );", 44 | "return depth;", 45 | 46 | "}", 47 | 48 | "void main() {", 49 | 50 | "float depth = 1.0 - unpackDepth( texture2D( tDiffuse, vUv ) );", 51 | "gl_FragColor = opacity * vec4( vec3( depth ), 1.0 );", 52 | 53 | "}" 54 | 55 | ].join("\n") 56 | 57 | }; 58 | -------------------------------------------------------------------------------- /views/pages/Problems.jade: -------------------------------------------------------------------------------- 1 | extends layout 2 | 3 | block content 4 | .container 5 | .header 6 | ul.nav.nav-pills.pull-right 7 | li 8 | a(href='./') Home 9 | li 10 | a(href='./About') About 11 | li.active 12 | a(href='./Problems') Problems 13 | li 14 | a(href='./Contact') Contact 15 | h3.text-muted Real Faces 16 | .jumbotron 17 | h2 Problems 18 | p.lead 19 | | Real faces has been built with new and developing technologies so it wont run smoothly for everyone 20 | //- p 21 | //- a.btn.btn-lg.btn-success(href='#', role='button') Sign up today 22 | .row.marketing 23 | .col-lg-12 24 | p Real faces was built upon WebGL and WebRTC. WebGL will perform poorly on older computers with less powerful graphics cards and WebRTC quality will differ depending on your location and internet bandwidth. 25 | | You will need a webcam to use Real Faces properly and is best run on Google Chrome or Firefox browsers. 26 | | If you see a way to fix/improve Real Faces please let us know and we'll do out best to make it happen (or make a pull request yourself!) 27 | 28 | .footer 29 | p 30 | |

Built by 31 | a(href='https://github.com/davemun') Dave Mun 32 | | and 33 | a(href='https://github.com/roryc89') Rory Campbell 34 | 35 | -------------------------------------------------------------------------------- /docs/ApplicationFlow.md: -------------------------------------------------------------------------------- 1 | # Application Flow 2 | 3 | This page describes the process of the Application at a high level. 4 | 5 | ## Index 6 | 7 | The index page and other pages linked in the navbar (e.g. Contact) are simple static html pages generated from jade files. 8 | 9 | ## 3D Scenes 10 | 11 | Once a scene has been selected on the index page, static/js/main.js initializes the App. The 3D environment is built with the Three.js abstraction over webGL. The initialization functions for this are in static/js/3DComponents/threemain.js and helper functions are in the same directory. 12 | 13 | The syncing of player movements in the scene between clients is done via socketIO. On the client side this is handled in static/js/socketMain.js and the socketIO server is located sockets/translations.js. Tweening is used to smooth other player movements between socket updates. 14 | 15 | ## Video Audio Streaming 16 | 17 | Media Streams are transmitted peer to peer via WebRTC. This is initialized via the RealWebRTC constructor in static/js/webRTCCompnents/webRTCMain.js. Once the streams have been established the socket ID of each client is sent to other users via the a WebRTC data channel, which allows other clients to match video streams to player avatars (which have their socket ID as part of their name). 18 | 19 | Once the videos are matched with their corresponding avatar, videos are rendered on the face of each avatar in the WebGL canvas by using the video element in the dom as a texture. 20 | -------------------------------------------------------------------------------- /views/pages/layout.jade: -------------------------------------------------------------------------------- 1 | !!! 5 2 | //if lt IE 7 3 | html.no-js.ie6.oldie(lang='en') 4 | //if IE 7 5 | html.no-js.ie7.oldie(lang='en') 6 | //if IE 8 7 | html.no-js.ie8.oldie(lang='en') 8 | //[if gt IE 8]><\\/script>') 32 | //-script(defer, src='js/plugins.js') 33 | //if lt IE 7 34 | script(defer, src='//ajax.googleapis.com/ajax/libs/chrome-frame/1.0.3/CFInstall.min.js') 35 | script(defer) 36 | window.attachEvent('onload',function(){CFInstall.check({mode:'overlay'})}) 37 | -------------------------------------------------------------------------------- /static/js/3DComponents/galleryPictures.js: -------------------------------------------------------------------------------- 1 | var createGalleryPicture = function(context, image, x, z, rotated, width, height, y){ 2 | var rotated = rotated || false; 3 | var height = height || 40; 4 | var width = width || 40; 5 | 6 | var plainMaterial = new THREE.MeshBasicMaterial( {color: 'white'} ); 7 | 8 | var texture = new THREE.ImageUtils.loadTexture( 'images/galleryPictures/' + image ); 9 | //var material = new THREE.MeshLambertMaterial( {map:texture, side:THREE.DoubleSide, color: 'white'} ); 10 | 11 | var materialArray = []; 12 | materialArray.push(plainMaterial); 13 | materialArray.push(plainMaterial); 14 | materialArray.push(plainMaterial); 15 | materialArray.push(plainMaterial); 16 | materialArray.push(new THREE.MeshBasicMaterial( { color:'white', map: texture })); 17 | materialArray.push(new THREE.MeshBasicMaterial( { color:'white', map: texture })); 18 | 19 | var material = new THREE.MeshFaceMaterial(materialArray); 20 | 21 | 22 | var picture = new THREE.Mesh( new THREE.BoxGeometry(width, height, 1), material ); 23 | picture.position.set(x, y || height/2, z); 24 | 25 | if (rotated){ 26 | picture.rotation.y = Math.PI / 2; 27 | } 28 | context.scene.add(picture); 29 | } 30 | 31 | var createGalleryPictures = function(context){ 32 | createGalleryPicture(context, 'initialSetup.png', -120, -98); 33 | createGalleryPicture(context, 'WebRTC.png', -70, -98); 34 | createGalleryPicture(context, 'DOMTo3D.png', -51, -20, true); 35 | createGalleryPicture(context, 'logoText.jpg', -18, 48, false, 50, 15, 30); 36 | } 37 | -------------------------------------------------------------------------------- /views/layout.jade: -------------------------------------------------------------------------------- 1 | !!! 5 2 | //if lt IE 7 3 | html.no-js.ie6.oldie(lang='en') 4 | //if IE 7 5 | html.no-js.ie7.oldie(lang='en') 6 | //if IE 8 7 | html.no-js.ie8.oldie(lang='en') 8 | //[if gt IE 8]><\\/script>') 33 | //-script(defer, src='js/plugins.js') 34 | //if lt IE 7 35 | script(defer, src='//ajax.googleapis.com/ajax/libs/chrome-frame/1.0.3/CFInstall.min.js') 36 | script(defer) 37 | window.attachEvent('onload',function(){CFInstall.check({mode:'overlay'})}) 38 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [ ![Codeship Status for realTalkTeam/realTalk](https://codeship.com/projects/ac08c320-6c68-0132-76fc-3a463caf9dbd/status?branch=master)](https://codeship.com/projects/54042) 2 | 3 | #Join the live deployment! 4 | 5 | You can view and use our project online at http://realfaces.org! 6 | 7 | ## Table of Contents 8 | 9 | 1. [Usage](#usage) 10 | 1. [Development](#development) 11 | 1. [Requirements](#requirements) 12 | 2. [Installing Dependencies](#installing-dependencies) 13 | 3. [Running the Server](#running-the-server) 14 | 1. [Team](#team) 15 | 1. [Roadmap](#roadmap) 16 | 1. [Contributing](#contributing) 17 | 18 | 19 | ## Usage 20 | 21 | - Click "Allow" on the webcam access prompt, type in your desired username, and have fun! Use WASD to walk around, press ESC to pause the client. Further detailed controls in the header. 22 | 23 | #Development 24 | 25 | ## Requirements 26 | 27 | - Node 0.10.x 28 | - Socket.io 29 | - THREE.js/WebGL 30 | - WebRTC 31 | 32 | 33 | ## Installing Dependencies 34 | 35 | From within the root directory: 36 | ``` 37 | npm install 38 | bower install 39 | ``` 40 | 41 | ## Running the Server 42 | ``` 43 | npm start 44 | ``` 45 | 46 | # Team 47 | 48 | - __Product Owner__: Rory Campbell 49 | - __Scrum Master__: Dave Mun 50 | - __Development Team Members__: Rory Campbell, Dave Mun 51 | 52 | # Roadmap 53 | 54 | View the [project roadmap](https://github.com/realTalkTeam/realTalk/issues). 55 | 56 | 57 | # Contributing 58 | 59 | See [CONTRIBUTING.md](CONTRIBUTING.md) for contribution guidelines. 60 | -------------------------------------------------------------------------------- /static/js/webRTCComponents/simpleWebRTCOverwrites.js: -------------------------------------------------------------------------------- 1 | // var speaking = false; 2 | // //This overwrite contains confusing variable names as we do not wish to alter the library 3 | // //but this allows positional sound 4 | // //volume 1 means that the user is not speaking 5 | // //volume 0.25 means the user is speaking 6 | // //volume 0 is used to denote no change 7 | SimpleWebRTC.prototype.setVolumeForAll = function (harkVolume, dontChangeHarkVolume) { 8 | // var volume; 9 | // //console.log('setting volume', harkVolume, dontChangeHarkVolume); 10 | 11 | // // Strange semantics due to SimpleWebRTC workaround 12 | // // SimpleWebRTC lowers volume of others when user is talking 13 | // if (!dontChangeHarkVolume){ 14 | // if (harkVolume === 1){ 15 | // this.speaking = false; 16 | // volume = 1; 17 | // }else if(harkVolume === 0.25){ 18 | // this.speaking = true; 19 | // volume = 0.25; 20 | // } 21 | // }else if (this.speaking === false){ 22 | // volume = 1; 23 | // }else if (this.speaking === true){ 24 | // volume = 0.25; 25 | // } 26 | 27 | var peers = realFaces.webrtc.webrtc.webrtc.peers; 28 | 29 | peers.forEach(function (peer) { 30 | if (peer.socketID){ 31 | var vdm = volumeDistanceModifier(peer.socketID); 32 | //console.log('vdm', vdm, volume) 33 | if (vdm === 'does not exist'){ 34 | delete peers[peer]; 35 | }else{ 36 | var harkVolume = harkVolume || 1; 37 | peer.videoEl.volume = harkVolume * vdm * vdm; 38 | } 39 | } 40 | }); 41 | }; 42 | -------------------------------------------------------------------------------- /test/object.test.js: -------------------------------------------------------------------------------- 1 | if (typeof NS == 'undefined') { NS = {}; } 2 | 3 | NS.myFunction = { 4 | //empty stuff array, filled during initialization 5 | stuff: [], 6 | 7 | init: function init() { 8 | this.stuff.push('Testing'); 9 | }, 10 | reset: function reset() { 11 | this.stuff = []; 12 | }, 13 | 14 | //replace “//will add new functionality here later” with the following: 15 | append: function append(string1, string2) { 16 | return string1 + ' ' + string2; 17 | } 18 | }; 19 | 20 | NS.myFunction.init(); 21 | 22 | 23 | describe("myFunction", function() { 24 | var myfunc = NS.myFunction; 25 | 26 | beforeEach(function(){ 27 | spyOn(myfunc, 'init').and.callThrough(); 28 | }); 29 | 30 | afterEach(function() { 31 | myfunc.reset(); 32 | }); 33 | 34 | it("should be able to initialize", function() { 35 | expect(myfunc.init).toBeDefined(); 36 | myfunc.init(); 37 | expect(myfunc.init).toHaveBeenCalled(); 38 | }); 39 | 40 | it("should populate stuff during initialization", function(){ 41 | myfunc.init(); 42 | expect(myfunc.stuff.length).toEqual(1); 43 | expect(myfunc.stuff[0]).toEqual('Testing'); 44 | }); 45 | 46 | //replace “//will insert additional tests here later” with the following: 47 | describe("appending strings", function() { 48 | it("should be able to append 2 strings", function() { 49 | expect(myfunc.append).toBeDefined(); 50 | }); 51 | it("should append 2 strings", function() { 52 | expect(myfunc.append('hello','world')).toEqual('hello world'); 53 | }); 54 | }); 55 | 56 | }); -------------------------------------------------------------------------------- /sockets/translations.js: -------------------------------------------------------------------------------- 1 | module.exports = function(io){ 2 | //create socket.io client movement namespacing 3 | var translations = io.of('/translations'); 4 | 5 | var clientTranslations = {}; 6 | 7 | translations.on('connection', function(client){ 8 | 9 | client.on('select_room', function(roomName){ 10 | client.join(roomName); 11 | client.roomName = roomName; 12 | }); 13 | 14 | client.on('player_join', function(){ 15 | 16 | console.log('Client Connected', client.id); 17 | 18 | if (!clientTranslations[client.roomName]){ 19 | clientTranslations[client.roomName] = {}; 20 | } 21 | 22 | clientTranslations[client.roomName][client.id] = { 23 | position: {x:0, y:15, z:0}, 24 | rotation: {x:0, y:0} 25 | }; 26 | 27 | //tells new clients about pre-existing clients 28 | client.emit('preexisting_clients', clientTranslations[client.roomName], client.id); 29 | 30 | //tells all pre-existing clients about new client 31 | client.broadcast.to(client.roomName).emit('new_client', client.id); 32 | 33 | //sets event listener for new client 34 | client.on('translate', function(translation){ 35 | client.broadcast.to(client.roomName).emit('move_other_player', {clientID:client.id, translation:translation}); 36 | clientTranslations[client.roomName][client.id] = translation; 37 | }); 38 | 39 | //sets disconnect listener for new client 40 | client.on('disconnect', function(){ 41 | console.log('Client Disconnected.', client.id); 42 | 43 | delete clientTranslations[client.roomName][client.id]; 44 | 45 | client.broadcast.to(client.roomName).emit('client_disconnected', client.id); 46 | }); 47 | }); 48 | }); 49 | }; 50 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration 2 | // Generated on Sat Jan 17 2015 06:56:55 GMT-0800 (Pacific Standard Time) 3 | 4 | module.exports = function(config) { 5 | config.set({ 6 | 7 | // base path that will be used to resolve all patterns (eg. files, exclude) 8 | basePath: '', 9 | 10 | 11 | // frameworks to use 12 | // available frameworks: https://npmjs.org/browse/keyword/karma-adapter 13 | frameworks: ['jasmine', 'requirejs'], 14 | 15 | 16 | // list of files / patterns to load in the browser 17 | files: [ 18 | 'test-main.js', 19 | {pattern: 'static/js/*.js', included: false}, 20 | {pattern: 'static/js/*/*.js', included: false}, 21 | {pattern: 'test/*.js', included: false} 22 | ], 23 | 24 | 25 | // list of files to exclude 26 | exclude: [ 27 | ], 28 | 29 | 30 | // preprocess matching files before serving them to the browser 31 | // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor 32 | preprocessors: { 33 | }, 34 | 35 | 36 | // test results reporter to use 37 | // possible values: 'dots', 'progress' 38 | // available reporters: https://npmjs.org/browse/keyword/karma-reporter 39 | reporters: ['progress'], 40 | 41 | 42 | // web server port 43 | port: 9876, 44 | 45 | 46 | // enable / disable colors in the output (reporters and logs) 47 | colors: true, 48 | 49 | 50 | // level of logging 51 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 52 | logLevel: config.LOG_INFO, 53 | 54 | 55 | // enable / disable watching file and executing tests whenever any file changes 56 | autoWatch: false, 57 | 58 | 59 | // start these browsers 60 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher 61 | browsers: ['PhantomJS'], 62 | 63 | 64 | // Continuous Integration mode 65 | // if true, Karma captures browsers, runs the tests and exits 66 | singleRun: true 67 | }); 68 | }; 69 | -------------------------------------------------------------------------------- /static/js/3DComponents/ceiling.js: -------------------------------------------------------------------------------- 1 | var createCeiling = function (options, context){ 2 | 3 | var options = options || {}; 4 | 5 | var height = options.height || 2; 6 | var width = options.width || 150; 7 | var length = options.length || 250; 8 | var x = options.x || -25; 9 | var y = options.y || 50; 10 | var z = options.z || -25; 11 | var rotated = options.rotated || false; 12 | var castShadow = options.castShadow || false; 13 | var receiveShadow = options.receiveShadow || true; 14 | 15 | 16 | 17 | var cube_geometry = new THREE.BoxGeometry( length, height, width ); 18 | var cube_mesh = new THREE.Mesh( cube_geometry ); 19 | cube_mesh.position.x = x; 20 | cube_mesh.position.y = y; 21 | cube_mesh.position.z = z; 22 | var cube_bsp = new ThreeBSP( cube_mesh ); 23 | 24 | var glass_geometry = new THREE.BoxGeometry( 50, 3, 50 ); 25 | var glass_mesh1 = new THREE.Mesh( glass_geometry ); 26 | 27 | glass_mesh1.position.x = x + 30; 28 | glass_mesh1.position.y = y; 29 | glass_mesh1.position.z = z + 25; 30 | var glass_bsp1 = new ThreeBSP( glass_mesh1 ); 31 | 32 | var glass_mesh2 = new THREE.Mesh( glass_geometry ); 33 | 34 | glass_mesh2.position.x = x - 70; 35 | glass_mesh2.position.y = y; 36 | glass_mesh2.position.z = z; 37 | var glass_bsp2 = new ThreeBSP( glass_mesh2 ); 38 | 39 | 40 | var subtract_bsp = cube_bsp.subtract( glass_bsp1 ).subtract( glass_bsp2 ); 41 | var ceiling = subtract_bsp.toMesh( new THREE.MeshLambertMaterial({ color:'white' }) ); 42 | ceiling.geometry.computeVertexNormals(); 43 | 44 | ceiling.matrixAutoUpdate = false; 45 | ceiling.updateMatrix(); 46 | 47 | // var glass_geometry = new THREE.BoxGeometry( 5, 3, 5 ); 48 | // var glass_mesh = new THREE.Mesh( glass_geometry ); 49 | // glass_mesh.position.x = x; 50 | // glass_mesh.position.y = y; 51 | // glass_mesh.position.z = z; 52 | // var glass_bsp = new ThreeBSP( glass_mesh ); 53 | 54 | // var subtract_bsp = cube_bsp.subtract( glass_bsp ); 55 | // var result = subtract_bsp.toMesh( new THREE.MeshLambertMaterial({ color:'white' }) ); 56 | // result.geometry.computeVertexNormals(); 57 | 58 | context.scene.add( ceiling ); 59 | 60 | } 61 | -------------------------------------------------------------------------------- /static/js/webRTCComponents/chatMain.js: -------------------------------------------------------------------------------- 1 | function chatInit(){ 2 | //on press enter in chat box, send chat message 3 | $('#chatInput').on("keypress", function(e) { 4 | if (e.keyCode === 13) { 5 | parseChatInput(); 6 | return false; // prevent the default behavior from happening 7 | } 8 | }); 9 | 10 | //or if they click submit button, also send chat message 11 | $('#chatInputButton').on("click", function(e) { 12 | parseChatInput(); 13 | return false; // prevent the button click from happening 14 | }); 15 | } 16 | 17 | function parseChatInput (){ 18 | //create a copy of chat message 19 | var message = $('#chatInput').val(); 20 | //before we delete it 21 | $('#chatInput').val(""); 22 | //and then emit event that webRTCMain will use to send data message 23 | playerEvents.emitEvent('sendChatMessage', [message]); 24 | } 25 | 26 | function startChatTyping () { 27 | //reenable controls 28 | // controls.enabled = false; 29 | inChatBox = true; 30 | $('#chatInput').focus(); 31 | } 32 | 33 | //send a chat message 34 | function sendChatMessage (message){ 35 | var webrtc = realFaces.webrtc.webrtc; 36 | webrtc.sendDirectlyToAll('realTalkClient','chatMessage', {message:message, username:webrtc.username}); 37 | addChatMessage(null, message, 'You'); 38 | }; 39 | 40 | //receive a chat message from a peer 41 | function addChatMessage (peerID, msgText, msgOwner){ 42 | //construct new chat el 43 | var chatMessage = $('
').html(msgOwner+': '+msgText).attr('id','chatMessage'); 44 | //add new chat message to the chatBox 45 | $('#chatInput').before(chatMessage); 46 | //attach a timer, after 10 seconds, fade it out slowly, then remove it from the DOM 47 | setTimeout(function(){ 48 | chatMessage.hide('slow', function(){ chatMessage.remove(); }); 49 | },20000); 50 | } 51 | 52 | chatInit(); 53 | 54 | //check if user is typing every 50ms 55 | //if they are typing, disable controls 56 | setInterval(function(){ 57 | controls.enabled = ( $('#chatInput').is( ":focus" ) ) ? false : true; 58 | }, 50); 59 | 60 | playerEvents.addListener('start_chat_typing', startChatTyping); 61 | playerEvents.addListener('sendChatMessage', sendChatMessage); 62 | playerEvents.addListener('addChatMessage', addChatMessage); -------------------------------------------------------------------------------- /views/index.jade: -------------------------------------------------------------------------------- 1 | extends layout 2 | 3 | block content 4 | .container 5 | .header 6 | ul.nav.nav-pills.pull-right 7 | li.active 8 | a(href='./') Home 9 | li 10 | a(href='./About') About 11 | li 12 | a(href='./Problems') Problems 13 | li 14 | a(href='./Contact') Contact 15 | h3.text-muted Real Faces 16 | .jumbotron 17 | h2 Welcome to Real Faces 18 | p.lead 19 | | Real Faces is a 3D video conferencing app that aims to make internet communication as real as life. To start using it, select one of the scenes below... 20 | //- p 21 | //- a.btn.btn-lg.btn-success(href='#', role='button') Sign up today 22 | .row.marketing 23 | .col-lg-6 24 | h3 The Art Gallery 25 | p 26 | | Enter the art gallery, decorated with the technologies used to build Real Faces. This scene is the most complex and older computers may have difficulty running it. 27 | a.btn.btn-lg.btn-success(id= 'ArtGalleryPrivate', role='button') Create Private Room 28 | a.btn.btn-lg.btn-default(href='./ArtGallery', role='button') Enter Public Room 29 | h3 Union Square 30 | p 31 | | Have a conversation in San Francisco's Union Square. 32 | a.btn.btn-lg.btn-success(id= 'UnionSquarePrivate', role='button') Create Private Room 33 | a.btn.btn-lg.btn-default(href='./UnionSquare', role='button') Enter Public Room 34 | .col-lg-6 35 | h3 Outdoors 36 | p 37 | | Chat with other users in a leafy park. 38 | a.btn.btn-lg.btn-success(id= 'OutdoorsPrivate', role='button') Create Private Room 39 | a.btn.btn-lg.btn-default(href='./Outdoors', role='button') Enter Public Room 40 | h3 More Scenes to come 41 | p 42 | | More scenes are being created. If you would like to contribute a scene, please make a pull request to our 43 | a(href='https://github.com/realTalkTeam/realTalk') GitHub repo! 44 | .footer 45 | p 46 | |

Built by 47 | a(href='https://github.com/davemun') Dave Mun 48 | | and 49 | a(href='https://github.com/roryc89') Rory Campbell 50 | 51 | -------------------------------------------------------------------------------- /PRESS-RELEASE.md: -------------------------------------------------------------------------------- 1 | ## Heading ## 2 | > The 3D video chat website. 3 | 4 | ## Sub-Heading ## 5 | > A 3D virtual reality application, where the characters have their user's actual face, streamed in real time. 6 | 7 | ## Summary ## 8 | > Real Talk aims to bridge the gap between the virtual and the real by allowing users to communicate in an online 3D world, whilst being able to share voice and facial expressions through real time video and audio connections. For the first time ever you can communicate through sound, sight and space! 9 | 10 | ## Problem ## 11 | > Ever found that group video conferences lack some of the body language cues used in real life? Have they had awkward pauses when people are unsure of whose turn it is to speak, or multiple people beginning to talk at the same time? Have you ever wished that there was an easier way for online groups, such as remote students, to get to know each other and have informal chats? By making bringing video conferences closer to real life communication, Real Talk helps to solve these problems. 12 | 13 | ## Solution ## 14 | > Real Talk helps to fix video conferencing's current lack of body language communication by providing avatars that allow for a greater range of expression. The direction an avatar is facing and and the direction of the eyes give indications of who the person is listening to and whose "turn" it is to talk. Proximity, position and gesture allow for a far fuller collection of non-verbal messages. Informal chats are far more natural as participants can move through a room, joining and leaving conversations in a manner similar to real life. 15 | 16 | ## Quote from You ## 17 | >"We wanted a way for the people interact with each other online and bring the fluidity and naturalness of real life conversation to the web" 18 | 19 | ## How to Get Started ## 20 | > Go to realtalk-chat.com, join an existing room or create a new one, and invite your friends to start the Real Talk conversation. Ready in minutes- no installations or downloads needed. 21 | 22 | ## Customer Quote ## 23 | > "This was so easy to use and was a great way to get to know the other remote students in my class. Invaluable during the first week of a cohort when you're just getting to know everyone and you're not an on site student!" 24 | 25 | ## Closing and Call to Action ## 26 | > Try it out! Go to realtalk-chat.com and invite your friends, colleagues and family! 27 | -------------------------------------------------------------------------------- /docs/FutureImprovements.md: -------------------------------------------------------------------------------- 1 | # Future Impovements 2 | 3 | This file contains a list of possible future features and improvements that could be implemented. They are roughly ordered by most difficult/time consuming first. 4 | 5 | ## Centralized WebRTC architecture 6 | 7 | At the moment, the app uses peer to peer media streaming. This is pretty much free to provide but it suffers from upload bandwidth restrictions as each user must stream their video/audio to each other user indiviually. A centralized architecture using some form of media server would greatly increase the number of users that could stream to each other. The node-webrtc may be able do this without requiring any other programming languages. 8 | 9 | ## 3D sound 10 | 11 | Add positonal and directional audio so that clients can hear what direciton another is. This is currently only possible in FireFox, due to peer media stream incompatibility with Web Audio API, so probably not worth the time. 12 | 13 | ## Cropping via facial detection 14 | 15 | Open source JavaScript facial detection packages are available (https://github.com/auduno/clmtrackr), which could be used to crop the parts of the video around the face. This video could then be rendered onto an otherwise invisible screen head and it would give the impression of a floating head above the body! 16 | 17 | ## Private rooms 18 | 19 | Add the ability to make private rooms that the user could invite people to by sharing a generated URL. 20 | 21 | ## Create user Accounts 22 | 23 | Either create user accounts or use a google/facebook/twitter sign in. 24 | 25 | ## Customisatble characters 26 | 27 | Have the characters be customisable. An option that automatched the color of top the person was wearing could also be implemented. 28 | 29 | ## Detection and notification of lacking requirements 30 | 31 | The app would detect if the users computer or internet were not suitable for the app and show them the appropriate message. 32 | 33 | ## Slighty outwards curve on face screens 34 | 35 | This would allow a degree of vibility to people look from the side on, more similar to real life. 36 | 37 | ## Remove the prompt from WebRTCMain 38 | 39 | The name request prompt is ugly and blocks the script. This should be changed for a more attractive, ansyncronous method of getting the user name. 40 | 41 | ## Fix the SimpleWebRTC prototype overwrite 42 | 43 | This is a confusing way to change the method that was used to fix a bug under time pressure but should be changed for a more established overwrite technique. 44 | 45 | -------------------------------------------------------------------------------- /static/js/vendor/stats.js: -------------------------------------------------------------------------------- 1 | // stats.js r8 - http://github.com/mrdoob/stats.js 2 | var Stats=function(){var h,a,n=0,o=0,i=Date.now(),u=i,p=i,l=0,q=1E3,r=0,e,j,f,b=[[16,16,48],[0,255,255]],m=0,s=1E3,t=0,d,k,g,c=[[16,48,16],[0,255,0]];h=document.createElement("div");h.style.cursor="pointer";h.style.width="80px";h.style.opacity="0.9";h.style.zIndex="10001";h.addEventListener("mousedown",function(a){a.preventDefault();n=(n+1)%2;n==0?(e.style.display="block",d.style.display="none"):(e.style.display="none",d.style.display="block")},!1);e=document.createElement("div");e.style.textAlign= 3 | "left";e.style.lineHeight="1.2em";e.style.backgroundColor="rgb("+Math.floor(b[0][0]/2)+","+Math.floor(b[0][1]/2)+","+Math.floor(b[0][2]/2)+")";e.style.padding="0 0 3px 3px";h.appendChild(e);j=document.createElement("div");j.style.fontFamily="Helvetica, Arial, sans-serif";j.style.fontSize="9px";j.style.color="rgb("+b[1][0]+","+b[1][1]+","+b[1][2]+")";j.style.fontWeight="bold";j.innerHTML="FPS";e.appendChild(j);f=document.createElement("div");f.style.position="relative";f.style.width="74px";f.style.height= 4 | "30px";f.style.backgroundColor="rgb("+b[1][0]+","+b[1][1]+","+b[1][2]+")";for(e.appendChild(f);f.children.length<74;)a=document.createElement("span"),a.style.width="1px",a.style.height="30px",a.style.cssFloat="left",a.style.backgroundColor="rgb("+b[0][0]+","+b[0][1]+","+b[0][2]+")",f.appendChild(a);d=document.createElement("div");d.style.textAlign="left";d.style.lineHeight="1.2em";d.style.backgroundColor="rgb("+Math.floor(c[0][0]/2)+","+Math.floor(c[0][1]/2)+","+Math.floor(c[0][2]/2)+")";d.style.padding= 5 | "0 0 3px 3px";d.style.display="none";h.appendChild(d);k=document.createElement("div");k.style.fontFamily="Helvetica, Arial, sans-serif";k.style.fontSize="9px";k.style.color="rgb("+c[1][0]+","+c[1][1]+","+c[1][2]+")";k.style.fontWeight="bold";k.innerHTML="MS";d.appendChild(k);g=document.createElement("div");g.style.position="relative";g.style.width="74px";g.style.height="30px";g.style.backgroundColor="rgb("+c[1][0]+","+c[1][1]+","+c[1][2]+")";for(d.appendChild(g);g.children.length<74;)a=document.createElement("span"), 6 | a.style.width="1px",a.style.height=Math.random()*30+"px",a.style.cssFloat="left",a.style.backgroundColor="rgb("+c[0][0]+","+c[0][1]+","+c[0][2]+")",g.appendChild(a);return{domElement:h,update:function(){i=Date.now();m=i-u;s=Math.min(s,m);t=Math.max(t,m);k.textContent=m+" MS ("+s+"-"+t+")";var a=Math.min(30,30-m/200*30);g.appendChild(g.firstChild).style.height=a+"px";u=i;o++;if(i>p+1E3)l=Math.round(o*1E3/(i-p)),q=Math.min(q,l),r=Math.max(r,l),j.textContent=l+" FPS ("+q+"-"+r+")",a=Math.min(30,30-l/ 7 | 100*30),f.appendChild(f.firstChild).style.height=a+"px",p=i,o=0}}}; 8 | 9 | -------------------------------------------------------------------------------- /static/js/3DComponents/yourPlayer.js: -------------------------------------------------------------------------------- 1 | var createYourPlayerScreen = function(){ 2 | 3 | var geometry = new THREE.BoxGeometry( 9, 9, 1 ); 4 | 5 | var plainMaterial = new THREE.MeshBasicMaterial( {color: 'lightgrey'} ); 6 | 7 | var materialArray = []; 8 | materialArray.push(plainMaterial); 9 | materialArray.push(plainMaterial); 10 | materialArray.push(plainMaterial); 11 | materialArray.push(plainMaterial); 12 | materialArray.push(plainMaterial); 13 | materialArray.push(new THREE.MeshBasicMaterial( { color:'white', map: THREE.ImageUtils.loadTexture( 'images/smiley.png' ) })); 14 | 15 | var material = new THREE.MeshFaceMaterial(materialArray); 16 | 17 | var playerScreen = new THREE.Mesh( geometry, material ); 18 | //playerScreen.castShadow = true; 19 | 20 | playerScreen.name = 'your-screen'; 21 | 22 | var body = new Avatar(); 23 | 24 | body.mesh.position.y = - 12; 25 | 26 | 27 | body.stopWalking(); 28 | 29 | //realFaces.THREE.duckWalkers[ID] = body; 30 | 31 | playerScreen.add(body.mesh); 32 | //playerScreen.position.y += 10; 33 | playerScreen.startWalking = function(){ 34 | body.startWalking(); 35 | }; 36 | 37 | playerScreen.stopWalking = function(){ 38 | body.stopWalking(); 39 | }; 40 | 41 | playerScreen.update = function(){ 42 | body.render(); 43 | }; 44 | 45 | 46 | playerScreen.addVideo = function(){ 47 | 48 | var video = document.getElementById('localVideo'); 49 | 50 | var videoTexture = new THREE.VideoTexture( video ); 51 | videoTexture.generateMipmaps = false; 52 | videoTexture.minFilter = THREE.LinearFilter; 53 | videoTexture.magFilter = THREE.LinearFilter; 54 | 55 | var materialArray = []; 56 | realFaces.THREE.scene = realFaces.THREE.scene || window.scene; 57 | var plainMaterial = new THREE.MeshBasicMaterial( { color: new THREE.Color('grey') } ); 58 | materialArray.push(plainMaterial); 59 | materialArray.push(plainMaterial); 60 | materialArray.push(plainMaterial); 61 | materialArray.push(plainMaterial); 62 | materialArray.push(plainMaterial); 63 | materialArray.push(new THREE.MeshBasicMaterial( { map: videoTexture })); 64 | var MovingCubeMat = new THREE.MeshFaceMaterial(materialArray); 65 | 66 | var cube = realFaces.THREE.scene.getObjectByName('your-screen'); 67 | cube.material = MovingCubeMat; 68 | cube.material.needsUpdate = true; 69 | }; 70 | 71 | playerEvents.addListener('joined_room', playerScreen.addVideo); 72 | 73 | return playerScreen; 74 | 75 | // realFaces.THREE.objects.push( playerScreen ); 76 | // realFaces.THREE.scene.add( playerScreen ); 77 | // realFaces.THREE.collidableMeshList.push(playerScreen); 78 | } 79 | -------------------------------------------------------------------------------- /views/pages/About.jade: -------------------------------------------------------------------------------- 1 | extends ./layout 2 | 3 | block content 4 | .container 5 | .header 6 | ul.nav.nav-pills.pull-right 7 | li 8 | a(href='./') Home 9 | li.active 10 | a(href='./About') About 11 | li 12 | a(href='./Problems') Problems 13 | li 14 | a(href='./Contact') Contact 15 | h3.text-muted Real Faces 16 | .jumbotron 17 | h2 About Us 18 | p.lead 19 | | Real Faces is an 20 | a(href='https://github.com/realTalkTeam/realTalk') open source project, 21 | | created by 22 | a(href='https://github.com/roryc89') Rory Campbell 23 | | and 24 | a(href='https://github.com/davemun') Dave Mun 25 | //- p 26 | //- a.btn.btn-lg.btn-success(href='#', role='button') Sign up today 27 | .row.marketing 28 | .col-lg-12 29 | p Real faces was built with two goals in mind. 30 | | To counter some of the difficulties we had found in large group video conferences 31 | | and to explore new possibilities in combining WebRTC and WebGL technologies. 32 | p 33 | | If you would like to contribute to the project, please make a pull request to our 34 | a(href='https://github.com/realTalkTeam/realTalk') GitHub repo. 35 | | Requests for new features 36 | | or bug fixes are also welcome. 37 | p 38 | | Many thanks to the various open source technologies that helped us to build this project 39 | | including 40 | a(href='https://simplewebrtc.com/') 41 | | SimpleWebRTC, 42 | a(href='http://www.scenevr.com/') SceneVR 43 | | for design inspiration and the floor tiling, 44 | | the stunning selection of skyboxes from 45 | a(href='http://www.humus.name/index.php?page=3D&&start=0') Humus, 46 | | deathcap's minecraft style 47 | a(href='https://github.com/deathcap/avatar') avatars, 48 | | and their 49 | a(href='https://github.com/flyswatter/voxel-walk') voxel-walk, 50 | | animation. 51 | | Also thanks to the great materials, tutorials and examples provided by 52 | a(href='http://mrdoob.com/') 53 | | MrDoob, 54 | a(href='https://github.com/stemkoski/') 55 | | Stemkoski 56 | | and 57 | a(href='http://jetienne.com/') 58 | | Jerome Etienne 59 | .footer 60 | p 61 | |

Built by 62 | a(href='https://github.com/davemun') Dave Mun 63 | | and 64 | a(href='https://github.com/roryc89') Rory Campbell 65 | 66 | -------------------------------------------------------------------------------- /static/js/socketMain.js: -------------------------------------------------------------------------------- 1 | var RealSocket = function (app) { 2 | console.log(location, location.pathname, location.search); 3 | this.socketInterval = 100; 4 | this.yourPlayerTranslation; 5 | this.lastRecordedPlayerTranslations = {}; 6 | var context = this; 7 | 8 | 9 | //YOUR PLAYER UPDATES TO SERVER 10 | this.yourPlayerTranslation = { 11 | position: {x:0, y:10, z:0}, 12 | rotation: {x:0, y:0} 13 | }; 14 | 15 | this.translated = false; 16 | 17 | //connect to server namespace 18 | this.socketio = io.connect('/translations'); 19 | 20 | this.socketio.emit('select_room', app.roomName); 21 | 22 | //set up event listeners from socket 23 | //OTHER PLAYER UPDATES FROM SERVER 24 | this.socketio.on('preexisting_clients', function(clientTranslations, yourID, thisRef){ 25 | //save your socketio ID 26 | context.yourID = yourID; 27 | //draw pre-existing clients when you login 28 | for (var id in clientTranslations){ 29 | if (clientTranslations.hasOwnProperty(id) && clientTranslations[id] && id !== yourID){ 30 | context.lastRecordedPlayerTranslations[id] = clientTranslations[id]; 31 | playerEvents.emit('new_player', id, clientTranslations[id]); 32 | playerEvents.emit('teleport_other_player', id, clientTranslations[id]); 33 | } 34 | } 35 | //initialize webRTC connection after drawing other clients 36 | playerEvents.emit('start_webRTC', yourID, app); 37 | }); 38 | 39 | this.socketio.on('new_client', function(clientID){ 40 | console.log('new player socket', clientID) 41 | context.lastRecordedPlayerTranslations[clientID] = {position:{x:0, y:10, z:10}, rotation:{x:0,y:0}}; 42 | //otherPlayerUpdates will hear this and create a new player 43 | playerEvents.emit('new_player', [clientID]); 44 | }); 45 | 46 | this.socketio.on('client_disconnected', function(clientID){ 47 | delete context.lastRecordedPlayerTranslations[clientID]; 48 | playerEvents.emit('remove_player', [clientID]); 49 | }); 50 | 51 | this.socketio.on('move_other_player', function(data){ 52 | context.lastRecordedPlayerTranslations[data.clientID] = data.translation; 53 | //otherPlayerUpdates will hear this and move the respective player 54 | playerEvents.emit('move_other_player', data.clientID, data.translation); 55 | }); 56 | 57 | //check for movement to broadcast to server at regular intervals 58 | var thisPointer = this; 59 | setInterval(function(){ 60 | if (thisPointer.translated){ 61 | thisPointer.socketio.emit('translate', thisPointer.yourPlayerTranslation); 62 | thisPointer.translated = false; 63 | } 64 | }, thisPointer.socketInterval); 65 | }; 66 | 67 | RealSocket.prototype.storePlayerTranslation = function(translation){ 68 | realFaces.socket.yourPlayerTranslation = translation; 69 | realFaces.socket.translated = true; 70 | }; 71 | 72 | -------------------------------------------------------------------------------- /views/pages/OutdoorsLayout.jade: -------------------------------------------------------------------------------- 1 | !!! 5 2 | //if lt IE 7 3 | html.no-js.ie6.oldie(lang='en') 4 | //if IE 7 5 | html.no-js.ie7.oldie(lang='en') 6 | //if IE 8 7 | html.no-js.ie8.oldie(lang='en') 8 | //[if gt IE 8]><\\/script>') 32 | //-script(defer, src='js/plugins.js') 33 | script(defer, src='/js/vendor/EventEmitter.js') 34 | script(defer, src='/js/vendor/tween.min.js') 35 | script(defer, src='/js/vendor/stats.js') 36 | script(defer, src='/js/vendor/Mirror.js') 37 | script(defer, src='/js/vendor/ThreeCSG.js') 38 | script(defer, src='/js/vendor/webGLDebug.js') 39 | script(defer, src='/js/3DComponents/avatars.js') 40 | script(defer, src='/js/3DComponents/threeMain.js') 41 | script(defer, src='/js/3DComponents/skyboxes.js') 42 | script(defer, src='/js/3DComponents/walls.js') 43 | script(defer, src='/js/3DComponents/ceiling.js') 44 | script(defer, src='/js/3DComponents/collisionDetection.js') 45 | script(defer, src='/js/vendor/PointerLockControls.js') 46 | script(defer, src='/socket.io/socket.io.js') 47 | script(defer, src='/js/audioHelpers/volumeDistanceModifier.js') 48 | script(defer, src='//simplewebrtc.com/latest.js') 49 | script(defer, src='/js/webRTCComponents/simpleWebRTCOverwrites.js') 50 | script(defer, src='/js/webRTCComponents/webRTCMain.js') 51 | script(defer, src='/js/webRTCComponents/chatMain.js') 52 | script(defer, src='/js/socketMain.js') 53 | script(defer, src='/js/3DComponents/otherPlayerUpdates.js') 54 | script(defer, src='/js/main.js') 55 | script(defer, src='/js/bootloaderOutdoors.js') 56 | //if lt IE 7 57 | script(defer, src='//ajax.googleapis.com/ajax/libs/chrome-frame/1.0.3/CFInstall.min.js') 58 | script(defer) 59 | window.attachEvent('onload',function(){CFInstall.check({mode:'overlay'})}) 60 | -------------------------------------------------------------------------------- /views/pages/UnionSquareLayout.jade: -------------------------------------------------------------------------------- 1 | !!! 5 2 | //if lt IE 7 3 | html.no-js.ie6.oldie(lang='en') 4 | //if IE 7 5 | html.no-js.ie7.oldie(lang='en') 6 | //if IE 8 7 | html.no-js.ie8.oldie(lang='en') 8 | //[if gt IE 8]><\\/script>') 32 | //-script(defer, src='js/plugins.js') 33 | script(defer, src='/js/vendor/EventEmitter.js') 34 | script(defer, src='/js/vendor/tween.min.js') 35 | script(defer, src='/js/vendor/stats.js') 36 | script(defer, src='/js/vendor/Mirror.js') 37 | script(defer, src='/js/vendor/ThreeCSG.js') 38 | script(defer, src='/js/vendor/webGLDebug.js') 39 | script(defer, src='/js/3DComponents/avatars.js') 40 | script(defer, src='/js/3DComponents/threeMain.js') 41 | script(defer, src='/js/3DComponents/skyboxes.js') 42 | script(defer, src='/js/3DComponents/walls.js') 43 | script(defer, src='/js/3DComponents/ceiling.js') 44 | script(defer, src='/js/3DComponents/collisionDetection.js') 45 | script(defer, src='/js/vendor/PointerLockControls.js') 46 | script(defer, src='/socket.io/socket.io.js') 47 | script(defer, src='/js/audioHelpers/volumeDistanceModifier.js') 48 | script(defer, src='//simplewebrtc.com/latest.js') 49 | script(defer, src='/js/webRTCComponents/simpleWebRTCOverwrites.js') 50 | script(defer, src='/js/webRTCComponents/webRTCMain.js') 51 | script(defer, src='/js/webRTCComponents/chatMain.js') 52 | script(defer, src='/js/socketMain.js') 53 | script(defer, src='/js/3DComponents/otherPlayerUpdates.js') 54 | script(defer, src='/js/main.js') 55 | script(defer, src='/js/bootloaderUnionSquare.js') 56 | //if lt IE 7 57 | script(defer, src='//ajax.googleapis.com/ajax/libs/chrome-frame/1.0.3/CFInstall.min.js') 58 | script(defer) 59 | window.attachEvent('onload',function(){CFInstall.check({mode:'overlay'})}) 60 | -------------------------------------------------------------------------------- /views/pages/ArtGalleryLayout.jade: -------------------------------------------------------------------------------- 1 | !!! 5 2 | //if lt IE 7 3 | html.no-js.ie6.oldie(lang='en') 4 | //if IE 7 5 | html.no-js.ie7.oldie(lang='en') 6 | //if IE 8 7 | html.no-js.ie8.oldie(lang='en') 8 | //[if gt IE 8]><\\/script>') 32 | //-script(defer, src='js/plugins.js') 33 | script(defer, src='/js/vendor/EventEmitter.js') 34 | script(defer, src='/js/vendor/tween.min.js') 35 | script(defer, src='/js/vendor/stats.js') 36 | script(defer, src='/js/vendor/Mirror.js') 37 | script(defer, src='/js/vendor/ThreeCSG.js') 38 | script(defer, src='/js/vendor/webGLDebug.js') 39 | script(defer, src='/js/3DComponents/avatars.js') 40 | script(defer, src='/js/3DComponents/threeMain.js') 41 | script(defer, src='/js/3DComponents/skyboxes.js') 42 | script(defer, src='/js/3DComponents/walls.js') 43 | script(defer, src='/js/3DComponents/ceiling.js') 44 | script(defer, src='/js/3DComponents/collisionDetection.js') 45 | script(defer, src='/js/3DComponents/yourPlayer.js') 46 | script(defer, src='/js/3DComponents/galleryPictures.js') 47 | script(defer, src='/js/vendor/PointerLockControls.js') 48 | script(defer, src='/socket.io/socket.io.js') 49 | script(defer, src='/js/audioHelpers/volumeDistanceModifier.js') 50 | script(defer, src='//simplewebrtc.com/latest.js') 51 | script(defer, src='/js/webRTCComponents/simpleWebRTCOverwrites.js') 52 | script(defer, src='/js/webRTCComponents/webRTCMain.js') 53 | script(defer, src='/js/webRTCComponents/chatMain.js') 54 | script(defer, src='/js/socketMain.js') 55 | script(defer, src='/js/3DComponents/otherPlayerUpdates.js') 56 | script(defer, src='/js/main.js') 57 | script(defer, src='/js/bootloaderArtGallery.js') 58 | //if lt IE 7 59 | script(defer, src='//ajax.googleapis.com/ajax/libs/chrome-frame/1.0.3/CFInstall.min.js') 60 | script(defer) 61 | window.attachEvent('onload',function(){CFInstall.check({mode:'overlay'})}) 62 | -------------------------------------------------------------------------------- /static/css/style.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | width: 100%; 3 | height: 100%; 4 | } 5 | 6 | body { 7 | background-color: #ffffff; 8 | margin: 0; 9 | overflow: hidden; 10 | font-family: 'Lato', sans-serif; 11 | } 12 | 13 | #roomURL{ 14 | border: 2px solid; 15 | border-color: white; 16 | border-radius: 10px; 17 | background: no-repeat scroll 0% 0% transparent; 18 | background-color: #D3D3D3; 19 | width: 40%; 20 | overflow: auto; 21 | height: 40px; 22 | /*min-height: 10%;*/ 23 | position: float; 24 | bottom: 5%; 25 | opacity: 0.75; 26 | float: left; 27 | } 28 | 29 | /*body > #realFacesCanvas{ 30 | z-index: -100 !important; 31 | }*/ 32 | 33 | #blocker { 34 | /*z-index: 0;*/ 35 | 36 | position: absolute; 37 | 38 | width: 100%; 39 | height: 120%; 40 | 41 | background-color: rgba(0,0,0,0.7); 42 | 43 | } 44 | 45 | #controls { 46 | 47 | white-space: pre; 48 | 49 | } 50 | 51 | #instructions { 52 | 53 | width: 100%; 54 | height: 100%; 55 | 56 | display: -webkit-box; 57 | display: -moz-box; 58 | display: box; 59 | 60 | -webkit-box-orient: horizontal; 61 | -moz-box-orient: horizontal; 62 | box-orient: horizontal; 63 | 64 | -webkit-box-pack: center; 65 | -moz-box-pack: center; 66 | box-pack: center; 67 | 68 | -webkit-box-align: center; 69 | -moz-box-align: center; 70 | box-align: center; 71 | 72 | color: #ffffff; 73 | text-align: center; 74 | 75 | cursor: pointer; 76 | 77 | } 78 | 79 | #localVideo, #remotesVideos > video { 80 | height: 50px; 81 | width: 50px; 82 | visibility: hidden; 83 | } 84 | 85 | #webcamWarning { 86 | visibility: hidden; 87 | font-size: 20px; 88 | color: red; 89 | } 90 | 91 | #chatBox{ 92 | border: 2px solid; 93 | border-color: white; 94 | border-radius: 10px; 95 | background: no-repeat scroll 0% 0% transparent; 96 | background-color: #D3D3D3; 97 | width: 40%; 98 | overflow: auto; 99 | min-height: 10%; 100 | position: absolute; 101 | bottom: 5%; 102 | opacity: 0.75; 103 | } 104 | 105 | #chatBox:hover{ 106 | opacity: 1.0; 107 | } 108 | 109 | #chatMessage { 110 | border: 2px solid; 111 | border-color: white; 112 | border-radius: 10px; 113 | background: no-repeat scroll 0% 0% transparent; 114 | background-color: #A9A9A9; 115 | height: 2%; 116 | text-align: left; 117 | margin-top:2px; 118 | padding: 2px; 119 | margin-left: auto; 120 | margin-right: auto; 121 | width: 95%; 122 | position: relative; 123 | } 124 | 125 | #chatInput { 126 | border: 2px solid; 127 | border-color: white; 128 | border-radius: 2px; 129 | background: no-repeat scroll 0% 0% transparent; 130 | background-color: #A9A9A9; 131 | width: 80%; 132 | height: 20px; 133 | position: relative; 134 | margin-top: 5%; 135 | margin-left: 2%; 136 | float: left; 137 | } 138 | 139 | #chatInputButton { 140 | border: 2px solid; 141 | border-color: white; 142 | border-radius: 5px; 143 | background: no-repeat scroll 0% 0% transparent; 144 | background-color: #A9A9A9; 145 | width: 10%; 146 | height: 25px; 147 | position: relative; 148 | margin-right: 2%; 149 | margin-top: 5%; 150 | float: right; 151 | } 152 | 153 | -------------------------------------------------------------------------------- /static/js/main.js: -------------------------------------------------------------------------------- 1 | //construct main app object 2 | //can load its own webRTC dependency 3 | var RealFaces = function(sceneName){ 4 | //will load webRTC deps on event, set to be called when THREE.js scene is done rendering 5 | this.roomName = location.pathname; 6 | 7 | 8 | document.getElementById('roomURL').innerHTML = "realfaces.org" + location.pathname + "
Share this URL with your friends so they can join your room!" 9 | 10 | 11 | playerEvents.addListener('start_webRTC', this.initWebRTC); 12 | 13 | //construct THREE.js renderer 14 | if(sceneName === 'ArtGallery'){ 15 | this.THREE = new RealTHREE(-150, 100, -100, 50, true); 16 | }else{ 17 | this.THREE = new RealTHREE(); 18 | } 19 | 20 | //activate pointer lock 21 | this.THREE.pointerLock(); 22 | 23 | //construct scene based on url 24 | this.THREE.createScene(sceneName); 25 | //e.g. 26 | //realFaces.THREE.createSceneOutdoor(); 27 | //realFaces.THREE.createSceneUnionSquare(); 28 | 29 | //start animations 30 | this.THREE.animate(this.THREE); 31 | 32 | this.socket = new RealSocket(this); 33 | 34 | playerEvents.addListener('new_player', this.socket.createPlayerScreen); 35 | 36 | playerEvents.addListener('remove_player', this.socket.removePlayer); 37 | 38 | playerEvents.addListener('teleport_other_player', this.socket.teleportPlayer); 39 | 40 | playerEvents.addListener('move_other_player', this.socket.movePlayer); 41 | 42 | playerEvents.addListener('player_movement', this.socket.storePlayerTranslation); 43 | 44 | //after event listeners are set, tell server to send back self data 45 | this.socket.socketio.emit('player_join'); 46 | }; 47 | 48 | RealFaces.prototype.initWebRTC = function(clientID, context){ 49 | context.webrtc = new RealWebRTC(clientID); 50 | }; 51 | 52 | // var realFaces = { 53 | // 'initWebRTC': function(clientID) { 54 | // realFaces.webrtc = new RealWebRTC(clientID) 55 | // } 56 | // }; 57 | 58 | // //will load webRTC deps on event, set to be called when THREE.js scene is done rendering 59 | // playerEvents.addListener('start_webRTC', realFaces.initWebRTC); 60 | 61 | // //construct THREE.js renderer 62 | // realFaces.THREE = new RealTHREE(); 63 | 64 | // //activate pointer lock 65 | // realFaces.THREE.pointerLock(); 66 | 67 | // //construct scene based on url 68 | // //eg 69 | // realFaces.THREE.createSceneArtGallery(); 70 | // //realFaces.THREE.createSceneOutdoor(); 71 | // //realFaces.THREE.createSceneUnionSquare(); 72 | 73 | // //start animations 74 | // realFaces.THREE.animate(); 75 | 76 | //on document ready, start listening for socket events 77 | // $(document).ready(function() { 78 | // realFaces.socket = new RealSocket(); 79 | 80 | // playerEvents.addListener('new_player', realFaces.socket.createPlayerScreen); 81 | 82 | // playerEvents.addListener('remove_player', realFaces.socket.removePlayer); 83 | 84 | // playerEvents.addListener('teleport_other_player', realFaces.socket.teleportPlayer); 85 | 86 | // playerEvents.addListener('move_other_player', realFaces.socket.movePlayer); 87 | 88 | // playerEvents.addListener('player_movement', realFaces.socket.storePlayerTranslation); 89 | 90 | // //after event listeners are set, tell server to send back self data 91 | // realFaces.socket.socketio.emit('player_join'); 92 | // }); 93 | 94 | -------------------------------------------------------------------------------- /static/js/3DComponents/collisionDetection.js: -------------------------------------------------------------------------------- 1 | RealTHREE.prototype.findOtherPlayerCollision = function(positionX, positionZ, buffer){ 2 | var buffer = buffer || 1; 3 | var playerSpacing = 9 * buffer; 4 | 5 | for (var ID in realFaces.socket.lastRecordedPlayerTranslations){ 6 | if (realFaces.socket.lastRecordedPlayerTranslations.hasOwnProperty(ID) && ID !== realFaces.socket.socketio.yourID){ 7 | var otherPlayerPosition = realFaces.socket.lastRecordedPlayerTranslations[ID].position; 8 | 9 | var distanceX = Math.abs(positionX - otherPlayerPosition.x); 10 | var distanceZ = Math.abs(positionZ - otherPlayerPosition.z); 11 | 12 | //calculate if other player is within spacing 13 | if (Math.sqrt((distanceX * distanceX) + (distanceZ * distanceZ)) < playerSpacing){ 14 | return {x : otherPlayerPosition.x, z:otherPlayerPosition.z}; 15 | } 16 | } 17 | } 18 | 19 | return false; 20 | }; 21 | 22 | RealTHREE.prototype.findCollisionZoneEdge = function(otherPlayer, yourPlayer, playerSpacing){ 23 | var buffer = 1.01; 24 | var playerSpacing = playerSpacing || 9; 25 | var radius = playerSpacing * buffer; 26 | 27 | //if you are exactly on top of the other player, get a bump in a random direction 28 | //so collision detection doesnt divide by zero (the diff in coords) 29 | //else, do normal collision bouncing 30 | if(otherPlayer.x === yourPlayer.x && otherPlayer.z === yourPlayer.z){ 31 | //basically flip a coin with 0 or 1 32 | //and then add a flat value to a random axis 33 | if( Math.floor(Math.random()*2) === 0){ 34 | yourPlayer.x += 1; 35 | }else{ 36 | yourPlayer.z += 1; 37 | } 38 | } 39 | 40 | var denominator = Math.sqrt(Math.pow((yourPlayer.x - otherPlayer.x), 2) + Math.pow((yourPlayer.z - otherPlayer.z), 2)); 41 | 42 | var edgeX = otherPlayer.x + (radius * ((yourPlayer.x - otherPlayer.x)/denominator)); 43 | var edgeZ = otherPlayer.z + (radius * ((yourPlayer.z - otherPlayer.z)/denominator)); 44 | 45 | return [edgeX, edgeZ]; 46 | }; 47 | 48 | RealTHREE.prototype.isWallCollision = function(x,z, wallList){ 49 | 50 | var walls = wallList; 51 | 52 | for (var i = 0, len = walls.length; i < len; i++){ 53 | 54 | var wall = walls[i]; 55 | 56 | if(!wall.rotated){ 57 | if (wall.position.x - (wall.length/2) -1 < x && x < wall.position.x + (wall.length/2) + 1){ 58 | if (wall.position.z - 5 < z && z <= wall.position.z){ 59 | 60 | return [x, wall.position.z - 5.01]; 61 | }else if(wall.position.z <= z && z < wall.position.z + 5){ 62 | return [x, wall.position.z + 5.01]; 63 | } 64 | } 65 | }else{ 66 | if (wall.position.z - (wall.length/2) - 1 < z && z < wall.position.z + (wall.length/2) + 1){ 67 | if (wall.position.x - 5 < x && x <= wall.position.x){ 68 | return [wall.position.x - 5.01, z]; 69 | }else if(wall.position.x <= x && x < wall.position.x + 5){ 70 | return [wall.position.x + 5.01, z]; 71 | } 72 | } 73 | } 74 | } 75 | 76 | return false; 77 | 78 | }; 79 | 80 | RealTHREE.prototype.isOutsideBoundary = function(x,z, xMax, xMin, zMax, zMin){ 81 | var outsideBoundary = false, newX = x, newZ = z; 82 | 83 | var zMin = zMin || xMin; 84 | var zMax = zMax || xMax; 85 | 86 | 87 | if (x > xMax){ 88 | outsideBoundary = true; 89 | newX = xMax * 0.9999; 90 | }else if (x < xMin){ 91 | outsideBoundary = true; 92 | newX = xMin * 1.0001; 93 | } 94 | 95 | if (z > zMax){ 96 | outsideBoundary = true; 97 | newZ = zMax * 0.9999; 98 | }else if (z < zMin){ 99 | outsideBoundary = true; 100 | newZ = zMin * 1.0001; 101 | } 102 | 103 | if (!outsideBoundary){ 104 | return false; 105 | }else{ 106 | return [newX, newZ]; 107 | } 108 | 109 | }; 110 | -------------------------------------------------------------------------------- /static/js/3DComponents/otherPlayerUpdates.js: -------------------------------------------------------------------------------- 1 | 2 | RealSocket.prototype.createPlayerScreen = function(ID, createTranslation){ 3 | var geometry = new THREE.BoxGeometry( 9, 9, 1 ); 4 | 5 | var plainMaterial = new THREE.MeshBasicMaterial( {color: 'lightgrey'} ); 6 | 7 | var materialArray = []; 8 | materialArray.push(plainMaterial); 9 | materialArray.push(plainMaterial); 10 | materialArray.push(plainMaterial); 11 | materialArray.push(plainMaterial); 12 | materialArray.push(plainMaterial); 13 | materialArray.push(new THREE.MeshBasicMaterial( { color:'white', map: THREE.ImageUtils.loadTexture( 'images/smiley.png' ) })); 14 | 15 | var material = new THREE.MeshFaceMaterial(materialArray); 16 | 17 | var playerScreen = new THREE.Mesh( geometry, material ); 18 | //playerScreen.castShadow = true; 19 | 20 | playerScreen.name = 'player-' + ID; 21 | 22 | body = new Avatar(THREE); 23 | 24 | body.mesh.position.y = -realFaces.THREE.sceneVars.playerStartHeight; 25 | 26 | 27 | body.stopWalking(); 28 | 29 | realFaces.THREE.duckWalkers[ID] = body; 30 | 31 | playerScreen.add(body.mesh); 32 | playerScreen.position.y += 12; 33 | 34 | 35 | realFaces.THREE.objects.push( playerScreen ); 36 | realFaces.THREE.scene.add( playerScreen ); 37 | realFaces.THREE.collidableMeshList.push(playerScreen); 38 | 39 | }; 40 | 41 | 42 | RealSocket.prototype.removePlayer = function(ID){ 43 | var player = realFaces.THREE.scene.getObjectByName('player-'+ID); 44 | realFaces.THREE.scene.remove(player); 45 | var remotesContainer = document.getElementById('remotesVideos'); 46 | var remoteVideo = document.getElementById(ID); 47 | if (remoteVideo) 48 | remotesContainer.removeChild(remoteVideo); 49 | }; 50 | 51 | RealSocket.prototype.teleportPlayer = function(ID, translation){ 52 | if(ID === realFaces.yourID){ 53 | return; 54 | } 55 | 56 | var player = realFaces.THREE.scene.getObjectByName('player-'+ID); 57 | 58 | player.position.x = translation.position.x; 59 | player.position.y = translation.position.y; 60 | player.position.z = translation.position.z; 61 | 62 | // TODO: Euler Angles must be applied to other players before x rotation can be synced 63 | // player.rotation.x = translation.rotation.x; 64 | 65 | player.rotation.y = translation.rotation.y; 66 | 67 | }; 68 | 69 | RealSocket.prototype.movePlayer = function(ID, newTranslation){ 70 | var player = realFaces.THREE.scene.getObjectByName('player-'+ID); 71 | var body = realFaces.THREE.duckWalkers[ID]; 72 | 73 | if (body.isWalking() && Math.abs(player.position.x - newTranslation.position.x) < 1 && Math.abs(player.position.y - newTranslation.position.y) < 1 && Math.abs(player.position.z - newTranslation.position.z) < 1){ 74 | body.stopWalking(); 75 | }else if(!body.walking && ( Math.abs(player.position.x - newTranslation.position.x) > 1|| Math.abs(player.position.y - newTranslation.position.y) > 1 || Math.abs(player.position.z - newTranslation.position.z) > 1 ) ){ 76 | body.startWalking(); 77 | } 78 | 79 | if(!player.tweenedPosition){ 80 | player.tweenedPosition = { 81 | x : player.position.x, 82 | y : player.position.y, 83 | z : player.position.z 84 | }; 85 | player.tweenedRotation = { 86 | y : player.rotation.y 87 | }; 88 | } 89 | 90 | player.positionTween = new TWEEN.Tween(player.tweenedPosition).to(newTranslation.position, realFaces.socket.socketInterval); 91 | player.rotationTween = new TWEEN.Tween(player.tweenedRotation).to(newTranslation.rotation, realFaces.socket.socketInterval); 92 | 93 | player.positionTween.onUpdate(function(){ 94 | player.position.x = player.tweenedPosition.x; 95 | player.position.y = player.tweenedPosition.y; 96 | player.position.z = player.tweenedPosition.z; 97 | }); 98 | 99 | player.rotationTween.onUpdate(function(){ 100 | player.rotation.y = player.tweenedRotation.y; 101 | }); 102 | 103 | player.positionTween.start(); 104 | player.rotationTween.start(); 105 | }; 106 | 107 | 108 | -------------------------------------------------------------------------------- /sockets/signalmaster.js: -------------------------------------------------------------------------------- 1 | var stunServers = require('config.json')('./config/stunservers.json'); 2 | var turnServers = require('config.json')('./config/turnservers.json'); 3 | 4 | module.exports = function(io){ 5 | //create socket.io client signalmaster namespacing 6 | var signal = io.of('/signalmaster'); 7 | 8 | /*global console*/ 9 | var uuid = require('node-uuid'), 10 | crypto = require('crypto'); 11 | 12 | function describeRoom(name) { 13 | //var clients = io.of('/chat').clients('room'); 14 | var clients = signal.clients(name); 15 | var result = { 16 | clients: {} 17 | }; 18 | clients.forEach(function (client) { 19 | result.clients[client.id] = client.resources; 20 | }); 21 | return result; 22 | } 23 | 24 | function safeCb(cb) { 25 | if (typeof cb === 'function') { 26 | return cb; 27 | } else { 28 | return function () {}; 29 | } 30 | } 31 | 32 | signal.on('connection', function(client){ 33 | client.resources = { 34 | screen: false, 35 | video: true, 36 | audio: false 37 | }; 38 | 39 | // pass a message to another id 40 | client.on('message', function (details) { 41 | if (!details) return; 42 | 43 | var otherClient = signal.sockets[details.to]; 44 | if (!otherClient) return; 45 | 46 | details.from = client.id; 47 | otherClient.emit('message', details); 48 | }); 49 | 50 | client.on('shareScreen', function () { 51 | client.resources.screen = true; 52 | }); 53 | 54 | client.on('unshareScreen', function (type) { 55 | client.resources.screen = false; 56 | removeFeed('screen'); 57 | }); 58 | 59 | client.on('join', join); 60 | 61 | function removeFeed(type) { 62 | if (client.room) { 63 | io.sockets.in(client.room).emit('remove', { 64 | id: client.id, 65 | type: type 66 | }); 67 | if (!type) { 68 | client.leave(client.room); 69 | client.room = undefined; 70 | } 71 | } 72 | } 73 | 74 | function join(name, cb) { 75 | // sanity check 76 | if (typeof name !== 'string') return; 77 | // leave any existing rooms 78 | removeFeed(); 79 | safeCb(cb)(null, describeRoom(name)); 80 | client.join(name); 81 | client.room = name; 82 | } 83 | 84 | // we don't want to pass "leave" directly because the 85 | // event type string of "socket end" gets passed too. 86 | client.on('disconnect', function () { 87 | removeFeed(); 88 | }); 89 | client.on('leave', function () { 90 | removeFeed(); 91 | }); 92 | 93 | client.on('create', function (name, cb) { 94 | if (arguments.length == 2) { 95 | cb = (typeof cb == 'function') ? cb : function () {}; 96 | name = name || uuid(); 97 | } else { 98 | cb = name; 99 | name = uuid(); 100 | } 101 | // check if exists 102 | if (signal.sockets.clients(name).length) { 103 | safeCb(cb)('taken'); 104 | } else { 105 | join(name); 106 | safeCb(cb)(null, name); 107 | } 108 | }); 109 | 110 | 111 | console.log(JSON.stringify(stunServers.servers)); 112 | console.log('=========================================') 113 | var servers = { 114 | "stunservers" : stunServers.servers, 115 | "turnservers" : 116 | [ 117 | /* 118 | this is an example of how you can generate credentials. generally we dont need to. 119 | { 120 | "url": "turn:192.158.29.39:3478?transport=udp", 121 | "secret": "turnserversharedsecret" 122 | "expiry": 86400 123 | } 124 | */ 125 | ] 126 | }; 127 | 128 | //load pre-generated credentials 129 | var credentials = turnServers.servers; 130 | 131 | //here you can generate credentials from secret 132 | //e.g. 133 | //credentials.push(generateCredentialObj()); 134 | 135 | // tell client about stun and turn servers and generate nonces 136 | client.emit('stunservers', servers.stunservers || []); 137 | client.emit('turnservers', credentials); 138 | }); 139 | }; -------------------------------------------------------------------------------- /static/js/webRTCComponents/webRTCMain.js: -------------------------------------------------------------------------------- 1 | var RealWebRTC = function (clientID) { 2 | //create webRTC obj from library 3 | this.webrtc = new SimpleWebRTC({ 4 | // the signalmaster URL to implement handshakes 5 | url: '/signalmaster', 6 | // the id/element dom element that will hold "our" video 7 | localVideoEl: 'localVideo', 8 | // the id/element dom element that will hold remote videos 9 | remoteVideosEl: 'remotesVideos', 10 | // immediately ask for camera access 11 | autoRequestMedia: true 12 | }); 13 | 14 | //store clientID 15 | this.yourID = clientID; 16 | 17 | 18 | //////////////////////////// 19 | //create webRTC listeners// 20 | /////////////////////////// 21 | 22 | //listen for other clients joining webRTC room, render their video 23 | this.webrtc.on('channelMessage', function (peer, label, data) { 24 | if (data.type === 'setClientID') { 25 | peer.socketID = data.payload; 26 | updateCubeWithVideo(peer.id+'_video_incoming', data.payload); 27 | //add clientID to DOM video node 28 | document.getElementById(peer.id+'_video_incoming').setAttribute("id", data.payload); 29 | } else if (data.type === 'chatMessage'){ 30 | playerEvents.emit('addChatMessage', peer.id, data.payload.message, data.payload.username); 31 | } 32 | }); 33 | 34 | this.webrtc.on('videoAdded', function(video,peer){ 35 | videoAdd(video, peer, realFaces.webrtc.yourID); 36 | }); 37 | 38 | this.webrtc.on('readyToCall', function () { 39 | //ask for username 40 | this.username = prompt("Please enter your name", "Anonymous"); 41 | //variable that allows pointer lock 42 | this.webcam = true; 43 | // you can name it anything 44 | this.joinRoom(realFaces.roomName); 45 | 46 | playerEvents.emit('joined_room'); 47 | }); 48 | 49 | // //OVERWRITES VANILLA LIBRARY METHOD 50 | // // set volume on video tag for all peers takse a value between 0 and 1 51 | // SimpleWebRTC.prototype.setVolumeForAll = function (volume) { 52 | // this.webrtc.peers.forEach(function (peer) { 53 | // if (peer.videoEl) { 54 | // peer.videoEl.volume = volume; 55 | // } 56 | // }); 57 | // }; 58 | 59 | //set volume for all peers to 0 60 | 61 | window.webRTCMain = this; 62 | setInterval(function(){ 63 | window.webRTCMain.webrtc.setVolumeForAll(null, true); 64 | },500); 65 | 66 | this.speaking = false; 67 | 68 | this.webrtc.setVolumeForAll = function (harkVolume, dontChangeHarkVolume) { 69 | // var volume; 70 | // //console.log('setting volume', harkVolume, dontChangeHarkVolume); 71 | 72 | // // Strange semantics due to SimpleWebRTC workaround 73 | // // SimpleWebRTC lowers volume of others when user is talking 74 | // if (!dontChangeHarkVolume){ 75 | // if (harkVolume === 1){ 76 | // this.speaking = false; 77 | // volume = 1; 78 | // }else if(harkVolume === 0.25){ 79 | // this.speaking = true; 80 | // volume = 0.25; 81 | // } 82 | // }else if (this.speaking === false){ 83 | // volume = 1; 84 | // }else if (this.speaking === true){ 85 | // volume = 0.25; 86 | // } 87 | 88 | var peers = realFaces.webrtc.webrtc.webrtc.peers; 89 | 90 | peers.forEach(function (peer) { 91 | if (peer.socketID){ 92 | var vdm = volumeDistanceModifier(peer.socketID); 93 | //console.log('vdm', vdm, volume) 94 | if (vdm === 'does not exist'){ 95 | delete peers[peer]; 96 | }else{ 97 | var harkVolume = harkVolume || 1; 98 | peer.videoEl.volume = harkVolume * vdm * vdm; 99 | } 100 | } 101 | }); 102 | }; 103 | }; 104 | 105 | var updateCubeWithVideo = function (divID, clientID) { 106 | var video = document.getElementById(divID); 107 | 108 | var videoTexture = new THREE.VideoTexture( video ); 109 | videoTexture.generateMipmaps = false; 110 | videoTexture.minFilter = THREE.LinearFilter; 111 | videoTexture.magFilter = THREE.LinearFilter; 112 | 113 | var materialArray = []; 114 | realFaces.THREE.scene = realFaces.THREE.scene || window.scene; 115 | var plainMaterial = new THREE.MeshBasicMaterial( { color: new THREE.Color('grey') } ); 116 | materialArray.push(plainMaterial); 117 | materialArray.push(plainMaterial); 118 | materialArray.push(plainMaterial); 119 | materialArray.push(plainMaterial); 120 | materialArray.push(plainMaterial); 121 | materialArray.push(new THREE.MeshBasicMaterial( { map: videoTexture })); 122 | var MovingCubeMat = new THREE.MeshFaceMaterial(materialArray); 123 | 124 | var cube = realFaces.THREE.scene.getObjectByName('player-'+clientID); 125 | cube.material = MovingCubeMat; 126 | cube.material.needsUpdate = true; 127 | }; 128 | 129 | 130 | var videoAdd = function (video, peer, clientID) { 131 | // Now, open the dataChannel 132 | var dc = peer.getDataChannel('realTalkClient'); 133 | // Now send my name to all the peers 134 | // Add a small timeout so dataChannel has time to be ready 135 | setTimeout(function(){ 136 | realFaces.webrtc.webrtc.webrtc.sendDirectlyToAll('realTalkClient','setClientID', realFaces.socket.yourID); 137 | }, 3000); 138 | }; 139 | -------------------------------------------------------------------------------- /static/js/vendor/tween.min.js: -------------------------------------------------------------------------------- 1 | // tween.js v.0.15.0 https://github.com/sole/tween.js 2 | void 0===Date.now&&(Date.now=function(){return(new Date).valueOf()});var TWEEN=TWEEN||function(){var n=[];return{REVISION:"14",getAll:function(){return n},removeAll:function(){n=[]},add:function(t){n.push(t)},remove:function(t){var r=n.indexOf(t);-1!==r&&n.splice(r,1)},update:function(t){if(0===n.length)return!1;var r=0;for(t=void 0!==t?t:"undefined"!=typeof window&&void 0!==window.performance&&void 0!==window.performance.now?window.performance.now():Date.now();rn;n++)E[n].stop()},this.delay=function(n){return s=n,this},this.repeat=function(n){return e=n,this},this.yoyo=function(n){return a=n,this},this.easing=function(n){return l=n,this},this.interpolation=function(n){return p=n,this},this.chain=function(){return E=arguments,this},this.onStart=function(n){return d=n,this},this.onUpdate=function(n){return I=n,this},this.onComplete=function(n){return w=n,this},this.onStop=function(n){return M=n,this},this.update=function(n){var f;if(h>n)return!0;v===!1&&(null!==d&&d.call(t),v=!0);var M=(n-h)/o;M=M>1?1:M;var O=l(M);for(f in i){var m=r[f]||0,N=i[f];N instanceof Array?t[f]=p(N,O):("string"==typeof N&&(N=m+parseFloat(N,10)),"number"==typeof N&&(t[f]=m+(N-m)*O))}if(null!==I&&I.call(t,O),1==M){if(e>0){isFinite(e)&&e--;for(f in u){if("string"==typeof i[f]&&(u[f]=u[f]+parseFloat(i[f],10)),a){var T=u[f];u[f]=i[f],i[f]=T}r[f]=u[f]}return a&&(c=!c),h=n+s,!0}null!==w&&w.call(t);for(var g=0,W=E.length;W>g;g++)E[g].start(n);return!1}return!0}},TWEEN.Easing={Linear:{None:function(n){return n}},Quadratic:{In:function(n){return n*n},Out:function(n){return n*(2-n)},InOut:function(n){return(n*=2)<1?.5*n*n:-.5*(--n*(n-2)-1)}},Cubic:{In:function(n){return n*n*n},Out:function(n){return--n*n*n+1},InOut:function(n){return(n*=2)<1?.5*n*n*n:.5*((n-=2)*n*n+2)}},Quartic:{In:function(n){return n*n*n*n},Out:function(n){return 1- --n*n*n*n},InOut:function(n){return(n*=2)<1?.5*n*n*n*n:-.5*((n-=2)*n*n*n-2)}},Quintic:{In:function(n){return n*n*n*n*n},Out:function(n){return--n*n*n*n*n+1},InOut:function(n){return(n*=2)<1?.5*n*n*n*n*n:.5*((n-=2)*n*n*n*n+2)}},Sinusoidal:{In:function(n){return 1-Math.cos(n*Math.PI/2)},Out:function(n){return Math.sin(n*Math.PI/2)},InOut:function(n){return.5*(1-Math.cos(Math.PI*n))}},Exponential:{In:function(n){return 0===n?0:Math.pow(1024,n-1)},Out:function(n){return 1===n?1:1-Math.pow(2,-10*n)},InOut:function(n){return 0===n?0:1===n?1:(n*=2)<1?.5*Math.pow(1024,n-1):.5*(-Math.pow(2,-10*(n-1))+2)}},Circular:{In:function(n){return 1-Math.sqrt(1-n*n)},Out:function(n){return Math.sqrt(1- --n*n)},InOut:function(n){return(n*=2)<1?-.5*(Math.sqrt(1-n*n)-1):.5*(Math.sqrt(1-(n-=2)*n)+1)}},Elastic:{In:function(n){var t,r=.1,i=.4;return 0===n?0:1===n?1:(!r||1>r?(r=1,t=i/4):t=i*Math.asin(1/r)/(2*Math.PI),-(r*Math.pow(2,10*(n-=1))*Math.sin(2*(n-t)*Math.PI/i)))},Out:function(n){var t,r=.1,i=.4;return 0===n?0:1===n?1:(!r||1>r?(r=1,t=i/4):t=i*Math.asin(1/r)/(2*Math.PI),r*Math.pow(2,-10*n)*Math.sin(2*(n-t)*Math.PI/i)+1)},InOut:function(n){var t,r=.1,i=.4;return 0===n?0:1===n?1:(!r||1>r?(r=1,t=i/4):t=i*Math.asin(1/r)/(2*Math.PI),(n*=2)<1?-.5*r*Math.pow(2,10*(n-=1))*Math.sin(2*(n-t)*Math.PI/i):r*Math.pow(2,-10*(n-=1))*Math.sin(2*(n-t)*Math.PI/i)*.5+1)}},Back:{In:function(n){var t=1.70158;return n*n*((t+1)*n-t)},Out:function(n){var t=1.70158;return--n*n*((t+1)*n+t)+1},InOut:function(n){var t=2.5949095;return(n*=2)<1?.5*n*n*((t+1)*n-t):.5*((n-=2)*n*((t+1)*n+t)+2)}},Bounce:{In:function(n){return 1-TWEEN.Easing.Bounce.Out(1-n)},Out:function(n){return 1/2.75>n?7.5625*n*n:2/2.75>n?7.5625*(n-=1.5/2.75)*n+.75:2.5/2.75>n?7.5625*(n-=2.25/2.75)*n+.9375:7.5625*(n-=2.625/2.75)*n+.984375},InOut:function(n){return.5>n?.5*TWEEN.Easing.Bounce.In(2*n):.5*TWEEN.Easing.Bounce.Out(2*n-1)+.5}}},TWEEN.Interpolation={Linear:function(n,t){var r=n.length-1,i=r*t,u=Math.floor(i),o=TWEEN.Interpolation.Utils.Linear;return 0>t?o(n[0],n[1],i):t>1?o(n[r],n[r-1],r-i):o(n[u],n[u+1>r?r:u+1],i-u)},Bezier:function(n,t){var r,i=0,u=n.length-1,o=Math.pow,e=TWEEN.Interpolation.Utils.Bernstein;for(r=0;u>=r;r++)i+=o(1-t,u-r)*o(t,r)*n[r]*e(u,r);return i},CatmullRom:function(n,t){var r=n.length-1,i=r*t,u=Math.floor(i),o=TWEEN.Interpolation.Utils.CatmullRom;return n[0]===n[r]?(0>t&&(u=Math.floor(i=r*(1+t))),o(n[(u-1+r)%r],n[u],n[(u+1)%r],n[(u+2)%r],i-u)):0>t?n[0]-(o(n[0],n[0],n[1],n[1],-i)-n[0]):t>1?n[r]-(o(n[r],n[r],n[r-1],n[r-1],i-r)-n[r]):o(n[u?u-1:0],n[u],n[u+1>r?r:u+1],n[u+2>r?r:u+2],i-u)},Utils:{Linear:function(n,t,r){return(t-n)*r+n},Bernstein:function(n,t){var r=TWEEN.Interpolation.Utils.Factorial;return r(n)/r(t)/r(n-t)},Factorial:function(){var n=[1];return function(t){var r,i=1;if(n[t])return n[t];for(r=t;r>1;r--)i*=r;return n[t]=i}}(),CatmullRom:function(n,t,r,i,u){var o=.5*(r-n),e=.5*(i-t),a=u*u,f=u*a;return(2*t-2*r+o+e)*f+(-3*t+3*r-2*o-e)*a+o*u+t}}},"undefined"!=typeof module&&module.exports&&(module.exports=TWEEN); -------------------------------------------------------------------------------- /static/js/3DComponents/walls.js: -------------------------------------------------------------------------------- 1 | var createWall = function (options){ 2 | 3 | var height = options.height || 50; 4 | var width = options.width || 2.5; 5 | var length = options.length; 6 | var x = options.x; 7 | var y = options.y || 25; 8 | var z = options.z; 9 | var rotated = options.rotated || false; 10 | var castShadow = options.castShadow || false; 11 | var receiveShadow = options.receiveShadow || true; 12 | var texture = options.texture || new THREE.ImageUtils.loadTexture( 'images/Applestone.jpg' ); 13 | var material = options.material || new THREE.MeshLambertMaterial( {map:texture, side:THREE.DoubleSide} ); 14 | 15 | if(!options.window){ 16 | var wall = new THREE.Mesh( new THREE.BoxGeometry(length, height, width), material ); 17 | wall.position.set(x, y, z); 18 | if (rotated){ 19 | wall.rotation.y = Math.PI / 2; 20 | wall.rotated = true; 21 | } 22 | wall.castShadow = castShadow; 23 | wall.receiveShadow = receiveShadow; 24 | 25 | }else{ 26 | var cube_geometry = new THREE.BoxGeometry( length, height, width ); 27 | var cube_mesh = new THREE.Mesh( cube_geometry ); 28 | cube_mesh.position.x = x; 29 | cube_mesh.position.y = y; 30 | cube_mesh.position.z = z; 31 | var cube_bsp = new ThreeBSP( cube_mesh ); 32 | var glass_geometry = new THREE.BoxGeometry( 26, 26, (width + 0.1) ); 33 | var glass_mesh = new THREE.Mesh( glass_geometry ); 34 | glass_mesh.position.x = x; 35 | glass_mesh.position.y = 20; 36 | glass_mesh.position.z = z; 37 | var glass_bsp = new ThreeBSP( glass_mesh ); 38 | 39 | var subtract_bsp = cube_bsp.subtract( glass_bsp ); 40 | var wall = subtract_bsp.toMesh( new THREE.MeshLambertMaterial({ map:texture }) ); 41 | wall.geometry.computeVertexNormals(); 42 | if (rotated){ 43 | wall.rotation.y = Math.PI / 2; 44 | wall.rotated = true; 45 | } 46 | } 47 | 48 | wall.length = length; 49 | 50 | wall.matrixAutoUpdate = false; 51 | wall.updateMatrix(); 52 | 53 | options.context.wallList.push(wall); 54 | 55 | options.context.scene.add(wall); 56 | }; 57 | 58 | 59 | var createWindowFrame = function (options){ 60 | 61 | var height = options.height || 25; 62 | var width = options.width || 2.5; 63 | var length = options.length; 64 | var x = options.x; 65 | var y = options.y || 20; 66 | var z = options.z; 67 | var rotated = options.rotated || false; 68 | var castShadow = options.castShadow || false; 69 | var receiveShadow = options.receiveShadow || false; 70 | //var texture = options.texture || new THREE.ImageUtils.loadTexture( 'images/Applestone.jpg' ); 71 | //var material = options.material || new THREE.MeshLambertMaterial( {map:texture, side:THREE.DoubleSide} ); 72 | 73 | //var start_time = (new Date()).getTime(); 74 | var cube_geometry = new THREE.BoxGeometry( height + 3, height + 3, 3 ); 75 | var cube_mesh = new THREE.Mesh( cube_geometry ); 76 | cube_mesh.position.x = x; 77 | cube_mesh.position.y = y; 78 | cube_mesh.position.z = z; 79 | var cube_bsp = new ThreeBSP( cube_mesh ); 80 | var glass_geometry = new THREE.BoxGeometry( height, height, 3.1 ); 81 | var glass_mesh = new THREE.Mesh( glass_geometry ); 82 | glass_mesh.position.x = x; 83 | glass_mesh.position.y = y; 84 | glass_mesh.position.z = z; 85 | var glass_bsp = new ThreeBSP( glass_mesh ); 86 | 87 | var subtract_bsp = cube_bsp.subtract( glass_bsp ); 88 | var result = subtract_bsp.toMesh( new THREE.MeshLambertMaterial({ shading: THREE.SmoothShading, color:'white' }) ); 89 | result.geometry.computeVertexNormals(); 90 | if (rotated){ 91 | result.rotation.y = Math.PI / 2; 92 | } 93 | 94 | options.context.scene.add( result ); 95 | } 96 | 97 | var createWalls = function(context){ 98 | 99 | createWall({length:150, x:25, z:50, window:true, context:context}); 100 | createWindowFrame({length:150, x:25, z:50, context:context}); 101 | createWall({length:100, x:-50, z:0, rotated:true, context:context}); 102 | createWall({length:100, x:0, z:-50, context:context}); 103 | createWall({length:150, x:100, z:-25, rotated:true, window:true, context:context}); 104 | createWindowFrame({length:150, x:100, z:-25, rotated:true, context:context}); 105 | createWall({length:250, x:-25, z:-100, window:true, context:context}); 106 | createWindowFrame({length:250, x:-25, z:-100, context:context}); 107 | //createWall({length:100, x:-100, z:-50, rotated:true}); 108 | //createWall({length:50, x:-125, z:-100}); 109 | createWall({length:150, x:-150, z:-25, rotated:true, window:true, context:context}); 110 | createWindowFrame({length:150, x:-150, z:-25, rotated:true, context:context}); 111 | // createWall({length:50, x:-150, z:25, rotated:true}); 112 | createWall({length:100, x:-100, z:50, window:true, context:context}); 113 | createWindowFrame({length:100, x:-100, z:50, context:context}); 114 | 115 | // createWall({length:150, x:25, z:50, window:true, context:context}); 116 | // createWindowFrame({length:150, x:25, z:50, context:context}); 117 | // createWall({length:100, x:-50, z:0, rotated:true, context:context}); 118 | // createWall({length:100, x:0, z:-50, context:context}); 119 | // createWall({length:150, x:100, z:-25, rotated:true, window:true, context:context}); 120 | // createWindowFrame({length:150, x:100, z:-25, rotated:true, context:context}); 121 | // createWall({length:200, x:0, z:-100, window:true, context:context}); 122 | // createWindowFrame({length:200, x:0, z:-100, context:context}); 123 | // createWall({length:100, x:-100, z:-50, rotated:true, context:context}); 124 | // createWall({length:50, x:-125, z:0, context:context}); 125 | // createWall({length:50, x:-150, z:-25, rotated:true, context:context}); 126 | // createWall({length:50, x:-150, z:25, rotated:true, context:context}); 127 | // createWall({length:100, x:-100, z:50, context:context}); 128 | 129 | }; 130 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | var express = require("express"), 2 | server = express(), 3 | http = require("http").createServer(server), 4 | bodyParser = require("body-parser"), 5 | io = require("socket.io").listen(http), 6 | // _ = require("underscore"), 7 | 8 | port = (process.env.PORT || 8081); 9 | 10 | 11 | 12 | /* Server config */ 13 | 14 | //Server's IP address 15 | server.set("ipaddr", "0.0.0.0"); 16 | 17 | //Server's port number 18 | server.set("port", port); 19 | 20 | //Specify the views folder 21 | server.set("views", __dirname + "/views"); 22 | 23 | //View engine is Jade 24 | server.set("view engine", "jade"); 25 | 26 | //Specify where the static content is 27 | server.use(express.static("static", __dirname + "/static")); 28 | 29 | //Tells server to support JSON requests 30 | server.use(bodyParser.json()); 31 | 32 | //server.listen( port); 33 | 34 | /////////////////////////////////////////// 35 | // Socket.io // 36 | /////////////////////////////////////////// 37 | 38 | //// ADD ALL YOUR SOCKET EVENTS HERE ///// 39 | 40 | //Setup Socket.IO 41 | // var httpServer = require('http').createServer(server); 42 | // var io = require('socket.io')(httpServer); 43 | 44 | // httpServer.listen(port); 45 | 46 | 47 | //translations middleware file: create event listeners at /translations namespace 48 | require('./sockets/translations.js')(io); 49 | 50 | //rtc signalmaster middleware file: create event listeners at /signalmaster namespace 51 | require('./sockets/signalmaster.js')(io); 52 | 53 | /////////////////////////////////////////// 54 | // Routes // 55 | /////////////////////////////////////////// 56 | 57 | /////// ADD ALL YOUR ROUTES HERE ///////// 58 | 59 | server.get('/', function(req,res){ 60 | res.render('index.jade', { 61 | locals : { 62 | title : 'Your Page Title' 63 | ,description: 'Your Page Description' 64 | ,author: 'Your Name' 65 | ,analyticssiteid: 'XXXXXXX' 66 | } 67 | }); 68 | }); 69 | 70 | server.get('/About', function(req,res){ 71 | res.render('pages/About.jade', { 72 | locals : { 73 | title : 'About' 74 | ,description: 'About Real Faces' 75 | ,author: 'Your Name' 76 | ,analyticssiteid: 'XXXXXXX' 77 | } 78 | }); 79 | }); 80 | 81 | server.get('/Problems', function(req,res){ 82 | res.render('pages/Problems.jade', { 83 | locals : { 84 | title : 'Problems' 85 | ,description: 'About Real Faces' 86 | ,author: 'Your Name' 87 | ,analyticssiteid: 'XXXXXXX' 88 | } 89 | }); 90 | }); 91 | 92 | server.get('/Contact', function(req,res){ 93 | res.render('pages/Contact.jade', { 94 | locals : { 95 | title : 'Contact' 96 | ,description: 'About Real Faces' 97 | ,author: 'Your Name' 98 | ,analyticssiteid: 'XXXXXXX' 99 | } 100 | }); 101 | }); 102 | 103 | 104 | // server.get('/Outdoors', function(req,res){ 105 | // res.render('pages/Outdoors.jade', { 106 | // locals : { 107 | // title : 'Your Page Title' 108 | // ,description: 'Your Page Description' 109 | // ,author: 'Your Name' 110 | // ,analyticssiteid: 'XXXXXXX' 111 | // } 112 | // }); 113 | // }); 114 | 115 | server.get('/Outdoors', function(req,res){ 116 | console.log('in normal') 117 | 118 | res.render('pages/Outdoors.jade', { 119 | locals : { 120 | title : 'Your Page Title' 121 | ,description: 'Your Page Description' 122 | ,author: 'Your Name' 123 | ,analyticssiteid: 'XXXXXXX' 124 | } 125 | }); 126 | }); 127 | 128 | server.get('/Outdoors-*', function(req,res){ 129 | console.log('in private') 130 | res.render('pages/Outdoors.jade', { 131 | locals : { 132 | title : 'Your Page Title' 133 | ,description: 'Your Page Description' 134 | ,author: 'Your Name' 135 | ,analyticssiteid: 'XXXXXXX' 136 | } 137 | }); 138 | }); 139 | 140 | server.get('/UnionSquare', function(req,res){ 141 | res.render('pages/UnionSquare.jade', { 142 | locals : { 143 | title : 'Your Page Title' 144 | ,description: 'Your Page Description' 145 | ,author: 'Your Name' 146 | ,analyticssiteid: 'XXXXXXX' 147 | } 148 | }); 149 | }); 150 | 151 | server.get('/UnionSquare-*', function(req,res){ 152 | res.render('pages/UnionSquare.jade', { 153 | locals : { 154 | title : 'Your Page Title' 155 | ,description: 'Your Page Description' 156 | ,author: 'Your Name' 157 | ,analyticssiteid: 'XXXXXXX' 158 | } 159 | }); 160 | }); 161 | 162 | server.get('/ArtGallery', function(req,res){ 163 | res.render('pages/ArtGallery.jade', { 164 | locals : { 165 | title : 'Your Page Title' 166 | ,description: 'Your Page Description' 167 | ,author: 'Your Name' 168 | ,analyticssiteid: 'XXXXXXX' 169 | } 170 | }); 171 | }); 172 | 173 | server.get('/ArtGallery-*', function(req,res){ 174 | res.render('pages/ArtGallery.jade', { 175 | locals : { 176 | title : 'Your Page Title' 177 | ,description: 'Your Page Description' 178 | ,author: 'Your Name' 179 | ,analyticssiteid: 'XXXXXXX' 180 | } 181 | }); 182 | }); 183 | 184 | 185 | //A Route for Creating a 500 Error (Useful to keep around) 186 | server.get('/500', function(req, res){ 187 | throw new Error('This is a 500 Error'); 188 | }); 189 | 190 | //The 404 Route (ALWAYS Keep this as the last route) 191 | // server.get('/*', function(req, res){ 192 | // throw new NotFound(); 193 | // }); 194 | 195 | function NotFound(msg){ 196 | this.name = 'NotFound'; 197 | Error.call(this, msg); 198 | Error.captureStackTrace(this, arguments.callee); 199 | } 200 | 201 | http.listen(server.get("port"), server.get("ipaddr"), function() { 202 | console.log("Server up and running. Go to http://" + server.get("ipaddr") + ":" + server.get("port")); 203 | }); 204 | 205 | //console.log('Listening on http://0.0.0.0:' + port ); 206 | -------------------------------------------------------------------------------- /static/js/3DComponents/avatars.js: -------------------------------------------------------------------------------- 1 | //simplified adaption of the voxel avatar node package with features that were incompatible with the new version of three.js removed 2 | 3 | function Avatar(three, opts) { 4 | 5 | opts = opts || {}; 6 | 7 | //THREE = three // hack until three.js fixes multiple instantiation 8 | this.sizeRatio = opts.sizeRatio || 0.4 9 | this.scale = opts.scale || new THREE.Vector3(1, 1, 1) 10 | this.fallbackImage = opts.fallbackImage || 'avatar.png' 11 | this.createCanvases() 12 | this.mesh = this.createPlayerObject() 13 | this.mesh.scale.set(this.sizeRatio, this.sizeRatio * 0.75, this.sizeRatio) 14 | 15 | this.walkSpeed = 0.6; 16 | this.startedWalking = 0.0; 17 | this.stoppedWalking = 0.0; 18 | this.walking = false; 19 | this.acceleration = 0.5; 20 | } 21 | 22 | Avatar.prototype.createCanvases = function() { 23 | this.avatarBig = document.createElement('canvas') 24 | this.avatarBigContext = this.avatarBig.getContext('2d') 25 | this.avatarBig.width = 64 * this.sizeRatio 26 | this.avatarBig.height = 32 * this.sizeRatio 27 | 28 | this.avatar = document.createElement('canvas') 29 | this.avatarContext = this.avatar.getContext('2d') 30 | this.avatar.width = 64 31 | this.avatar.height = 32 32 | } 33 | 34 | 35 | Avatar.prototype.createPlayerObject = function(scene) { 36 | var headgroup = new THREE.Object3D(); 37 | var upperbody = this.upperbody = new THREE.Object3D(); 38 | var plainMaterial = new THREE.MeshBasicMaterial( { color: new THREE.Color('grey') } ); 39 | 40 | var armMaterial = new THREE.MeshBasicMaterial( { map: THREE.ImageUtils.loadTexture( 'images/bodyTextures/defaultPerson/arm.png' )}); 41 | var bodyMaterial = new THREE.MeshBasicMaterial( { map: THREE.ImageUtils.loadTexture( 'images/bodyTextures/defaultPerson/body.png' )}); 42 | var bottomMaterial = new THREE.MeshBasicMaterial( { map: THREE.ImageUtils.loadTexture( 'images/bodyTextures/defaultPerson/bottom.png' )}); 43 | var handMaterial = new THREE.MeshBasicMaterial( { map: THREE.ImageUtils.loadTexture( 'images/bodyTextures/defaultPerson/hand.png' )}); 44 | var legMaterial = new THREE.MeshBasicMaterial( { map: THREE.ImageUtils.loadTexture( 'images/bodyTextures/defaultPerson/leg.png' )}); 45 | var shoeMaterial = new THREE.MeshBasicMaterial( { map: THREE.ImageUtils.loadTexture( 'images/bodyTextures/defaultPerson/shoe.png' )}); 46 | var shoulderMaterial = new THREE.MeshBasicMaterial( { map: THREE.ImageUtils.loadTexture( 'images/bodyTextures/defaultPerson/shoulder.png' )}); 47 | var sideMaterial = new THREE.MeshBasicMaterial( { map: THREE.ImageUtils.loadTexture( 'images/bodyTextures/defaultPerson/side.png' )}); 48 | 49 | var armMatFull = new THREE.MeshFaceMaterial([armMaterial, armMaterial, shoulderMaterial, handMaterial, armMaterial,armMaterial]) 50 | var bodyMatFull = new THREE.MeshFaceMaterial([bodyMaterial, bodyMaterial, bottomMaterial, bottomMaterial, sideMaterial,sideMaterial]) 51 | var legMatFull = new THREE.MeshFaceMaterial([legMaterial, legMaterial, shoeMaterial, shoeMaterial, legMaterial,legMaterial]) 52 | // Left leg 53 | var leftleggeo = new THREE.BoxGeometry(4, 12, 4); 54 | for(var i=0; i < 8; i+=1) { 55 | leftleggeo.vertices[i].y -= 6; 56 | } 57 | 58 | 59 | var leftleg = this.leftLeg = new THREE.Mesh(leftleggeo, legMatFull); 60 | leftleg.position.z = -2; 61 | leftleg.position.y = -6; 62 | 63 | // Right leg 64 | var rightleggeo = new THREE.BoxGeometry(4, 12, 4); 65 | for(var i=0; i < 8; i+=1) { 66 | rightleggeo.vertices[i].y -= 6; 67 | } 68 | var rightleg = this.rightLeg =new THREE.Mesh(rightleggeo, legMatFull); 69 | rightleg.position.z = 2; 70 | rightleg.position.y = -6; 71 | 72 | 73 | // Body 74 | var bodygeo = new THREE.BoxGeometry(4, 12, 8); 75 | var bodymesh = this.body = new THREE.Mesh(bodygeo, bodyMatFull); 76 | upperbody.add(bodymesh); 77 | 78 | 79 | // Left arm 80 | var leftarmgeo = new THREE.BoxGeometry(4, 12, 4); 81 | for(var i=0; i < 8; i+=1) { 82 | leftarmgeo.vertices[i].y -= 4; 83 | } 84 | var leftarm = this.leftArm = new THREE.Mesh(leftarmgeo, armMatFull); 85 | leftarm.position.z = -6; 86 | leftarm.position.y = 4; 87 | leftarm.rotation.x = Math.PI/32; 88 | upperbody.add(leftarm); 89 | 90 | // Right arm 91 | var rightarmgeo = new THREE.BoxGeometry(4, 12, 4); 92 | for(var i=0; i < 8; i+=1) { 93 | rightarmgeo.vertices[i].y -= 4; 94 | } 95 | var rightarm =this.rightArm = new THREE.Mesh(rightarmgeo, armMatFull); 96 | rightarm.position.z = 6; 97 | rightarm.position.y = 4; 98 | rightarm.rotation.x = -Math.PI/32; 99 | 100 | upperbody.add(rightarm); 101 | 102 | //Head 103 | // var headgeo = new THREE.BoxGeometry(0.1, 0.1, 0.1); 104 | // var headmesh = this.head = new THREE.Mesh(headgeo, plainMaterial); 105 | // headmesh.position.y = 2; 106 | 107 | 108 | // var unrotatedHeadMesh = new THREE.Object3D(); 109 | // unrotatedHeadMesh.rotation.y = Math.PI / 2; 110 | // unrotatedHeadMesh.add(headmesh); 111 | 112 | // headgroup.add(unrotatedHeadMesh); 113 | // headgroup.position.y = 8; 114 | 115 | var playerModel = this.playerModel = new THREE.Object3D(); 116 | 117 | playerModel.add(leftleg); 118 | playerModel.add(rightleg); 119 | 120 | playerModel.add(upperbody); 121 | 122 | var playerRotation = new THREE.Object3D(); 123 | playerRotation.rotation.y = Math.PI / 2 124 | playerRotation.position.y = 12 125 | playerRotation.add(playerModel) 126 | 127 | // var rotatedHead = new THREE.Object3D(); 128 | // rotatedHead.rotation.y = -Math.PI/2; 129 | // rotatedHead.add(headgroup); 130 | 131 | // playerModel.add(rotatedHead); 132 | playerModel.position.y = 6; 133 | 134 | var playerGroup = new THREE.Object3D(); 135 | 136 | 137 | playerGroup.add(playerRotation); 138 | playerGroup.scale = this.scale 139 | return playerGroup 140 | }; 141 | 142 | //slightly adapted copy of the voxel duckWalk node package 143 | 144 | Avatar.prototype.render = function(){ 145 | 146 | 147 | var time = Date.now() / 1000 148 | if (this.walking && time < this.startedWalking + this.acceleration){ 149 | this.walkSpeed = (time - this.startedWalking) / this.acceleration 150 | //console.log('walking', this.walkSpeed) 151 | } 152 | if (!this.walking ){ 153 | if (time < this.stoppedWalking + this.acceleration) 154 | this.walkSpeed = -1 / this.acceleration * (time - this.stoppedWalking) + 1 155 | else if(this.walkSpeed > 0.02) 156 | this.walkSpeed *= 0.95; 157 | else { this.walkSpeed = 0; } 158 | //console.log('not walking', this.walkSpeed) 159 | 160 | } 161 | 162 | // this.head.rotation.y = Math.sin(time * 1.5) / 3 * this.walkSpeed; 163 | // this.head.rotation.z = Math.sin(time) / 2 * this.walkSpeed; 164 | 165 | this.rightArm.rotation.z = 2 * Math.cos(0.6662 * time * 10 + Math.PI) * this.walkSpeed 166 | this.rightArm.rotation.x = 1 * (Math.cos(0.2812 * time * 10) - 1) * this.walkSpeed 167 | this.leftArm.rotation.z = 2 * Math.cos(0.6662 * time * 10) * this.walkSpeed 168 | this.leftArm.rotation.x = 1 * (Math.cos(0.2312 * time * 10) + 1) * this.walkSpeed 169 | 170 | this.rightLeg.rotation.z = 1.4 * Math.cos(0.6662 * time * 10) * this.walkSpeed 171 | this.leftLeg.rotation.z = 1.4 * Math.cos(0.6662 * time * 10 + Math.PI) * this.walkSpeed 172 | } 173 | 174 | Avatar.prototype.startWalking = function(){ 175 | //console.log('startWalking', this) 176 | var now = Date.now() / 1000 177 | this.walking = true 178 | if (this.stoppedWalking + this.acceleration > now){ 179 | var progress = now - this.stoppedWalking; 180 | this.startedWalking = now - (this.stoppedWalking + this.acceleration - now) 181 | } else { 182 | this.startedWalking = Date.now() / 1000 183 | } 184 | } 185 | 186 | Avatar.prototype.stopWalking = function() { 187 | //console.log('stopWalking', this) 188 | var now = Date.now() / 1000 189 | this.walking = false 190 | if (this.startedWalking + this.acceleration > now){ 191 | this.stoppedWalking = now - (this.startedWalking + this.acceleration - now) 192 | } else { 193 | this.stoppedWalking = Date.now() / 1000 194 | } 195 | //this.walkSpeed = 0; 196 | } 197 | 198 | Avatar.prototype.isWalking = function(){ 199 | return this.walking; 200 | } 201 | 202 | Avatar.prototype.setAcceleration = function(newA){ 203 | this.acceleration = newA; 204 | } 205 | -------------------------------------------------------------------------------- /static/js/vendor/PointerLockControls.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author mrdoob / http://mrdoob.com/ 3 | */ 4 | // WARNING: This vendor file has been heavily modified 5 | // consider moving from vendor directory 6 | THREE.PointerLockControls = function ( camera, sceneVars, positiveBoundaryX, negativeBoundaryX, positiveBoundaryZ, negativeBoundaryZ, wallList, mirrorCompatible) { 7 | 8 | var scope = this; 9 | 10 | camera.rotation.set( 0, 0, 0 ); 11 | 12 | var pitchObject = new THREE.Object3D(); 13 | pitchObject.add( camera ); 14 | 15 | var yawObject = new THREE.Object3D(); 16 | yawObject.position.y = sceneVars.playerStartHeight; 17 | //createPlayerScreen(yourID); 18 | yawObject.add( pitchObject ); 19 | 20 | if (mirrorCompatible){ 21 | var you = createYourPlayerScreen(); 22 | var walking = false; 23 | yawObject.add(you); 24 | } 25 | 26 | 27 | var moveForward = false; 28 | var moveBackward = false; 29 | var moveLeft = false; 30 | var moveRight = false; 31 | 32 | var isOnObject = false; 33 | var canJump = false; 34 | 35 | var prevTime = performance.now(); 36 | 37 | var velocity = new THREE.Vector3(); 38 | 39 | var PI_2 = Math.PI / 2; 40 | 41 | var getTranslation = function(){ 42 | var position = { 43 | x: yawObject.position.x, 44 | y: yawObject.position.y, 45 | z: yawObject.position.z, 46 | } 47 | var rotation = { 48 | x: pitchObject.rotation.x, 49 | y: yawObject.rotation.y 50 | } 51 | 52 | //console.log('yaw/pitch objs',yawObject, pitchObject) 53 | 54 | return {position:position, rotation:rotation}; 55 | }; 56 | 57 | var rotated = false; 58 | var onMouseMove = function ( event ) { 59 | 60 | if ( scope.enabled === false ) return; 61 | 62 | var movementX = event.movementX || event.mozMovementX || event.webkitMovementX || 0; 63 | var movementY = event.movementY || event.mozMovementY || event.webkitMovementY || 0; 64 | 65 | if (Math.abs(movementX) > 0.04 || Math.abs(movementY) > 0.04){ 66 | 67 | rotated = true; 68 | 69 | yawObject.rotation.y -= movementX * 0.002; 70 | pitchObject.rotation.x -= movementY * 0.002; 71 | 72 | pitchObject.rotation.x = Math.max( - PI_2, Math.min( PI_2, pitchObject.rotation.x ) ); 73 | 74 | } 75 | 76 | }; 77 | 78 | var onKeyDown = function ( event ) { 79 | 80 | switch ( event.keyCode ) { 81 | 82 | case 38: // up 83 | case 87: // w 84 | moveForward = true; 85 | break; 86 | 87 | case 37: // left 88 | case 65: // a 89 | moveLeft = true; 90 | break; 91 | 92 | case 40: // down 93 | case 83: // s 94 | moveBackward = true; 95 | break; 96 | 97 | case 39: // right 98 | case 68: // d 99 | moveRight = true; 100 | break; 101 | 102 | case 32: // space 103 | if ( canJump === true ) velocity.y += 180; 104 | canJump = false; 105 | break; 106 | 107 | //press p to re-request webcam 108 | case 80: // p 109 | realFaces.webrtc.webrtc.startLocalVideo(); 110 | break; 111 | } 112 | 113 | }; 114 | 115 | var onKeyUp = function ( event ) { 116 | 117 | switch( event.keyCode ) { 118 | 119 | case 38: // up 120 | case 87: // w 121 | moveForward = false; 122 | break; 123 | 124 | case 37: // left 125 | case 65: // a 126 | moveLeft = false; 127 | break; 128 | 129 | case 40: // down 130 | case 83: // s 131 | moveBackward = false; 132 | break; 133 | 134 | case 39: // right 135 | case 68: // d 136 | moveRight = false; 137 | break; 138 | 139 | case 84: //T for talk 140 | playerEvents.emitEvent('start_chat_typing'); 141 | break; 142 | 143 | } 144 | 145 | }; 146 | 147 | document.addEventListener( 'mousemove', onMouseMove, false ); 148 | document.addEventListener( 'keydown', onKeyDown, false ); 149 | document.addEventListener( 'keyup', onKeyUp, false ); 150 | 151 | this.enabled = false; 152 | 153 | this.getObject = function () { 154 | 155 | return yawObject; 156 | 157 | }; 158 | 159 | this.isOnObject = function ( boolean ) { 160 | 161 | isOnObject = boolean; 162 | canJump = boolean; 163 | 164 | }; 165 | 166 | 167 | 168 | //I believe this gets direction of other object relative to the camera 169 | this.getDirection = function() { 170 | 171 | // assumes the camera itself is not rotated 172 | 173 | var direction = new THREE.Vector3( 0, 0, -1 ); 174 | var rotation = new THREE.Euler( 0, 0, 0, "YXZ" ); 175 | 176 | return function( v ) { 177 | 178 | rotation.set( pitchObject.rotation.x, yawObject.rotation.y, 0 ); 179 | 180 | v.copy( direction ).applyEuler( rotation ); 181 | 182 | return v; 183 | 184 | } 185 | 186 | }(); 187 | 188 | 189 | 190 | this.update = function () { 191 | 192 | 193 | var jumped = false; 194 | 195 | 196 | if ( scope.enabled === false ) return; 197 | 198 | var time = performance.now(); 199 | var delta = ( time - prevTime ) / 1000; 200 | 201 | 202 | velocity.x -= velocity.x * 10.0 * delta; 203 | velocity.z -= velocity.z * 10.0 * delta; 204 | 205 | velocity.y -= 9.8 * 100.0 * delta; // 100.0 = mass 206 | 207 | //make sure character position is within boundary, reset it to outer edge if it is not 208 | // if ( yawObject.position.z > positiveBoundary){ 209 | // yawObject.position.z = positiveBoundary; 210 | // } else if (yawObject.position.z < negativeBoundary ){ 211 | // yawObject.position.z = negativeBoundary; 212 | // } else if ( yawObject.position.x > positiveBoundary){ 213 | // yawObject.position.x = positiveBoundary; 214 | // } else if (yawObject.position.x < negativeBoundary ){ 215 | // yawObject.position.x = negativeBoundary; 216 | // } 217 | 218 | //default is 400 219 | var speed = sceneVars.playerSpeed; 220 | //add velocity to your character if key is pressed 221 | if ( moveForward ) velocity.z -= speed * delta; 222 | if ( moveBackward ) velocity.z += speed * delta; 223 | 224 | if ( moveLeft ) velocity.x -= speed * delta; 225 | if ( moveRight ) velocity.x += speed * delta; 226 | 227 | 228 | // Min velocity is enabled to prevent insignificant movements from being broadcast 229 | // Max velocity is a work around for the after pause teleport bug in PointerLock vendor code 230 | if (Math.abs(velocity.x) < 0.001 || Math.abs(velocity.x) > 300 || Math.abs(velocity.x * delta) > 50) velocity.x = 0; 231 | if (Math.abs(velocity.y) < 0.001 || Math.abs(velocity.y) > 500 || Math.abs(velocity.y * delta) > 250) velocity.y = 0; 232 | if (Math.abs(velocity.z) < 0.001 || Math.abs(velocity.z) > 300 || Math.abs(velocity.z * delta) > 50) velocity.z = 0; 233 | 234 | if (mirrorCompatible){ 235 | if (Math.abs(velocity.x) > 1 || Math.abs(velocity.y) > 100 || Math.abs(velocity.z) > 1){ 236 | 237 | if (!walking){ 238 | you.startWalking(); 239 | walking = true; 240 | } 241 | 242 | }else{ 243 | 244 | if(walking){ 245 | you.stopWalking(); 246 | } 247 | walking = false; 248 | } 249 | 250 | you.update(); 251 | 252 | } 253 | 254 | 255 | if ( isOnObject === true ) { 256 | 257 | velocity.y = Math.max( 0, velocity.y ); 258 | 259 | } 260 | 261 | var originalX = yawObject.position.x; 262 | var originalZ = yawObject.position.z; 263 | 264 | 265 | yawObject.translateX( velocity.x * delta ); 266 | yawObject.translateY( velocity.y * delta ); 267 | yawObject.translateZ( velocity.z * delta ); 268 | 269 | 270 | 271 | var overlappedPlayerPosition = realFaces.THREE.findOtherPlayerCollision(yawObject.position.x, yawObject.position.z); 272 | 273 | if (overlappedPlayerPosition){ 274 | 275 | //console.log('overlap', overlappedPlayerPosition); 276 | 277 | var xzTuple = realFaces.THREE.findCollisionZoneEdge(overlappedPlayerPosition, yawObject.position); 278 | 279 | yawObject.position.setX(xzTuple[0]); 280 | yawObject.position.setZ(xzTuple[1]); 281 | 282 | } 283 | 284 | var wallCollisionPoint = realFaces.THREE.isWallCollision(yawObject.position.x, yawObject.position.z, wallList); 285 | 286 | if (wallCollisionPoint){ 287 | 288 | //console.log('mesh collision found') 289 | yawObject.position.setX(wallCollisionPoint[0]); 290 | yawObject.position.setZ(wallCollisionPoint[1]); 291 | 292 | } 293 | 294 | wallCollisionPoint = realFaces.THREE.isWallCollision(yawObject.position.x, yawObject.position.z, wallList); 295 | 296 | if (wallCollisionPoint){ 297 | 298 | yawObject.position.setX(wallCollisionPoint[0]); 299 | yawObject.position.setZ(wallCollisionPoint[1]); 300 | 301 | } 302 | 303 | var crossedOuterBoundary = realFaces.THREE.isOutsideBoundary(yawObject.position.x, yawObject.position.z, positiveBoundaryX, negativeBoundaryX, positiveBoundaryZ, negativeBoundaryZ); 304 | 305 | if (crossedOuterBoundary){ 306 | 307 | yawObject.position.setX(crossedOuterBoundary[0]); 308 | yawObject.position.setZ(crossedOuterBoundary[1]); 309 | 310 | } 311 | 312 | if ( yawObject.position.y < realFaces.THREE.sceneVars.playerStartHeight ) { 313 | 314 | velocity.y = 0; 315 | yawObject.position.y = realFaces.THREE.sceneVars.playerStartHeight; 316 | 317 | if (!canJump) 318 | jumped = true; 319 | 320 | canJump = true; 321 | 322 | } 323 | 324 | prevTime = time; 325 | 326 | if (velocity.x !== 0 || velocity.y !== 0 || velocity.z !== 0 || rotated || jumped){ 327 | 328 | var translation = getTranslation(); 329 | playerEvents.emitEvent('player_movement', [translation]); 330 | //socket.emit('movement', velocity); 331 | rotated = false; 332 | } 333 | 334 | }; 335 | 336 | }; 337 | -------------------------------------------------------------------------------- /static/js/vendor/Mirror.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Slayvin / http://slayvin.net 3 | */ 4 | 5 | THREE.ShaderLib['mirror'] = { 6 | 7 | uniforms: { "mirrorColor": { type: "c", value: new THREE.Color(0x7F7F7F) }, 8 | "mirrorSampler": { type: "t", value: null }, 9 | "textureMatrix" : { type: "m4", value: new THREE.Matrix4() } 10 | }, 11 | 12 | vertexShader: [ 13 | 14 | "uniform mat4 textureMatrix;", 15 | 16 | "varying vec4 mirrorCoord;", 17 | 18 | "void main() {", 19 | 20 | "vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );", 21 | "vec4 worldPosition = modelMatrix * vec4( position, 1.0 );", 22 | "mirrorCoord = textureMatrix * worldPosition;", 23 | 24 | "gl_Position = projectionMatrix * mvPosition;", 25 | 26 | "}" 27 | 28 | ].join("\n"), 29 | 30 | fragmentShader: [ 31 | 32 | "uniform vec3 mirrorColor;", 33 | "uniform sampler2D mirrorSampler;", 34 | 35 | "varying vec4 mirrorCoord;", 36 | 37 | "float blendOverlay(float base, float blend) {", 38 | "return( base < 0.5 ? ( 2.0 * base * blend ) : (1.0 - 2.0 * ( 1.0 - base ) * ( 1.0 - blend ) ) );", 39 | "}", 40 | 41 | "void main() {", 42 | 43 | "vec4 color = texture2DProj(mirrorSampler, mirrorCoord);", 44 | "color = vec4(blendOverlay(mirrorColor.r, color.r), blendOverlay(mirrorColor.g, color.g), blendOverlay(mirrorColor.b, color.b), 1.0);", 45 | 46 | "gl_FragColor = color;", 47 | 48 | "}" 49 | 50 | ].join("\n") 51 | 52 | }; 53 | 54 | THREE.Mirror = function ( renderer, camera, options ) { 55 | 56 | THREE.Object3D.call( this ); 57 | 58 | this.name = 'mirror_' + this.id; 59 | 60 | options = options || {}; 61 | 62 | this.matrixNeedsUpdate = true; 63 | 64 | var width = options.textureWidth !== undefined ? options.textureWidth : 512; 65 | var height = options.textureHeight !== undefined ? options.textureHeight : 512; 66 | 67 | this.clipBias = options.clipBias !== undefined ? options.clipBias : 0.0; 68 | 69 | var mirrorColor = options.color !== undefined ? new THREE.Color(options.color) : new THREE.Color(0x7F7F7F); 70 | 71 | this.renderer = renderer; 72 | this.mirrorPlane = new THREE.Plane(); 73 | this.normal = new THREE.Vector3( 0, 0, 1 ); 74 | this.mirrorWorldPosition = new THREE.Vector3(); 75 | this.cameraWorldPosition = new THREE.Vector3(); 76 | this.rotationMatrix = new THREE.Matrix4(); 77 | this.lookAtPosition = new THREE.Vector3(0, 0, -1); 78 | this.clipPlane = new THREE.Vector4(); 79 | 80 | // For debug only, show the normal and plane of the mirror 81 | var debugMode = options.debugMode !== undefined ? options.debugMode : false; 82 | 83 | if ( debugMode ) { 84 | 85 | var arrow = new THREE.ArrowHelper(new THREE.Vector3( 0, 0, 1 ), new THREE.Vector3( 0, 0, 0 ), 10, 0xffff80 ); 86 | var planeGeometry = new THREE.Geometry(); 87 | planeGeometry.vertices.push( new THREE.Vector3( -10, -10, 0 ) ); 88 | planeGeometry.vertices.push( new THREE.Vector3( 10, -10, 0 ) ); 89 | planeGeometry.vertices.push( new THREE.Vector3( 10, 10, 0 ) ); 90 | planeGeometry.vertices.push( new THREE.Vector3( -10, 10, 0 ) ); 91 | planeGeometry.vertices.push( planeGeometry.vertices[0] ); 92 | var plane = new THREE.Line( planeGeometry, new THREE.LineBasicMaterial( { color: 0xffff80 } ) ); 93 | 94 | this.add(arrow); 95 | this.add(plane); 96 | 97 | } 98 | 99 | if ( camera instanceof THREE.PerspectiveCamera ) { 100 | 101 | this.camera = camera; 102 | 103 | } else { 104 | 105 | this.camera = new THREE.PerspectiveCamera(); 106 | console.log( this.name + ': camera is not a Perspective Camera!' ); 107 | 108 | } 109 | 110 | this.textureMatrix = new THREE.Matrix4(); 111 | 112 | this.mirrorCamera = this.camera.clone(); 113 | 114 | this.texture = new THREE.WebGLRenderTarget( width, height ); 115 | this.tempTexture = new THREE.WebGLRenderTarget( width, height ); 116 | 117 | var mirrorShader = THREE.ShaderLib[ "mirror" ]; 118 | var mirrorUniforms = THREE.UniformsUtils.clone( mirrorShader.uniforms ); 119 | 120 | this.material = new THREE.ShaderMaterial( { 121 | 122 | fragmentShader: mirrorShader.fragmentShader, 123 | vertexShader: mirrorShader.vertexShader, 124 | uniforms: mirrorUniforms 125 | 126 | } ); 127 | 128 | this.material.uniforms.mirrorSampler.value = this.texture; 129 | this.material.uniforms.mirrorColor.value = mirrorColor; 130 | this.material.uniforms.textureMatrix.value = this.textureMatrix; 131 | 132 | if ( !THREE.Math.isPowerOfTwo(width) || !THREE.Math.isPowerOfTwo( height ) ) { 133 | 134 | this.texture.generateMipmaps = false; 135 | this.tempTexture.generateMipmaps = false; 136 | 137 | } 138 | 139 | this.updateTextureMatrix(); 140 | this.render(); 141 | 142 | }; 143 | 144 | THREE.Mirror.prototype = Object.create( THREE.Object3D.prototype ); 145 | 146 | THREE.Mirror.prototype.renderWithMirror = function ( otherMirror ) { 147 | 148 | // update the mirror matrix to mirror the current view 149 | this.updateTextureMatrix(); 150 | this.matrixNeedsUpdate = false; 151 | 152 | // set the camera of the other mirror so the mirrored view is the reference view 153 | var tempCamera = otherMirror.camera; 154 | otherMirror.camera = this.mirrorCamera; 155 | 156 | // render the other mirror in temp texture 157 | otherMirror.renderTemp(); 158 | otherMirror.material.uniforms.mirrorSampler.value = otherMirror.tempTexture; 159 | 160 | // render the current mirror 161 | this.render(); 162 | this.matrixNeedsUpdate = true; 163 | 164 | // restore material and camera of other mirror 165 | otherMirror.material.uniforms.mirrorSampler.value = otherMirror.texture; 166 | otherMirror.camera = tempCamera; 167 | 168 | // restore texture matrix of other mirror 169 | otherMirror.updateTextureMatrix(); 170 | }; 171 | 172 | THREE.Mirror.prototype.updateTextureMatrix = function () { 173 | 174 | this.updateMatrixWorld(); 175 | this.camera.updateMatrixWorld(); 176 | 177 | this.mirrorWorldPosition.setFromMatrixPosition( this.matrixWorld ); 178 | this.cameraWorldPosition.setFromMatrixPosition( this.camera.matrixWorld ); 179 | 180 | this.rotationMatrix.extractRotation( this.matrixWorld ); 181 | 182 | this.normal.set( 0, 0, 1 ); 183 | this.normal.applyMatrix4( this.rotationMatrix ); 184 | 185 | var view = this.mirrorWorldPosition.clone().sub( this.cameraWorldPosition ); 186 | view.reflect( this.normal ).negate(); 187 | view.add( this.mirrorWorldPosition ); 188 | 189 | this.rotationMatrix.extractRotation( this.camera.matrixWorld ); 190 | 191 | this.lookAtPosition.set(0, 0, -1); 192 | this.lookAtPosition.applyMatrix4( this.rotationMatrix ); 193 | this.lookAtPosition.add( this.cameraWorldPosition ); 194 | 195 | var target = this.mirrorWorldPosition.clone().sub( this.lookAtPosition ); 196 | target.reflect( this.normal ).negate(); 197 | target.add( this.mirrorWorldPosition ); 198 | 199 | this.up.set( 0, -1, 0 ); 200 | this.up.applyMatrix4( this.rotationMatrix ); 201 | this.up.reflect( this.normal ).negate(); 202 | 203 | this.mirrorCamera.position.copy( view ); 204 | this.mirrorCamera.up = this.up; 205 | this.mirrorCamera.lookAt( target ); 206 | 207 | this.mirrorCamera.updateProjectionMatrix(); 208 | this.mirrorCamera.updateMatrixWorld(); 209 | this.mirrorCamera.matrixWorldInverse.getInverse( this.mirrorCamera.matrixWorld ); 210 | 211 | // Update the texture matrix 212 | this.textureMatrix.set( 0.5, 0.0, 0.0, 0.5, 213 | 0.0, 0.5, 0.0, 0.5, 214 | 0.0, 0.0, 0.5, 0.5, 215 | 0.0, 0.0, 0.0, 1.0 ); 216 | this.textureMatrix.multiply( this.mirrorCamera.projectionMatrix ); 217 | this.textureMatrix.multiply( this.mirrorCamera.matrixWorldInverse ); 218 | 219 | // Now update projection matrix with new clip plane, implementing code from: http://www.terathon.com/code/oblique.html 220 | // Paper explaining this technique: http://www.terathon.com/lengyel/Lengyel-Oblique.pdf 221 | this.mirrorPlane.setFromNormalAndCoplanarPoint( this.normal, this.mirrorWorldPosition ); 222 | this.mirrorPlane.applyMatrix4( this.mirrorCamera.matrixWorldInverse ); 223 | 224 | this.clipPlane.set( this.mirrorPlane.normal.x, this.mirrorPlane.normal.y, this.mirrorPlane.normal.z, this.mirrorPlane.constant ); 225 | 226 | var q = new THREE.Vector4(); 227 | var projectionMatrix = this.mirrorCamera.projectionMatrix; 228 | 229 | q.x = ( Math.sign(this.clipPlane.x) + projectionMatrix.elements[8] ) / projectionMatrix.elements[0]; 230 | q.y = ( Math.sign(this.clipPlane.y) + projectionMatrix.elements[9] ) / projectionMatrix.elements[5]; 231 | q.z = - 1.0; 232 | q.w = ( 1.0 + projectionMatrix.elements[10] ) / projectionMatrix.elements[14]; 233 | 234 | // Calculate the scaled plane vector 235 | var c = new THREE.Vector4(); 236 | c = this.clipPlane.multiplyScalar( 2.0 / this.clipPlane.dot(q) ); 237 | 238 | // Replacing the third row of the projection matrix 239 | projectionMatrix.elements[2] = c.x; 240 | projectionMatrix.elements[6] = c.y; 241 | projectionMatrix.elements[10] = c.z + 1.0 - this.clipBias; 242 | projectionMatrix.elements[14] = c.w; 243 | 244 | }; 245 | 246 | THREE.Mirror.prototype.render = function () { 247 | 248 | if ( this.matrixNeedsUpdate ) this.updateTextureMatrix(); 249 | 250 | this.matrixNeedsUpdate = true; 251 | 252 | // Render the mirrored view of the current scene into the target texture 253 | var scene = this; 254 | 255 | while ( scene.parent !== undefined ) { 256 | 257 | scene = scene.parent; 258 | 259 | } 260 | 261 | if ( scene !== undefined && scene instanceof THREE.Scene) { 262 | 263 | this.renderer.render( scene, this.mirrorCamera, this.texture, true ); 264 | 265 | } 266 | 267 | }; 268 | 269 | THREE.Mirror.prototype.renderTemp = function () { 270 | 271 | if ( this.matrixNeedsUpdate ) this.updateTextureMatrix(); 272 | 273 | this.matrixNeedsUpdate = true; 274 | 275 | // Render the mirrored view of the current scene into the target texture 276 | var scene = this; 277 | 278 | while ( scene.parent !== undefined ) { 279 | 280 | scene = scene.parent; 281 | 282 | } 283 | 284 | if ( scene !== undefined && scene instanceof THREE.Scene) { 285 | 286 | this.renderer.render( scene, this.mirrorCamera, this.tempTexture, true ); 287 | 288 | } 289 | 290 | }; 291 | -------------------------------------------------------------------------------- /static/js/3DComponents/threeMain.js: -------------------------------------------------------------------------------- 1 | var RealTHREE = function (xMinBoundary, xMaxBoundary, zMinBoundary, zMaxBoundary, mirrorCompatible) { 2 | this.collidableMeshList = []; 3 | this.wallList = []; 4 | this.objects = []; 5 | this.duckWalkers = []; 6 | this.sceneVars = { 7 | playerStartHeight:12, 8 | playerSpeed: 300, 9 | playerJump: 'x', 10 | playerSize: 'x', 11 | sceneSize: 500, 12 | skySize: 4000 13 | }; 14 | this.negativeBoundaryX = xMinBoundary || -this.sceneVars.sceneSize/2; 15 | this.positiveBoundaryX = xMaxBoundary || this.sceneVars.sceneSize/2; 16 | this.negativeBoundaryZ = zMinBoundary || -this.sceneVars.sceneSize/2; 17 | this.positiveBoundaryZ = zMaxBoundary || this.sceneVars.sceneSize/2; 18 | 19 | this.camera = new THREE.PerspectiveCamera( 60, window.innerWidth / window.innerHeight, 1, 5000 ); 20 | 21 | this.scene = new THREE.Scene(); 22 | this.scene.fog = new THREE.Fog( 0xffffff, 0, 1750 ); 23 | 24 | this.controls = new THREE.PointerLockControls( this.camera, this.sceneVars, this.positiveBoundaryX, this.negativeBoundaryX, this.positiveBoundaryZ, this.negativeBoundaryZ, this.wallList, mirrorCompatible ); 25 | this.scene.add( this.controls.getObject() ); 26 | 27 | this.raycaster = new THREE.Raycaster( new THREE.Vector3(), new THREE.Vector3( 0, - 1, 0 ), 0, 10 ); 28 | 29 | this.renderer = new THREE.WebGLRenderer(); 30 | this.renderer.domElement.id = 'realFacesCanvas'; 31 | this.renderer.domElement.style.zIndex = 1; 32 | this.renderer.setClearColor( 0xffffff ); 33 | this.renderer.setSize( window.innerWidth, window.innerHeight ); 34 | 35 | // document.getElementById('blocker').style.zIndex = 0; 36 | 37 | // document.getElementById('roomURL').style.zIndex = 5; 38 | 39 | document.body.appendChild( this.renderer.domElement ); 40 | 41 | window.addEventListener( 'resize', this.onWindowResize, false ); 42 | 43 | }; 44 | 45 | RealTHREE.prototype.createSceneOutdoors = function () { 46 | //////////////////// 47 | // CREATE FLOOR /// 48 | /////////////////// 49 | 50 | // Tiled floor 51 | // note: 4x4 checkboard pattern scaled so that each square is 25 by 25 pixels. 52 | var floorTexture = new THREE.ImageUtils.loadTexture( 'images/grass2.jpg' ); 53 | floorTexture.wrapS = floorTexture.wrapT = THREE.RepeatWrapping; 54 | floorTexture.repeat.set( 1, 1 ); 55 | // DoubleSide: render texture on both sides of mesh 56 | var floorMaterial = new THREE.MeshBasicMaterial( { map: floorTexture, side: THREE.DoubleSide } ); 57 | var floorGeometry = new THREE.PlaneBufferGeometry(this.sceneVars.sceneSize, this.sceneVars.sceneSize, 1, 1); 58 | var floor = new THREE.Mesh(floorGeometry, floorMaterial); 59 | floor.position.y = -0.5; 60 | floor.rotation.x = Math.PI / 2; 61 | this.scene.add(floor); 62 | 63 | var light = new THREE.HemisphereLight( 0xeeeeff, 0x777788, 0.75 ); 64 | light.position.set( 0.5, 1, 0.75 ); 65 | this.scene.add( light ); 66 | 67 | ////////////////////// 68 | // END CREATE FLOOR // 69 | ////////////////////// 70 | 71 | ////////////////////// 72 | // CREATE SKYBOX /// 73 | ////////////////////// 74 | var context = this; 75 | createSkybox('Tantolunden', this.sceneVars.skySize, context); 76 | 77 | //////////////////////// 78 | // END CREATE SKYBOX /// 79 | //////////////////////// 80 | 81 | /////////////////// 82 | // CREATE WALL //// 83 | /////////////////// 84 | var wallGeometry; 85 | var wallMaterial = new THREE.MeshBasicMaterial( {color: 0x8888ff} ); 86 | var wireMaterial = new THREE.MeshBasicMaterial( { color: 0x000000, visible:false } ); 87 | 88 | //west wall 89 | wallGeometry = new THREE.BoxGeometry( 10, 100, this.sceneVars.sceneSize); 90 | var wallWest = new THREE.Mesh(wallGeometry, wireMaterial); 91 | wallWest.position.set(-this.sceneVars.sceneSize/2, 50, 0); 92 | this.scene.add(wallWest); 93 | this.collidableMeshList.push(wallWest); 94 | 95 | //east wall 96 | var wallEast = new THREE.Mesh(wallGeometry, wireMaterial); 97 | wallEast.position.set(this.sceneVars.sceneSize/2, 50, 0); 98 | this.scene.add(wallEast); 99 | this.collidableMeshList.push(wallEast); 100 | 101 | //north wall 102 | wallGeometry = new THREE.BoxGeometry(this.sceneVars.sceneSize, 100, 10, 1, 1, 1 ); 103 | var wallNorth = new THREE.Mesh(wallGeometry, wireMaterial); 104 | wallNorth.position.set(0, 50, -this.sceneVars.sceneSize/2); 105 | this.scene.add(wallNorth); 106 | this.collidableMeshList.push(wallNorth); 107 | 108 | //south wall 109 | var wallSouth = new THREE.Mesh(wallGeometry, wireMaterial); 110 | wallSouth.position.set(0, 50, this.sceneVars.sceneSize/2); 111 | this.scene.add(wallSouth); 112 | this.collidableMeshList.push(wallSouth); 113 | 114 | /////////////////////// 115 | // END CREATE WALL //// 116 | /////////////////////// 117 | }; 118 | 119 | 120 | RealTHREE.prototype.createSceneArtGallery = function () { 121 | 122 | var ambient = new THREE.AmbientLight( 0x444444 ); 123 | this.scene.add( ambient ); 124 | 125 | var light = new THREE.SpotLight( 0xffffff, 0.85, 0, Math.PI / 2, 1 ); 126 | light.position.set( 0, 1500, 1000 ); 127 | light.target.position.set( 0, 0, 0 ); 128 | 129 | this.scene.add( light ); 130 | 131 | //cannon fps copy floor 132 | 133 | var floorTexture = new THREE.ImageUtils.loadTexture( 'images/grid.png' ); 134 | floorTexture.wrapS = floorTexture.wrapT = THREE.RepeatWrapping; 135 | floorTexture.repeat.set( 50, 30 ); 136 | var geometry = new THREE.PlaneBufferGeometry( 250, 150, 5, 5 ); 137 | geometry.applyMatrix( new THREE.Matrix4().makeRotationX( - Math.PI / 2 ) ); 138 | var material = new THREE.MeshLambertMaterial( { map: floorTexture} ); 139 | var floor = new THREE.Mesh( geometry, material ); 140 | floor.position.z = -25; 141 | floor.position.x = -25; 142 | 143 | floor.castShadow = false; 144 | floor.receiveShadow = false; 145 | this.scene.add( floor ); 146 | 147 | var context = this; 148 | createWalls(context); 149 | //fix this function call 150 | createCeiling(null, context); 151 | 152 | createGalleryPictures(context); 153 | 154 | createSkybox('Sorsele3', this.sceneVars.skySize, context); 155 | 156 | /////////////// 157 | // FURNITURE // 158 | /////////////// 159 | 160 | renderer = new THREE.WebGLRenderer({ antialias: true} ); 161 | renderer.context.canvas = WebGLDebugUtils.makeLostContextSimulatingCanvas(renderer.context.canvas); 162 | renderer.context.canvas.addEventListener("webglcontextlost", function(event) { 163 | event.preventDefault(); 164 | // animationID would have been set by your call to requestAnimationFrame 165 | cancelAnimationFrame(animationID); 166 | console.log('animation cancelled due to lost webGL context') 167 | }, false); 168 | //renderer.setClearColor( scene.fog.color ); 169 | //renderer.setPixelRatio( window.devicePixelRatio ); 170 | renderer.setSize( window.innerWidth, window.innerHeight ); 171 | //container.appendChild( renderer.domElement ); 172 | 173 | //renderer.autoClear = false; 174 | 175 | 176 | this.verticalMirror = new THREE.Mirror( this.renderer, this.camera, { clipBias: 0.01, textureWidth: window.innerWidth, textureHeight: window.innerHeight } ); 177 | 178 | var verticalMirrorMesh = new THREE.Mesh( new THREE.PlaneBufferGeometry( 30, 25 ), this.verticalMirror.material ); 179 | verticalMirrorMesh.add( this.verticalMirror ); 180 | verticalMirrorMesh.position.y = 15; 181 | verticalMirrorMesh.position.z = -47.49; 182 | this.scene.add( verticalMirrorMesh ); 183 | 184 | document.body.appendChild( renderer.domElement ); 185 | 186 | } 187 | 188 | RealTHREE.prototype.createSceneUnionSquare = function () { 189 | //////////////////// 190 | // CREATE FLOOR /// 191 | /////////////////// 192 | 193 | // Tiled floor 194 | // note: 4x4 checkboard pattern scaled so that each square is 25 by 25 pixels. 195 | var floorTexture = new THREE.ImageUtils.loadTexture( 'images/checkerboard.jpg' ); 196 | floorTexture.wrapS = floorTexture.wrapT = THREE.RepeatWrapping; 197 | floorTexture.repeat.set( 10, 10 ); 198 | // DoubleSide: render texture on both sides of mesh 199 | var floorMaterial = new THREE.MeshBasicMaterial( { map: floorTexture, side: THREE.DoubleSide } ); 200 | var floorGeometry = new THREE.PlaneBufferGeometry(this.sceneVars.sceneSize, this.sceneVars.sceneSize, 1, 1); 201 | var floor = new THREE.Mesh(floorGeometry, floorMaterial); 202 | floor.position.y = -0.5; 203 | floor.rotation.x = Math.PI / 2; 204 | floor.matrixAutoUpdate = false; 205 | floor.updateMatrix(); 206 | this.scene.add(floor); 207 | 208 | ////////////////////// 209 | // END CREATE FLOOR // 210 | ////////////////////// 211 | 212 | var light = new THREE.HemisphereLight( 0xeeeeff, 0x777788, 0.75 ); 213 | light.position.set( 0.5, 1, 0.75 ); 214 | this.scene.add( light ); 215 | 216 | 217 | ////////////////////// 218 | // CREATE SKYBOX /// 219 | ////////////////////// 220 | 221 | var skyBoxDir = 'UnionSquare'; 222 | 223 | var path = "images/skyBoxes/" + skyBoxDir + "/"; 224 | var format = '.jpg'; 225 | var urls = [ 226 | path + 'posx' + format, path + 'negx' + format, 227 | path + 'posy' + format, path + 'negy' + format, 228 | path + 'posz' + format, path + 'negz' + format 229 | ]; 230 | 231 | var reflectionCube = THREE.ImageUtils.loadTextureCube( urls ); 232 | reflectionCube.format = THREE.RGBFormat; 233 | 234 | var shader = THREE.ShaderLib[ "cube" ]; 235 | shader.uniforms[ "tCube" ].value = reflectionCube; 236 | 237 | var material = new THREE.ShaderMaterial( { 238 | 239 | fragmentShader: shader.fragmentShader, 240 | vertexShader: shader.vertexShader, 241 | uniforms: shader.uniforms, 242 | depthWrite: false, 243 | side: THREE.BackSide 244 | 245 | } ), 246 | 247 | skyBox = new THREE.Mesh( new THREE.BoxGeometry( this.sceneVars.skySize, this.sceneVars.skySize, this.sceneVars.skySize ), material ); 248 | skyBox.position.set(0, this.sceneVars.skySize * 0.4, 0); 249 | this.scene.add( skyBox ); 250 | 251 | //////////////////////// 252 | // END CREATE SKYBOX /// 253 | //////////////////////// 254 | 255 | } 256 | 257 | RealTHREE.prototype.createScene = function (sceneName) { 258 | if(sceneName === 'Outdoors'){ 259 | this.createSceneOutdoors(); 260 | }else if(sceneName === 'ArtGallery'){ 261 | this.createSceneArtGallery(); 262 | }else if(sceneName === 'UnionSquare'){ 263 | this.createSceneUnionSquare(); 264 | } 265 | } 266 | 267 | RealTHREE.prototype.onWindowResize = function() { 268 | realFaces.THREE.camera.aspect = window.innerWidth / window.innerHeight; 269 | realFaces.THREE.camera.updateProjectionMatrix(); 270 | 271 | realFaces.THREE.renderer.setSize( window.innerWidth, window.innerHeight ); 272 | } 273 | 274 | RealTHREE.prototype.animate = function (thisRef) { 275 | window.animateContext = window.animateContext || thisRef; 276 | var thisRef = window.animateContext; 277 | 278 | requestAnimationFrame( window.animateContext.animate ); 279 | 280 | thisRef.controls.isOnObject( false ); 281 | 282 | thisRef.raycaster.ray.origin.copy( thisRef.controls.getObject().position ); 283 | thisRef.raycaster.ray.origin.y -= 10; 284 | 285 | var intersections = thisRef.raycaster.intersectObjects( thisRef.objects ); 286 | 287 | if ( intersections.length > 0 ) { 288 | thisRef.controls.isOnObject( true ); 289 | } 290 | 291 | for(var i = 0, len = thisRef.objects.length; i < len; i++){ 292 | var object = thisRef.objects[i]; 293 | if (typeof(object.update) === 'function') 294 | object.update(); 295 | } 296 | 297 | for(var ID in thisRef.duckWalkers){ 298 | thisRef.duckWalkers[ID].render(); 299 | } 300 | 301 | TWEEN.update(); 302 | //if this scene has mirrors, render them 303 | if(thisRef.verticalMirror){ 304 | thisRef.verticalMirror.render(); 305 | } 306 | thisRef.controls.update(); 307 | thisRef.renderer.render( thisRef.scene, thisRef.camera ); 308 | } 309 | 310 | 311 | RealTHREE.prototype.pointerLock = function () { 312 | 313 | //POINTER LOCK 314 | var blocker = document.getElementById( 'blocker' ); 315 | var instructions = document.getElementById( 'instructions' ); 316 | 317 | var negativeBoundary = -this.sceneVars.sceneSize/2, positiveBoundary = this.sceneVars.sceneSize/2; 318 | 319 | var havePointerLock = 'pointerLockElement' in document || 'mozPointerLockElement' in document || 'webkitPointerLockElement' in document; 320 | 321 | if ( havePointerLock ) { 322 | 323 | var element = document.body; 324 | 325 | var pointerlockchange = function ( event ) { 326 | 327 | if ( document.pointerLockElement === element || document.mozPointerLockElement === element || document.webkitPointerLockElement === element ) { 328 | 329 | // if(!realFaces.webrtc.webcam){ 330 | // document.getElementById('webcamWarning').style.visibility = 'visible'; 331 | // }else{ 332 | // document.getElementById('webcamWarning').style.visibility = 'hidden'; 333 | // } 334 | 335 | realFaces.THREE.controls.enabled = true; 336 | 337 | blocker.style.display = 'none'; 338 | 339 | } else { 340 | 341 | // if(!realFaces.webrtc.webcam){ 342 | // document.getElementById('webcamWarning').style.visibility = 'visible'; 343 | // }else{ 344 | // document.getElementById('webcamWarning').style.visibility = 'hidden'; 345 | // } 346 | 347 | realFaces.THREE.controls.enabled = false; 348 | 349 | blocker.style.display = '-webkit-box'; 350 | blocker.style.display = '-moz-box'; 351 | blocker.style.display = 'box'; 352 | 353 | instructions.style.display = ''; 354 | 355 | } 356 | 357 | } 358 | 359 | var pointerlockerror = function ( event ) { 360 | 361 | instructions.style.display = ''; 362 | 363 | }; 364 | 365 | // Hook pointer lock state change events 366 | document.addEventListener( 'pointerlockchange', pointerlockchange, false ); 367 | document.addEventListener( 'mozpointerlockchange', pointerlockchange, false ); 368 | document.addEventListener( 'webkitpointerlockchange', pointerlockchange, false ); 369 | 370 | document.addEventListener( 'pointerlockerror', pointerlockerror, false ); 371 | document.addEventListener( 'mozpointerlockerror', pointerlockerror, false ); 372 | document.addEventListener( 'webkitpointerlockerror', pointerlockerror, false ); 373 | 374 | instructions.addEventListener( 'click', function ( event ) { 375 | 376 | instructions.style.display = 'none'; 377 | 378 | // Ask the browser to lock the pointer 379 | element.requestPointerLock = element.requestPointerLock || element.mozRequestPointerLock || element.webkitRequestPointerLock; 380 | 381 | if ( /Firefox/i.test( navigator.userAgent ) ) { 382 | 383 | var fullscreenchange = function ( event ) { 384 | 385 | if ( document.fullscreenElement === element || document.mozFullscreenElement === element || document.mozFullScreenElement === element ) { 386 | 387 | document.removeEventListener( 'fullscreenchange', fullscreenchange ); 388 | document.removeEventListener( 'mozfullscreenchange', fullscreenchange ); 389 | 390 | element.requestPointerLock(); 391 | } 392 | 393 | }; 394 | 395 | document.addEventListener( 'fullscreenchange', fullscreenchange, false ); 396 | document.addEventListener( 'mozfullscreenchange', fullscreenchange, false ); 397 | 398 | element.requestFullscreen = element.requestFullscreen || element.mozRequestFullscreen || element.mozRequestFullScreen || element.webkitRequestFullscreen; 399 | 400 | element.requestFullscreen(); 401 | 402 | } else { 403 | 404 | element.requestPointerLock(); 405 | 406 | } 407 | 408 | }, false ); 409 | 410 | } else { 411 | 412 | instructions.innerHTML = 'Your browser doesn\'t seem to support Pointer Lock API'; 413 | 414 | } 415 | //POINTER LOCK 416 | } 417 | -------------------------------------------------------------------------------- /static/js/vendor/ThreeCSG.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | window.ThreeBSP = (function() { 3 | 4 | var ThreeBSP, 5 | EPSILON = 1e-5, 6 | COPLANAR = 0, 7 | FRONT = 1, 8 | BACK = 2, 9 | SPANNING = 3; 10 | 11 | ThreeBSP = function( geometry ) { 12 | // Convert THREE.Geometry to ThreeBSP 13 | var i, _length_i, 14 | face, vertex, faceVertexUvs, uvs, 15 | polygon, 16 | polygons = [], 17 | tree; 18 | 19 | if ( geometry instanceof THREE.Geometry ) { 20 | this.matrix = new THREE.Matrix4; 21 | } else if ( geometry instanceof THREE.Mesh ) { 22 | // #todo: add hierarchy support 23 | geometry.updateMatrix(); 24 | this.matrix = geometry.matrix.clone(); 25 | geometry = geometry.geometry; 26 | } else if ( geometry instanceof ThreeBSP.Node ) { 27 | this.tree = geometry; 28 | this.matrix = new THREE.Matrix4; 29 | return this; 30 | } else { 31 | throw 'ThreeBSP: Given geometry is unsupported'; 32 | } 33 | 34 | for ( i = 0, _length_i = geometry.faces.length; i < _length_i; i++ ) { 35 | face = geometry.faces[i]; 36 | faceVertexUvs = geometry.faceVertexUvs[0][i]; 37 | polygon = new ThreeBSP.Polygon; 38 | 39 | if ( face instanceof THREE.Face3 ) { 40 | vertex = geometry.vertices[ face.a ]; 41 | uvs = faceVertexUvs ? new THREE.Vector2( faceVertexUvs[0].x, faceVertexUvs[0].y ) : null; 42 | vertex = new ThreeBSP.Vertex( vertex.x, vertex.y, vertex.z, face.vertexNormals[0], uvs ); 43 | vertex.applyMatrix4(this.matrix); 44 | polygon.vertices.push( vertex ); 45 | 46 | vertex = geometry.vertices[ face.b ]; 47 | uvs = faceVertexUvs ? new THREE.Vector2( faceVertexUvs[1].x, faceVertexUvs[1].y ) : null; 48 | vertex = new ThreeBSP.Vertex( vertex.x, vertex.y, vertex.z, face.vertexNormals[2], uvs ); 49 | vertex.applyMatrix4(this.matrix); 50 | polygon.vertices.push( vertex ); 51 | 52 | vertex = geometry.vertices[ face.c ]; 53 | uvs = faceVertexUvs ? new THREE.Vector2( faceVertexUvs[2].x, faceVertexUvs[2].y ) : null; 54 | vertex = new ThreeBSP.Vertex( vertex.x, vertex.y, vertex.z, face.vertexNormals[2], uvs ); 55 | vertex.applyMatrix4(this.matrix); 56 | polygon.vertices.push( vertex ); 57 | } else if ( typeof THREE.Face4 ) { 58 | vertex = geometry.vertices[ face.a ]; 59 | uvs = faceVertexUvs ? new THREE.Vector2( faceVertexUvs[0].x, faceVertexUvs[0].y ) : null; 60 | vertex = new ThreeBSP.Vertex( vertex.x, vertex.y, vertex.z, face.vertexNormals[0], uvs ); 61 | vertex.applyMatrix4(this.matrix); 62 | polygon.vertices.push( vertex ); 63 | 64 | vertex = geometry.vertices[ face.b ]; 65 | uvs = faceVertexUvs ? new THREE.Vector2( faceVertexUvs[1].x, faceVertexUvs[1].y ) : null; 66 | vertex = new ThreeBSP.Vertex( vertex.x, vertex.y, vertex.z, face.vertexNormals[1], uvs ); 67 | vertex.applyMatrix4(this.matrix); 68 | polygon.vertices.push( vertex ); 69 | 70 | vertex = geometry.vertices[ face.c ]; 71 | uvs = faceVertexUvs ? new THREE.Vector2( faceVertexUvs[2].x, faceVertexUvs[2].y ) : null; 72 | vertex = new ThreeBSP.Vertex( vertex.x, vertex.y, vertex.z, face.vertexNormals[2], uvs ); 73 | vertex.applyMatrix4(this.matrix); 74 | polygon.vertices.push( vertex ); 75 | 76 | vertex = geometry.vertices[ face.d ]; 77 | uvs = faceVertexUvs ? new THREE.Vector2( faceVertexUvs[3].x, faceVertexUvs[3].y ) : null; 78 | vertex = new ThreeBSP.Vertex( vertex.x, vertex.y, vertex.z, face.vertexNormals[3], uvs ); 79 | vertex.applyMatrix4(this.matrix); 80 | polygon.vertices.push( vertex ); 81 | } else { 82 | throw 'Invalid face type at index ' + i; 83 | } 84 | 85 | polygon.calculateProperties(); 86 | polygons.push( polygon ); 87 | }; 88 | 89 | this.tree = new ThreeBSP.Node( polygons ); 90 | }; 91 | ThreeBSP.prototype.subtract = function( other_tree ) { 92 | var a = this.tree.clone(), 93 | b = other_tree.tree.clone(); 94 | 95 | a.invert(); 96 | a.clipTo( b ); 97 | b.clipTo( a ); 98 | b.invert(); 99 | b.clipTo( a ); 100 | b.invert(); 101 | a.build( b.allPolygons() ); 102 | a.invert(); 103 | a = new ThreeBSP( a ); 104 | a.matrix = this.matrix; 105 | return a; 106 | }; 107 | ThreeBSP.prototype.union = function( other_tree ) { 108 | var a = this.tree.clone(), 109 | b = other_tree.tree.clone(); 110 | 111 | a.clipTo( b ); 112 | b.clipTo( a ); 113 | b.invert(); 114 | b.clipTo( a ); 115 | b.invert(); 116 | a.build( b.allPolygons() ); 117 | a = new ThreeBSP( a ); 118 | a.matrix = this.matrix; 119 | return a; 120 | }; 121 | ThreeBSP.prototype.intersect = function( other_tree ) { 122 | var a = this.tree.clone(), 123 | b = other_tree.tree.clone(); 124 | 125 | a.invert(); 126 | b.clipTo( a ); 127 | b.invert(); 128 | a.clipTo( b ); 129 | b.clipTo( a ); 130 | a.build( b.allPolygons() ); 131 | a.invert(); 132 | a = new ThreeBSP( a ); 133 | a.matrix = this.matrix; 134 | return a; 135 | }; 136 | ThreeBSP.prototype.toGeometry = function() { 137 | var i, j, 138 | matrix = new THREE.Matrix4().getInverse( this.matrix ), 139 | geometry = new THREE.Geometry(), 140 | polygons = this.tree.allPolygons(), 141 | polygon_count = polygons.length, 142 | polygon, polygon_vertice_count, 143 | vertice_dict = {}, 144 | vertex_idx_a, vertex_idx_b, vertex_idx_c, 145 | vertex, face, 146 | verticeUvs; 147 | 148 | for ( i = 0; i < polygon_count; i++ ) { 149 | polygon = polygons[i]; 150 | polygon_vertice_count = polygon.vertices.length; 151 | 152 | for ( j = 2; j < polygon_vertice_count; j++ ) { 153 | verticeUvs = []; 154 | 155 | vertex = polygon.vertices[0]; 156 | verticeUvs.push( new THREE.Vector2( vertex.uv.x, vertex.uv.y ) ); 157 | vertex = new THREE.Vector3( vertex.x, vertex.y, vertex.z ); 158 | vertex.applyMatrix4(matrix); 159 | 160 | if ( typeof vertice_dict[ vertex.x + ',' + vertex.y + ',' + vertex.z ] !== 'undefined' ) { 161 | vertex_idx_a = vertice_dict[ vertex.x + ',' + vertex.y + ',' + vertex.z ]; 162 | } else { 163 | geometry.vertices.push( vertex ); 164 | vertex_idx_a = vertice_dict[ vertex.x + ',' + vertex.y + ',' + vertex.z ] = geometry.vertices.length - 1; 165 | } 166 | 167 | vertex = polygon.vertices[j-1]; 168 | verticeUvs.push( new THREE.Vector2( vertex.uv.x, vertex.uv.y ) ); 169 | vertex = new THREE.Vector3( vertex.x, vertex.y, vertex.z ); 170 | vertex.applyMatrix4(matrix); 171 | if ( typeof vertice_dict[ vertex.x + ',' + vertex.y + ',' + vertex.z ] !== 'undefined' ) { 172 | vertex_idx_b = vertice_dict[ vertex.x + ',' + vertex.y + ',' + vertex.z ]; 173 | } else { 174 | geometry.vertices.push( vertex ); 175 | vertex_idx_b = vertice_dict[ vertex.x + ',' + vertex.y + ',' + vertex.z ] = geometry.vertices.length - 1; 176 | } 177 | 178 | vertex = polygon.vertices[j]; 179 | verticeUvs.push( new THREE.Vector2( vertex.uv.x, vertex.uv.y ) ); 180 | vertex = new THREE.Vector3( vertex.x, vertex.y, vertex.z ); 181 | vertex.applyMatrix4(matrix); 182 | if ( typeof vertice_dict[ vertex.x + ',' + vertex.y + ',' + vertex.z ] !== 'undefined' ) { 183 | vertex_idx_c = vertice_dict[ vertex.x + ',' + vertex.y + ',' + vertex.z ]; 184 | } else { 185 | geometry.vertices.push( vertex ); 186 | vertex_idx_c = vertice_dict[ vertex.x + ',' + vertex.y + ',' + vertex.z ] = geometry.vertices.length - 1; 187 | } 188 | 189 | face = new THREE.Face3( 190 | vertex_idx_a, 191 | vertex_idx_b, 192 | vertex_idx_c, 193 | new THREE.Vector3( polygon.normal.x, polygon.normal.y, polygon.normal.z ) 194 | ); 195 | 196 | geometry.faces.push( face ); 197 | geometry.faceVertexUvs[0].push( verticeUvs ); 198 | } 199 | 200 | } 201 | return geometry; 202 | }; 203 | ThreeBSP.prototype.toMesh = function( material ) { 204 | var geometry = this.toGeometry(), 205 | mesh = new THREE.Mesh( geometry, material ); 206 | 207 | mesh.position.setFromMatrixPosition( this.matrix ); 208 | mesh.rotation.setFromRotationMatrix( this.matrix ); 209 | 210 | return mesh; 211 | }; 212 | 213 | 214 | ThreeBSP.Polygon = function( vertices, normal, w ) { 215 | if ( !( vertices instanceof Array ) ) { 216 | vertices = []; 217 | } 218 | 219 | this.vertices = vertices; 220 | if ( vertices.length > 0 ) { 221 | this.calculateProperties(); 222 | } else { 223 | this.normal = this.w = undefined; 224 | } 225 | }; 226 | ThreeBSP.Polygon.prototype.calculateProperties = function() { 227 | var a = this.vertices[0], 228 | b = this.vertices[1], 229 | c = this.vertices[2]; 230 | 231 | this.normal = b.clone().subtract( a ).cross( 232 | c.clone().subtract( a ) 233 | ).normalize(); 234 | 235 | this.w = this.normal.clone().dot( a ); 236 | 237 | return this; 238 | }; 239 | ThreeBSP.Polygon.prototype.clone = function() { 240 | var i, vertice_count, 241 | polygon = new ThreeBSP.Polygon; 242 | 243 | for ( i = 0, vertice_count = this.vertices.length; i < vertice_count; i++ ) { 244 | polygon.vertices.push( this.vertices[i].clone() ); 245 | }; 246 | polygon.calculateProperties(); 247 | 248 | return polygon; 249 | }; 250 | 251 | ThreeBSP.Polygon.prototype.flip = function() { 252 | var i, vertices = []; 253 | 254 | this.normal.multiplyScalar( -1 ); 255 | this.w *= -1; 256 | 257 | for ( i = this.vertices.length - 1; i >= 0; i-- ) { 258 | vertices.push( this.vertices[i] ); 259 | }; 260 | this.vertices = vertices; 261 | 262 | return this; 263 | }; 264 | ThreeBSP.Polygon.prototype.classifyVertex = function( vertex ) { 265 | var side_value = this.normal.dot( vertex ) - this.w; 266 | 267 | if ( side_value < -EPSILON ) { 268 | return BACK; 269 | } else if ( side_value > EPSILON ) { 270 | return FRONT; 271 | } else { 272 | return COPLANAR; 273 | } 274 | }; 275 | ThreeBSP.Polygon.prototype.classifySide = function( polygon ) { 276 | var i, vertex, classification, 277 | num_positive = 0, 278 | num_negative = 0, 279 | vertice_count = polygon.vertices.length; 280 | 281 | for ( i = 0; i < vertice_count; i++ ) { 282 | vertex = polygon.vertices[i]; 283 | classification = this.classifyVertex( vertex ); 284 | if ( classification === FRONT ) { 285 | num_positive++; 286 | } else if ( classification === BACK ) { 287 | num_negative++; 288 | } 289 | } 290 | 291 | if ( num_positive > 0 && num_negative === 0 ) { 292 | return FRONT; 293 | } else if ( num_positive === 0 && num_negative > 0 ) { 294 | return BACK; 295 | } else if ( num_positive === 0 && num_negative === 0 ) { 296 | return COPLANAR; 297 | } else { 298 | return SPANNING; 299 | } 300 | }; 301 | ThreeBSP.Polygon.prototype.splitPolygon = function( polygon, coplanar_front, coplanar_back, front, back ) { 302 | var classification = this.classifySide( polygon ); 303 | 304 | if ( classification === COPLANAR ) { 305 | 306 | ( this.normal.dot( polygon.normal ) > 0 ? coplanar_front : coplanar_back ).push( polygon ); 307 | 308 | } else if ( classification === FRONT ) { 309 | 310 | front.push( polygon ); 311 | 312 | } else if ( classification === BACK ) { 313 | 314 | back.push( polygon ); 315 | 316 | } else { 317 | 318 | var vertice_count, 319 | i, j, ti, tj, vi, vj, 320 | t, v, 321 | f = [], 322 | b = []; 323 | 324 | for ( i = 0, vertice_count = polygon.vertices.length; i < vertice_count; i++ ) { 325 | 326 | j = (i + 1) % vertice_count; 327 | vi = polygon.vertices[i]; 328 | vj = polygon.vertices[j]; 329 | ti = this.classifyVertex( vi ); 330 | tj = this.classifyVertex( vj ); 331 | 332 | if ( ti != BACK ) f.push( vi ); 333 | if ( ti != FRONT ) b.push( vi ); 334 | if ( (ti | tj) === SPANNING ) { 335 | t = ( this.w - this.normal.dot( vi ) ) / this.normal.dot( vj.clone().subtract( vi ) ); 336 | v = vi.interpolate( vj, t ); 337 | f.push( v ); 338 | b.push( v ); 339 | } 340 | } 341 | 342 | 343 | if ( f.length >= 3 ) front.push( new ThreeBSP.Polygon( f ).calculateProperties() ); 344 | if ( b.length >= 3 ) back.push( new ThreeBSP.Polygon( b ).calculateProperties() ); 345 | } 346 | }; 347 | 348 | ThreeBSP.Vertex = function( x, y, z, normal, uv ) { 349 | this.x = x; 350 | this.y = y; 351 | this.z = z; 352 | this.normal = normal || new THREE.Vector3; 353 | this.uv = uv || new THREE.Vector2; 354 | }; 355 | ThreeBSP.Vertex.prototype.clone = function() { 356 | return new ThreeBSP.Vertex( this.x, this.y, this.z, this.normal.clone(), this.uv.clone() ); 357 | }; 358 | ThreeBSP.Vertex.prototype.add = function( vertex ) { 359 | this.x += vertex.x; 360 | this.y += vertex.y; 361 | this.z += vertex.z; 362 | return this; 363 | }; 364 | ThreeBSP.Vertex.prototype.subtract = function( vertex ) { 365 | this.x -= vertex.x; 366 | this.y -= vertex.y; 367 | this.z -= vertex.z; 368 | return this; 369 | }; 370 | ThreeBSP.Vertex.prototype.multiplyScalar = function( scalar ) { 371 | this.x *= scalar; 372 | this.y *= scalar; 373 | this.z *= scalar; 374 | return this; 375 | }; 376 | ThreeBSP.Vertex.prototype.cross = function( vertex ) { 377 | var x = this.x, 378 | y = this.y, 379 | z = this.z; 380 | 381 | this.x = y * vertex.z - z * vertex.y; 382 | this.y = z * vertex.x - x * vertex.z; 383 | this.z = x * vertex.y - y * vertex.x; 384 | 385 | return this; 386 | }; 387 | ThreeBSP.Vertex.prototype.normalize = function() { 388 | var length = Math.sqrt( this.x * this.x + this.y * this.y + this.z * this.z ); 389 | 390 | this.x /= length; 391 | this.y /= length; 392 | this.z /= length; 393 | 394 | return this; 395 | }; 396 | ThreeBSP.Vertex.prototype.dot = function( vertex ) { 397 | return this.x * vertex.x + this.y * vertex.y + this.z * vertex.z; 398 | }; 399 | ThreeBSP.Vertex.prototype.lerp = function( a, t ) { 400 | this.add( 401 | a.clone().subtract( this ).multiplyScalar( t ) 402 | ); 403 | 404 | this.normal.add( 405 | a.normal.clone().sub( this.normal ).multiplyScalar( t ) 406 | ); 407 | 408 | this.uv.add( 409 | a.uv.clone().sub( this.uv ).multiplyScalar( t ) 410 | ); 411 | 412 | return this; 413 | }; 414 | ThreeBSP.Vertex.prototype.interpolate = function( other, t ) { 415 | return this.clone().lerp( other, t ); 416 | }; 417 | ThreeBSP.Vertex.prototype.applyMatrix4 = function ( m ) { 418 | 419 | // input: THREE.Matrix4 affine matrix 420 | 421 | var x = this.x, y = this.y, z = this.z; 422 | 423 | var e = m.elements; 424 | 425 | this.x = e[0] * x + e[4] * y + e[8] * z + e[12]; 426 | this.y = e[1] * x + e[5] * y + e[9] * z + e[13]; 427 | this.z = e[2] * x + e[6] * y + e[10] * z + e[14]; 428 | 429 | return this; 430 | 431 | } 432 | 433 | 434 | ThreeBSP.Node = function( polygons ) { 435 | var i, polygon_count, 436 | front = [], 437 | back = []; 438 | 439 | this.polygons = []; 440 | this.front = this.back = undefined; 441 | 442 | if ( !(polygons instanceof Array) || polygons.length === 0 ) return; 443 | 444 | this.divider = polygons[0].clone(); 445 | 446 | for ( i = 0, polygon_count = polygons.length; i < polygon_count; i++ ) { 447 | this.divider.splitPolygon( polygons[i], this.polygons, this.polygons, front, back ); 448 | } 449 | 450 | if ( front.length > 0 ) { 451 | this.front = new ThreeBSP.Node( front ); 452 | } 453 | 454 | if ( back.length > 0 ) { 455 | this.back = new ThreeBSP.Node( back ); 456 | } 457 | }; 458 | ThreeBSP.Node.isConvex = function( polygons ) { 459 | var i, j; 460 | for ( i = 0; i < polygons.length; i++ ) { 461 | for ( j = 0; j < polygons.length; j++ ) { 462 | if ( i !== j && polygons[i].classifySide( polygons[j] ) !== BACK ) { 463 | return false; 464 | } 465 | } 466 | } 467 | return true; 468 | }; 469 | ThreeBSP.Node.prototype.build = function( polygons ) { 470 | var i, polygon_count, 471 | front = [], 472 | back = []; 473 | 474 | if ( !this.divider ) { 475 | this.divider = polygons[0].clone(); 476 | } 477 | 478 | for ( i = 0, polygon_count = polygons.length; i < polygon_count; i++ ) { 479 | this.divider.splitPolygon( polygons[i], this.polygons, this.polygons, front, back ); 480 | } 481 | 482 | if ( front.length > 0 ) { 483 | if ( !this.front ) this.front = new ThreeBSP.Node(); 484 | this.front.build( front ); 485 | } 486 | 487 | if ( back.length > 0 ) { 488 | if ( !this.back ) this.back = new ThreeBSP.Node(); 489 | this.back.build( back ); 490 | } 491 | }; 492 | ThreeBSP.Node.prototype.allPolygons = function() { 493 | var polygons = this.polygons.slice(); 494 | if ( this.front ) polygons = polygons.concat( this.front.allPolygons() ); 495 | if ( this.back ) polygons = polygons.concat( this.back.allPolygons() ); 496 | return polygons; 497 | }; 498 | ThreeBSP.Node.prototype.clone = function() { 499 | var node = new ThreeBSP.Node(); 500 | 501 | node.divider = this.divider.clone(); 502 | node.polygons = this.polygons.map( function( polygon ) { return polygon.clone(); } ); 503 | node.front = this.front && this.front.clone(); 504 | node.back = this.back && this.back.clone(); 505 | 506 | return node; 507 | }; 508 | ThreeBSP.Node.prototype.invert = function() { 509 | var i, polygon_count, temp; 510 | 511 | for ( i = 0, polygon_count = this.polygons.length; i < polygon_count; i++ ) { 512 | this.polygons[i].flip(); 513 | } 514 | 515 | this.divider.flip(); 516 | if ( this.front ) this.front.invert(); 517 | if ( this.back ) this.back.invert(); 518 | 519 | temp = this.front; 520 | this.front = this.back; 521 | this.back = temp; 522 | 523 | return this; 524 | }; 525 | ThreeBSP.Node.prototype.clipPolygons = function( polygons ) { 526 | var i, polygon_count, 527 | front, back; 528 | 529 | if ( !this.divider ) return polygons.slice(); 530 | 531 | front = [], back = []; 532 | 533 | for ( i = 0, polygon_count = polygons.length; i < polygon_count; i++ ) { 534 | this.divider.splitPolygon( polygons[i], front, back, front, back ); 535 | } 536 | 537 | if ( this.front ) front = this.front.clipPolygons( front ); 538 | if ( this.back ) back = this.back.clipPolygons( back ); 539 | else back = []; 540 | 541 | return front.concat( back ); 542 | }; 543 | 544 | ThreeBSP.Node.prototype.clipTo = function( node ) { 545 | this.polygons = node.clipPolygons( this.polygons ); 546 | if ( this.front ) this.front.clipTo( node ); 547 | if ( this.back ) this.back.clipTo( node ); 548 | }; 549 | 550 | 551 | return ThreeBSP; 552 | })(); -------------------------------------------------------------------------------- /static/js/vendor/EventEmitter.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * EventEmitter v4.2.11 - git.io/ee 3 | * Unlicense - http://unlicense.org/ 4 | * Oliver Caldwell - http://oli.me.uk/ 5 | * @preserve 6 | */ 7 | 8 | ;(function () { 9 | 'use strict'; 10 | 11 | /** 12 | * Class for managing events. 13 | * Can be extended to provide event functionality in other classes. 14 | * 15 | * @class EventEmitter Manages event registering and emitting. 16 | */ 17 | function EventEmitter() {} 18 | 19 | // Shortcuts to improve speed and size 20 | var proto = EventEmitter.prototype; 21 | var exports = this; 22 | var originalGlobalValue = exports.EventEmitter; 23 | 24 | /** 25 | * Finds the index of the listener for the event in its storage array. 26 | * 27 | * @param {Function[]} listeners Array of listeners to search through. 28 | * @param {Function} listener Method to look for. 29 | * @return {Number} Index of the specified listener, -1 if not found 30 | * @api private 31 | */ 32 | function indexOfListener(listeners, listener) { 33 | var i = listeners.length; 34 | while (i--) { 35 | if (listeners[i].listener === listener) { 36 | return i; 37 | } 38 | } 39 | 40 | return -1; 41 | } 42 | 43 | /** 44 | * Alias a method while keeping the context correct, to allow for overwriting of target method. 45 | * 46 | * @param {String} name The name of the target method. 47 | * @return {Function} The aliased method 48 | * @api private 49 | */ 50 | function alias(name) { 51 | return function aliasClosure() { 52 | return this[name].apply(this, arguments); 53 | }; 54 | } 55 | 56 | /** 57 | * Returns the listener array for the specified event. 58 | * Will initialise the event object and listener arrays if required. 59 | * Will return an object if you use a regex search. The object contains keys for each matched event. So /ba[rz]/ might return an object containing bar and baz. But only if you have either defined them with defineEvent or added some listeners to them. 60 | * Each property in the object response is an array of listener functions. 61 | * 62 | * @param {String|RegExp} evt Name of the event to return the listeners from. 63 | * @return {Function[]|Object} All listener functions for the event. 64 | */ 65 | proto.getListeners = function getListeners(evt) { 66 | var events = this._getEvents(); 67 | var response; 68 | var key; 69 | 70 | // Return a concatenated array of all matching events if 71 | // the selector is a regular expression. 72 | if (evt instanceof RegExp) { 73 | response = {}; 74 | for (key in events) { 75 | if (events.hasOwnProperty(key) && evt.test(key)) { 76 | response[key] = events[key]; 77 | } 78 | } 79 | } 80 | else { 81 | response = events[evt] || (events[evt] = []); 82 | } 83 | 84 | return response; 85 | }; 86 | 87 | /** 88 | * Takes a list of listener objects and flattens it into a list of listener functions. 89 | * 90 | * @param {Object[]} listeners Raw listener objects. 91 | * @return {Function[]} Just the listener functions. 92 | */ 93 | proto.flattenListeners = function flattenListeners(listeners) { 94 | var flatListeners = []; 95 | var i; 96 | 97 | for (i = 0; i < listeners.length; i += 1) { 98 | flatListeners.push(listeners[i].listener); 99 | } 100 | 101 | return flatListeners; 102 | }; 103 | 104 | /** 105 | * Fetches the requested listeners via getListeners but will always return the results inside an object. This is mainly for internal use but others may find it useful. 106 | * 107 | * @param {String|RegExp} evt Name of the event to return the listeners from. 108 | * @return {Object} All listener functions for an event in an object. 109 | */ 110 | proto.getListenersAsObject = function getListenersAsObject(evt) { 111 | var listeners = this.getListeners(evt); 112 | var response; 113 | 114 | if (listeners instanceof Array) { 115 | response = {}; 116 | response[evt] = listeners; 117 | } 118 | 119 | return response || listeners; 120 | }; 121 | 122 | /** 123 | * Adds a listener function to the specified event. 124 | * The listener will not be added if it is a duplicate. 125 | * If the listener returns true then it will be removed after it is called. 126 | * If you pass a regular expression as the event name then the listener will be added to all events that match it. 127 | * 128 | * @param {String|RegExp} evt Name of the event to attach the listener to. 129 | * @param {Function} listener Method to be called when the event is emitted. If the function returns true then it will be removed after calling. 130 | * @return {Object} Current instance of EventEmitter for chaining. 131 | */ 132 | proto.addListener = function addListener(evt, listener) { 133 | var listeners = this.getListenersAsObject(evt); 134 | var listenerIsWrapped = typeof listener === 'object'; 135 | var key; 136 | 137 | for (key in listeners) { 138 | if (listeners.hasOwnProperty(key) && indexOfListener(listeners[key], listener) === -1) { 139 | listeners[key].push(listenerIsWrapped ? listener : { 140 | listener: listener, 141 | once: false 142 | }); 143 | } 144 | } 145 | 146 | return this; 147 | }; 148 | 149 | /** 150 | * Alias of addListener 151 | */ 152 | proto.on = alias('addListener'); 153 | 154 | /** 155 | * Semi-alias of addListener. It will add a listener that will be 156 | * automatically removed after its first execution. 157 | * 158 | * @param {String|RegExp} evt Name of the event to attach the listener to. 159 | * @param {Function} listener Method to be called when the event is emitted. If the function returns true then it will be removed after calling. 160 | * @return {Object} Current instance of EventEmitter for chaining. 161 | */ 162 | proto.addOnceListener = function addOnceListener(evt, listener) { 163 | return this.addListener(evt, { 164 | listener: listener, 165 | once: true 166 | }); 167 | }; 168 | 169 | /** 170 | * Alias of addOnceListener. 171 | */ 172 | proto.once = alias('addOnceListener'); 173 | 174 | /** 175 | * Defines an event name. This is required if you want to use a regex to add a listener to multiple events at once. If you don't do this then how do you expect it to know what event to add to? Should it just add to every possible match for a regex? No. That is scary and bad. 176 | * You need to tell it what event names should be matched by a regex. 177 | * 178 | * @param {String} evt Name of the event to create. 179 | * @return {Object} Current instance of EventEmitter for chaining. 180 | */ 181 | proto.defineEvent = function defineEvent(evt) { 182 | this.getListeners(evt); 183 | return this; 184 | }; 185 | 186 | /** 187 | * Uses defineEvent to define multiple events. 188 | * 189 | * @param {String[]} evts An array of event names to define. 190 | * @return {Object} Current instance of EventEmitter for chaining. 191 | */ 192 | proto.defineEvents = function defineEvents(evts) { 193 | for (var i = 0; i < evts.length; i += 1) { 194 | this.defineEvent(evts[i]); 195 | } 196 | return this; 197 | }; 198 | 199 | /** 200 | * Removes a listener function from the specified event. 201 | * When passed a regular expression as the event name, it will remove the listener from all events that match it. 202 | * 203 | * @param {String|RegExp} evt Name of the event to remove the listener from. 204 | * @param {Function} listener Method to remove from the event. 205 | * @return {Object} Current instance of EventEmitter for chaining. 206 | */ 207 | proto.removeListener = function removeListener(evt, listener) { 208 | var listeners = this.getListenersAsObject(evt); 209 | var index; 210 | var key; 211 | 212 | for (key in listeners) { 213 | if (listeners.hasOwnProperty(key)) { 214 | index = indexOfListener(listeners[key], listener); 215 | 216 | if (index !== -1) { 217 | listeners[key].splice(index, 1); 218 | } 219 | } 220 | } 221 | 222 | return this; 223 | }; 224 | 225 | /** 226 | * Alias of removeListener 227 | */ 228 | proto.off = alias('removeListener'); 229 | 230 | /** 231 | * Adds listeners in bulk using the manipulateListeners method. 232 | * If you pass an object as the second argument you can add to multiple events at once. The object should contain key value pairs of events and listeners or listener arrays. You can also pass it an event name and an array of listeners to be added. 233 | * You can also pass it a regular expression to add the array of listeners to all events that match it. 234 | * Yeah, this function does quite a bit. That's probably a bad thing. 235 | * 236 | * @param {String|Object|RegExp} evt An event name if you will pass an array of listeners next. An object if you wish to add to multiple events at once. 237 | * @param {Function[]} [listeners] An optional array of listener functions to add. 238 | * @return {Object} Current instance of EventEmitter for chaining. 239 | */ 240 | proto.addListeners = function addListeners(evt, listeners) { 241 | // Pass through to manipulateListeners 242 | return this.manipulateListeners(false, evt, listeners); 243 | }; 244 | 245 | /** 246 | * Removes listeners in bulk using the manipulateListeners method. 247 | * If you pass an object as the second argument you can remove from multiple events at once. The object should contain key value pairs of events and listeners or listener arrays. 248 | * You can also pass it an event name and an array of listeners to be removed. 249 | * You can also pass it a regular expression to remove the listeners from all events that match it. 250 | * 251 | * @param {String|Object|RegExp} evt An event name if you will pass an array of listeners next. An object if you wish to remove from multiple events at once. 252 | * @param {Function[]} [listeners] An optional array of listener functions to remove. 253 | * @return {Object} Current instance of EventEmitter for chaining. 254 | */ 255 | proto.removeListeners = function removeListeners(evt, listeners) { 256 | // Pass through to manipulateListeners 257 | return this.manipulateListeners(true, evt, listeners); 258 | }; 259 | 260 | /** 261 | * Edits listeners in bulk. The addListeners and removeListeners methods both use this to do their job. You should really use those instead, this is a little lower level. 262 | * The first argument will determine if the listeners are removed (true) or added (false). 263 | * If you pass an object as the second argument you can add/remove from multiple events at once. The object should contain key value pairs of events and listeners or listener arrays. 264 | * You can also pass it an event name and an array of listeners to be added/removed. 265 | * You can also pass it a regular expression to manipulate the listeners of all events that match it. 266 | * 267 | * @param {Boolean} remove True if you want to remove listeners, false if you want to add. 268 | * @param {String|Object|RegExp} evt An event name if you will pass an array of listeners next. An object if you wish to add/remove from multiple events at once. 269 | * @param {Function[]} [listeners] An optional array of listener functions to add/remove. 270 | * @return {Object} Current instance of EventEmitter for chaining. 271 | */ 272 | proto.manipulateListeners = function manipulateListeners(remove, evt, listeners) { 273 | var i; 274 | var value; 275 | var single = remove ? this.removeListener : this.addListener; 276 | var multiple = remove ? this.removeListeners : this.addListeners; 277 | 278 | // If evt is an object then pass each of its properties to this method 279 | if (typeof evt === 'object' && !(evt instanceof RegExp)) { 280 | for (i in evt) { 281 | if (evt.hasOwnProperty(i) && (value = evt[i])) { 282 | // Pass the single listener straight through to the singular method 283 | if (typeof value === 'function') { 284 | single.call(this, i, value); 285 | } 286 | else { 287 | // Otherwise pass back to the multiple function 288 | multiple.call(this, i, value); 289 | } 290 | } 291 | } 292 | } 293 | else { 294 | // So evt must be a string 295 | // And listeners must be an array of listeners 296 | // Loop over it and pass each one to the multiple method 297 | i = listeners.length; 298 | while (i--) { 299 | single.call(this, evt, listeners[i]); 300 | } 301 | } 302 | 303 | return this; 304 | }; 305 | 306 | /** 307 | * Removes all listeners from a specified event. 308 | * If you do not specify an event then all listeners will be removed. 309 | * That means every event will be emptied. 310 | * You can also pass a regex to remove all events that match it. 311 | * 312 | * @param {String|RegExp} [evt] Optional name of the event to remove all listeners for. Will remove from every event if not passed. 313 | * @return {Object} Current instance of EventEmitter for chaining. 314 | */ 315 | proto.removeEvent = function removeEvent(evt) { 316 | var type = typeof evt; 317 | var events = this._getEvents(); 318 | var key; 319 | 320 | // Remove different things depending on the state of evt 321 | if (type === 'string') { 322 | // Remove all listeners for the specified event 323 | delete events[evt]; 324 | } 325 | else if (evt instanceof RegExp) { 326 | // Remove all events matching the regex. 327 | for (key in events) { 328 | if (events.hasOwnProperty(key) && evt.test(key)) { 329 | delete events[key]; 330 | } 331 | } 332 | } 333 | else { 334 | // Remove all listeners in all events 335 | delete this._events; 336 | } 337 | 338 | return this; 339 | }; 340 | 341 | /** 342 | * Alias of removeEvent. 343 | * 344 | * Added to mirror the node API. 345 | */ 346 | proto.removeAllListeners = alias('removeEvent'); 347 | 348 | /** 349 | * Emits an event of your choice. 350 | * When emitted, every listener attached to that event will be executed. 351 | * If you pass the optional argument array then those arguments will be passed to every listener upon execution. 352 | * Because it uses `apply`, your array of arguments will be passed as if you wrote them out separately. 353 | * So they will not arrive within the array on the other side, they will be separate. 354 | * You can also pass a regular expression to emit to all events that match it. 355 | * 356 | * @param {String|RegExp} evt Name of the event to emit and execute listeners for. 357 | * @param {Array} [args] Optional array of arguments to be passed to each listener. 358 | * @return {Object} Current instance of EventEmitter for chaining. 359 | */ 360 | proto.emitEvent = function emitEvent(evt, args) { 361 | var listeners = this.getListenersAsObject(evt); 362 | var listener; 363 | var i; 364 | var key; 365 | var response; 366 | 367 | for (key in listeners) { 368 | if (listeners.hasOwnProperty(key)) { 369 | i = listeners[key].length; 370 | 371 | while (i--) { 372 | // If the listener returns true then it shall be removed from the event 373 | // The function is executed either with a basic call or an apply if there is an args array 374 | listener = listeners[key][i]; 375 | 376 | if (listener.once === true) { 377 | this.removeListener(evt, listener.listener); 378 | } 379 | 380 | response = listener.listener.apply(this, args || []); 381 | 382 | if (response === this._getOnceReturnValue()) { 383 | this.removeListener(evt, listener.listener); 384 | } 385 | } 386 | } 387 | } 388 | 389 | return this; 390 | }; 391 | 392 | /** 393 | * Alias of emitEvent 394 | */ 395 | proto.trigger = alias('emitEvent'); 396 | 397 | /** 398 | * Subtly different from emitEvent in that it will pass its arguments on to the listeners, as opposed to taking a single array of arguments to pass on. 399 | * As with emitEvent, you can pass a regex in place of the event name to emit to all events that match it. 400 | * 401 | * @param {String|RegExp} evt Name of the event to emit and execute listeners for. 402 | * @param {...*} Optional additional arguments to be passed to each listener. 403 | * @return {Object} Current instance of EventEmitter for chaining. 404 | */ 405 | proto.emit = function emit(evt) { 406 | var args = Array.prototype.slice.call(arguments, 1); 407 | return this.emitEvent(evt, args); 408 | }; 409 | 410 | /** 411 | * Sets the current value to check against when executing listeners. If a 412 | * listeners return value matches the one set here then it will be removed 413 | * after execution. This value defaults to true. 414 | * 415 | * @param {*} value The new value to check for when executing listeners. 416 | * @return {Object} Current instance of EventEmitter for chaining. 417 | */ 418 | proto.setOnceReturnValue = function setOnceReturnValue(value) { 419 | this._onceReturnValue = value; 420 | return this; 421 | }; 422 | 423 | /** 424 | * Fetches the current value to check against when executing listeners. If 425 | * the listeners return value matches this one then it should be removed 426 | * automatically. It will return true by default. 427 | * 428 | * @return {*|Boolean} The current value to check for or the default, true. 429 | * @api private 430 | */ 431 | proto._getOnceReturnValue = function _getOnceReturnValue() { 432 | if (this.hasOwnProperty('_onceReturnValue')) { 433 | return this._onceReturnValue; 434 | } 435 | else { 436 | return true; 437 | } 438 | }; 439 | 440 | /** 441 | * Fetches the events object and creates one if required. 442 | * 443 | * @return {Object} The events storage object. 444 | * @api private 445 | */ 446 | proto._getEvents = function _getEvents() { 447 | return this._events || (this._events = {}); 448 | }; 449 | 450 | /** 451 | * Reverts the global {@link EventEmitter} to its previous value and returns a reference to this version. 452 | * 453 | * @return {Function} Non conflicting EventEmitter class. 454 | */ 455 | EventEmitter.noConflict = function noConflict() { 456 | exports.EventEmitter = originalGlobalValue; 457 | return EventEmitter; 458 | }; 459 | 460 | // Expose the class either via AMD, CommonJS or the global object 461 | if (typeof define === 'function' && define.amd) { 462 | define(function () { 463 | return EventEmitter; 464 | }); 465 | } 466 | else if (typeof module === 'object' && module.exports){ 467 | module.exports = EventEmitter; 468 | } 469 | else { 470 | exports.EventEmitter = EventEmitter; 471 | } 472 | }.call(this)); 473 | 474 | var playerEvents = new EventEmitter(); 475 | 476 | --------------------------------------------------------------------------------