├── .gitignore
├── LICENSE.md
├── README.md
├── index.html
├── main.js
├── package.json
├── server
├── app.js
├── bin
│ └── www
├── middlewares
│ └── rtcServer.js
├── package.json
├── public
│ ├── images
│ │ ├── checkerboard.jpg
│ │ ├── uni_hifi.jpg
│ │ └── uni_lowfi.jpg
│ ├── javascripts
│ │ ├── helpers
│ │ │ └── THREEx.WindowResize.js
│ │ ├── index.js
│ │ ├── rtcClient
│ │ │ └── rtcClient.js
│ │ ├── scene
│ │ │ ├── detector.js
│ │ │ ├── mainWindow.js
│ │ │ ├── surfaceDisplay.js
│ │ │ └── widget.js
│ │ ├── webvr
│ │ │ ├── webvr-polyfill-init.js
│ │ │ ├── webvr-polyfill.js
│ │ │ └── webvrconfig.js
│ │ └── webvrThree
│ │ │ ├── vrcontrol.js
│ │ │ ├── vreffect.js
│ │ │ └── webvr.js
│ └── stylesheets
│ │ └── style.css
├── routes
│ └── index.js
├── views
│ ├── error.jade
│ ├── index.jade
│ └── layout.jade
└── webpack.config.js
├── src
├── common
│ ├── rtcClient.js
│ └── socketio.js
├── components
│ ├── window.js
│ └── windowContainer.js
└── renderer.js
└── webpack.config.js
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | bundle.js
3 | npm-debug.log
4 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | CC0 1.0 Universal
2 | ==================
3 |
4 | Statement of Purpose
5 | ---------------------
6 |
7 | The laws of most jurisdictions throughout the world automatically confer exclusive Copyright and Related Rights (defined below) upon the creator and subsequent owner(s) (each and all, an "owner") of an original work of authorship and/or a database (each, a "Work").
8 |
9 | Certain owners wish to permanently relinquish those rights to a Work for the purpose of contributing to a commons of creative, cultural and scientific works ("Commons") that the public can reliably and without fear of later claims of infringement build upon, modify, incorporate in other works, reuse and redistribute as freely as possible in any form whatsoever and for any purposes, including without limitation commercial purposes. These owners may contribute to the Commons to promote the ideal of a free culture and the further production of creative, cultural and scientific works, or to gain reputation or greater distribution for their Work in part through the use and efforts of others.
10 |
11 | For these and/or other purposes and motivations, and without any expectation of additional consideration or compensation, the person associating CC0 with a Work (the "Affirmer"), to the extent that he or she is an owner of Copyright and Related Rights in the Work, voluntarily elects to apply CC0 to the Work and publicly distribute the Work under its terms, with knowledge of his or her Copyright and Related Rights in the Work and the meaning and intended legal effect of CC0 on those rights.
12 |
13 | 1. Copyright and Related Rights.
14 | --------------------------------
15 | A Work made available under CC0 may be protected by copyright and related or neighboring rights ("Copyright and Related Rights"). Copyright and Related Rights include, but are not limited to, the following:
16 |
17 | i. the right to reproduce, adapt, distribute, perform, display, communicate, and translate a Work;
18 | ii. moral rights retained by the original author(s) and/or performer(s);
19 | iii. publicity and privacy rights pertaining to a person's image or likeness depicted in a Work;
20 | iv. rights protecting against unfair competition in regards to a Work, subject to the limitations in paragraph 4(a), below;
21 | v. rights protecting the extraction, dissemination, use and reuse of data in a Work;
22 | vi. database rights (such as those arising under Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, and under any national implementation thereof, including any amended or successor version of such directive); and
23 | vii. other similar, equivalent or corresponding rights throughout the world based on applicable law or treaty, and any national implementations thereof.
24 |
25 | 2. Waiver.
26 | -----------
27 | To the greatest extent permitted by, but not in contravention of, applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and unconditionally waives, abandons, and surrenders all of Affirmer's Copyright and Related Rights and associated claims and causes of action, whether now known or unknown (including existing as well as future claims and causes of action), in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each member of the public at large and to the detriment of Affirmer's heirs and successors, fully intending that such Waiver shall not be subject to revocation, rescission, cancellation, termination, or any other legal or equitable action to disrupt the quiet enjoyment of the Work by the public as contemplated by Affirmer's express Statement of Purpose.
28 |
29 | 3. Public License Fallback.
30 | ----------------------------
31 | Should any part of the Waiver for any reason be judged legally invalid or ineffective under applicable law, then the Waiver shall be preserved to the maximum extent permitted taking into account Affirmer's express Statement of Purpose. In addition, to the extent the Waiver is so judged Affirmer hereby grants to each affected person a royalty-free, non transferable, non sublicensable, non exclusive, irrevocable and unconditional license to exercise Affirmer's Copyright and Related Rights in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the "License"). The License shall be deemed effective as of the date CC0 was applied by Affirmer to the Work. Should any part of the License for any reason be judged legally invalid or ineffective under applicable law, such partial invalidity or ineffectiveness shall not invalidate the remainder of the License, and in such case Affirmer hereby affirms that he or she will not (i) exercise any of his or her remaining Copyright and Related Rights in the Work or (ii) assert any associated claims and causes of action with respect to the Work, in either case contrary to Affirmer's express Statement of Purpose.
32 |
33 | 4. Limitations and Disclaimers.
34 | --------------------------------
35 |
36 | a. No trademark or patent rights held by Affirmer are waived, abandoned, surrendered, licensed or otherwise affected by this document.
37 | b. Affirmer offers the Work as-is and makes no representations or warranties of any kind concerning the Work, express, implied, statutory or otherwise, including without limitation warranties of title, merchantability, fitness for a particular purpose, non infringement, or the absence of latent or other defects, accuracy, or the present or absence of errors, whether or not discoverable, all to the greatest extent permissible under applicable law.
38 | c. Affirmer disclaims responsibility for clearing rights of other persons that may apply to the Work or any use thereof, including without limitation any person's Copyright and Related Rights in the Work. Further, Affirmer disclaims responsibility for obtaining any necessary consents, permissions or other rights required for any use of the Work.
39 | d. Affirmer understands and acknowledges that Creative Commons is not a party to this document and has no duty or obligation with respect to this CC0 or use of the Work.
40 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # coder from 22nd century (c22)
2 |
3 | ## Why
4 |
5 | A demo of Micro-Situational Mixed Reality. You could check it out from [this article](http://www.hanyi.name/blog/2016/11/20/micro-situational-mixed-reality/) (Chinese only), or youtube video below (English subtitle).
6 |
7 | [](http://www.youtube.com/watch?v=24fQwHYODeI"Coder from 22nd Century")
8 |
9 | 
10 |
11 | Does world still need coders in 22nd century? No one knows except who may have ability on somehow mysterious prophecy. Depending on the trend of technology growth in next decades years, while it is indeed possible to deliver a blueprint which describes a programming scene that overcomes the gap from time to space, from devices to brains.
12 |
13 | ## Compatibility
14 |
15 | All OS supported for the Host.
16 |
17 | VR display (Cardboard) only work for Android now. Other platforms please refer to [WebRTC website](https://webrtc.org/native-code/)
18 |
19 | Basically the major techniques (WebRTC, WebVR) of C22 are not widely supported by mobile world (up to end of Nov, 2016), but which is highly possible to be brought to Android & IOS in 2017 according to both engineers interview from Google and Apple.
20 |
21 | There is no stable version browser supporting WebVR natively, luckily we use webvr-polyfill for early development. Or you may use latest version of Chrome without it.
22 |
23 | Besides WebVR, The incompatibility is mainly from WebRTC, which Safari totally ignore (should be released in 2017).
24 |
25 | ## Network
26 |
27 | In development we prefer using WIFI HotSpot directly than communicating through any AP. Please make sure your WIFI environment is unlimited otherwise there may be unbearable lag in mobile devices.
28 |
29 | ## Resolution
30 |
31 | It's strictly limited to mobile devices and hardly to be resolved by us. But our suggestion is just using lower display resolution in your PC.
32 |
33 | In development we switched to 1024*640 for Mac Pro, you could try higher if you have mobile screen with 1080p or plus.
34 |
35 | ## How to use
36 |
37 | Install packages for both desktop code and server code:
38 | ```[shell]
39 | npm install
40 | ```
41 | Build bundle.js for both desktop code and server code:
42 | ```[shell]
43 | webpack
44 | ```
45 | Under root path, run:
46 | ```[shell]
47 | npm start
48 | ```
49 | Open chrome in your cardboard device, and access:
50 |
51 | http://host:8301
52 |
53 | Try to active some applications screen in host side and come back to VR.
54 |
55 | Enjoy and be dizzy:p
56 |
57 | ## Technologies
58 |
59 | Thus we have this repository, which has technology stack of:
60 |
61 | Electron
62 |
63 | Express
64 |
65 | WebSocket
66 |
67 | WebRTC
68 |
69 | WebVR
70 |
71 | three.js
72 |
73 | ~~Unity3D~~
74 |
75 | ## Roadmap
76 |
77 | Native WebVR support
78 |
79 | Elastic apps showing
80 |
81 | Control switch for apps
82 |
83 | Network tuning
84 |
85 | Display tuning
86 |
87 | Native WebVR apps support
88 |
89 | Peripherals for WebVR apps (like bluetooth keyboards, earphones, daydream controllers, etc)
90 |
91 | Well supported peripherals summary
92 |
93 | Support for non-coders
94 |
95 | #### License [CC0 1.0 (Public Domain)](LICENSE.md)
96 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Coder from 22nd century
6 |
7 |
8 |
9 |
10 | We are using Node.js ,
11 | Chromium ,
12 | and Electron .
13 |
14 |
18 |
19 |
--------------------------------------------------------------------------------
/main.js:
--------------------------------------------------------------------------------
1 | const electron = require('electron')
2 | // Module to control application life.
3 | const mainApp = electron.app
4 | // Module to create native browser window.
5 | const BrowserWindow = electron.BrowserWindow
6 |
7 | const path = require('path')
8 | const url = require('url')
9 |
10 | // Keep a global reference of the window object, if you don't, the window will
11 | // be closed automatically when the JavaScript object is garbage collected.
12 | let mainWindow
13 |
14 | function createWindow () {
15 | // Create the browser window.
16 | mainWindow = new BrowserWindow({width: 800, height: 600})
17 |
18 | // and load the index.html of the app.
19 | mainWindow.loadURL(url.format({
20 | pathname: path.join(__dirname, 'index.html'),
21 | protocol: 'file:',
22 | slashes: true
23 | }))
24 |
25 | // Open the DevTools.
26 | mainWindow.webContents.openDevTools()
27 |
28 | // Emitted when the window is closed.
29 | mainWindow.on('closed', function () {
30 | // Dereference the window object, usually you would store windows
31 | // in an array if your app supports multi windows, this is the time
32 | // when you should delete the corresponding element.
33 | mainWindow = null
34 | })
35 | }
36 |
37 | // This method will be called when Electron has finished
38 | // initialization and is ready to create browser windows.
39 | // Some APIs can only be used after this event occurs.
40 | mainApp.on('ready', createWindow)
41 |
42 | // Quit when all windows are closed.
43 | mainApp.on('window-all-closed', function () {
44 | // On OS X it is common for applications and their menu bar
45 | // to stay active until the user quits explicitly with Cmd + Q
46 | if (process.platform !== 'darwin') {
47 | mainApp.quit()
48 | }
49 | })
50 |
51 | mainApp.on('activate', function () {
52 | // On OS X it's common to re-create a window in the app when the
53 | // dock icon is clicked and there are no other windows open.
54 | if (mainWindow === null) {
55 | createWindow()
56 | }
57 | })
58 |
59 | require('./server/app')
60 | // In this file you can include the rest of your app's specific main process
61 | // code. You can also put them in separate files and require them here.
62 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "coderFrom22ndCentury",
3 | "version": "0.1.0",
4 | "description": "",
5 | "main": "main.js",
6 | "scripts": {
7 | "start": "electron ."
8 | },
9 | "repository": "",
10 | "keywords": [],
11 | "author": "Han Yi",
12 | "license": "CC0-1.0",
13 | "devDependencies": {
14 | "babel-core": "^6.18.2",
15 | "babel-loader": "^6.2.7",
16 | "babel-preset-es2015": "^6.18.0",
17 | "babel-preset-react": "^6.16.0",
18 | "babel-preset-stage-1": "^6.16.0",
19 | "body-parser": "~1.15.2",
20 | "cookie-parser": "~1.4.3",
21 | "debug": "~2.2.0",
22 | "electron": "^1.4.1",
23 | "express": "~4.14.0",
24 | "jade": "~1.11.0",
25 | "morgan": "~1.7.0",
26 | "react": "^15.3.2",
27 | "react-dom": "^15.3.2",
28 | "serve-favicon": "~2.3.0",
29 | "socket.io": "^1.5.1",
30 | "socket.io-client": "^1.5.1"
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/server/app.js:
--------------------------------------------------------------------------------
1 | var express = require('express');
2 | var path = require('path');
3 | var favicon = require('serve-favicon');
4 | var logger = require('morgan');
5 | var cookieParser = require('cookie-parser');
6 | var bodyParser = require('body-parser');
7 |
8 | var index = require('./routes/index');
9 |
10 | var app = express();
11 | var rtcServer = require('./middlewares/rtcServer');
12 |
13 | // view engine setup
14 | app.set('views', path.join(__dirname, 'views'));
15 | app.set('view engine', 'jade');
16 |
17 | // uncomment after placing your favicon in /public
18 | //app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')));
19 | app.use(logger('dev'));
20 | app.use(bodyParser.json());
21 | app.use(bodyParser.urlencoded({ extended: false }));
22 | app.use(cookieParser());
23 | app.use(express.static(path.join(__dirname, 'public')));
24 |
25 | app.use('/', index);
26 |
27 | // catch 404 and forward to error handler
28 | app.use(function(req, res, next) {
29 | var err = new Error('Not Found');
30 | err.status = 404;
31 | next(err);
32 | });
33 |
34 | // error handler
35 | app.use(function(err, req, res, next) {
36 | // set locals, only providing error in development
37 | res.locals.message = err.message;
38 | res.locals.error = req.app.get('env') === 'development' ? err : {};
39 |
40 | // render the error page
41 | res.status(err.status || 500);
42 | res.render('error');
43 | });
44 |
45 | var port = process.env.PORT || 8301;
46 |
47 | var server = app.listen(port, function () {
48 | console.log('Updated : Server listening at port %d', port);
49 | });
50 |
51 | rtcServer(server);
52 |
53 | module.exports = app;
54 |
--------------------------------------------------------------------------------
/server/bin/www:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | /**
4 | * Module dependencies.
5 | */
6 |
7 | var app = require('../app');
8 | var debug = require('debug')('server:server');
9 | var http = require('http');
10 |
11 | /**
12 | * Get port from environment and store in Express.
13 | */
14 |
15 | var port = normalizePort(process.env.PORT || '3000');
16 | app.set('port', port);
17 |
18 | /**
19 | * Create HTTP server.
20 | */
21 |
22 | var server = http.createServer(app);
23 |
24 | /**
25 | * Listen on provided port, on all network interfaces.
26 | */
27 |
28 | server.listen(port);
29 | server.on('error', onError);
30 | server.on('listening', onListening);
31 |
32 | /**
33 | * Normalize a port into a number, string, or false.
34 | */
35 |
36 | function normalizePort(val) {
37 | var port = parseInt(val, 10);
38 |
39 | if (isNaN(port)) {
40 | // named pipe
41 | return val;
42 | }
43 |
44 | if (port >= 0) {
45 | // port number
46 | return port;
47 | }
48 |
49 | return false;
50 | }
51 |
52 | /**
53 | * Event listener for HTTP server "error" event.
54 | */
55 |
56 | function onError(error) {
57 | if (error.syscall !== 'listen') {
58 | throw error;
59 | }
60 |
61 | var bind = typeof port === 'string'
62 | ? 'Pipe ' + port
63 | : 'Port ' + port;
64 |
65 | // handle specific listen errors with friendly messages
66 | switch (error.code) {
67 | case 'EACCES':
68 | console.error(bind + ' requires elevated privileges');
69 | process.exit(1);
70 | break;
71 | case 'EADDRINUSE':
72 | console.error(bind + ' is already in use');
73 | process.exit(1);
74 | break;
75 | default:
76 | throw error;
77 | }
78 | }
79 |
80 | /**
81 | * Event listener for HTTP server "listening" event.
82 | */
83 |
84 | function onListening() {
85 | var addr = server.address();
86 | var bind = typeof addr === 'string'
87 | ? 'pipe ' + addr
88 | : 'port ' + addr.port;
89 | debug('Listening on ' + bind);
90 | }
91 |
--------------------------------------------------------------------------------
/server/middlewares/rtcServer.js:
--------------------------------------------------------------------------------
1 | function socket(server) {
2 | var io = require('socket.io')(server);
3 | io.on('connection', function (socket) {
4 | socket.emit('news', {hello: 'world'});
5 | socket.on('offer', function (data) {
6 | io.sockets.emit('emitOffer', data);
7 | });
8 | socket.on('answer', function (data) {
9 | io.sockets.emit('answer', data);
10 | });
11 | socket.on('connect_timeout', function (data) {
12 | socket.disconnect();
13 | });
14 | socket.on('offerCandidate', function (data) {
15 | io.sockets.emit('offerCandidate', data);
16 | });
17 | socket.on('answerCandidate', function (data) {
18 | io.sockets.emit('answerCandidate', data);
19 | });
20 | });
21 | }
22 | module.exports = socket;
23 |
--------------------------------------------------------------------------------
/server/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "server",
3 | "version": "0.0.0",
4 | "private": true,
5 | "scripts": {
6 | "start": "node ./bin/www"
7 | },
8 | "dependencies": {
9 | "body-parser": "~1.15.2",
10 | "cookie-parser": "~1.4.3",
11 | "debug": "~2.2.0",
12 | "express": "~4.14.0",
13 | "jade": "~1.11.0",
14 | "morgan": "~1.7.0",
15 | "serve-favicon": "~2.3.0"
16 | },
17 | "devDependencies": {
18 | "socket.io-client": "^1.6.0",
19 | "three": "^0.82.1",
20 | "webvr-polyfill": "^0.9.23"
21 | }
22 | }
--------------------------------------------------------------------------------
/server/public/images/checkerboard.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hanystudy/coder-from-22nd-century/f209ee8d77cdc77a4d2dae4c6b1d08f80a7f91fc/server/public/images/checkerboard.jpg
--------------------------------------------------------------------------------
/server/public/images/uni_hifi.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hanystudy/coder-from-22nd-century/f209ee8d77cdc77a4d2dae4c6b1d08f80a7f91fc/server/public/images/uni_hifi.jpg
--------------------------------------------------------------------------------
/server/public/images/uni_lowfi.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hanystudy/coder-from-22nd-century/f209ee8d77cdc77a4d2dae4c6b1d08f80a7f91fc/server/public/images/uni_lowfi.jpg
--------------------------------------------------------------------------------
/server/public/javascripts/helpers/THREEx.WindowResize.js:
--------------------------------------------------------------------------------
1 | // This THREEx helper makes it easy to handle window resize.
2 | // It will update renderer and camera when window is resized.
3 | //
4 | // # Usage
5 | //
6 | // **Step 1**: Start updating renderer and camera
7 | //
8 | // ```var windowResize = THREEx.WindowResize(aRenderer, aCamera)```
9 | //
10 | // **Step 2**: Start updating renderer and camera
11 | //
12 | // ```windowResize.stop()```
13 | // # Code
14 |
15 | //
16 |
17 | /** @namespace */
18 | window.THREEx = window.THREEx || {};
19 |
20 | /**
21 | * Update renderer and camera when the window is resized
22 | *
23 | * @param {Object} renderer the renderer to update
24 | * @param {Object} Camera the camera to update
25 | */
26 | THREEx.WindowResize = function(renderer, camera){
27 | var callback = function(){
28 | // notify the renderer of the size change
29 | renderer.setSize( window.innerWidth, window.innerHeight );
30 | // update the camera
31 | camera.aspect = window.innerWidth / window.innerHeight;
32 | camera.updateProjectionMatrix();
33 | }
34 | // bind the resize event
35 | window.addEventListener('resize', callback, false);
36 | // return .stop() the function to stop watching window resize
37 | return {
38 | /**
39 | * Stop watching window resize
40 | */
41 | stop : function(){
42 | window.removeEventListener('resize', callback);
43 | }
44 | };
45 | }
46 |
--------------------------------------------------------------------------------
/server/public/javascripts/index.js:
--------------------------------------------------------------------------------
1 | window.THREE = require('three')
2 | require('./webvr/webvr-polyfill-init')
3 | require('./webvrThree/vreffect')
4 | require('./webvrThree/vrcontrol')
5 | require('./webvrThree/webvr')
6 |
7 | import remoteVideos from './rtcClient/rtcClient'
8 | import MainWindow from './scene/mainWindow'
9 |
10 | let mainWindow = new MainWindow(remoteVideos, window.innerWidth, window.innerHeight)
11 | let controls = new THREE.VRControls(mainWindow.camera)
12 | let effect = new THREE.VREffect(mainWindow.renderer)
13 | mainWindow.setVR(controls, effect)
14 | if (navigator.getVRDisplays) {
15 | navigator.getVRDisplays()
16 | .then( function ( displays ) {
17 | effect.setVRDisplay( displays[ 0 ] );
18 | controls.setVRDisplay( displays[ 0 ] );
19 | } )
20 | .catch( function () {
21 | // no displays
22 | } );
23 | document.body.appendChild(WEBVR.getButton(effect));
24 | }
25 |
--------------------------------------------------------------------------------
/server/public/javascripts/rtcClient/rtcClient.js:
--------------------------------------------------------------------------------
1 | var socket = require('socket.io-client')(location.origin);
2 | var pc2 = null;
3 | var webrtc = [];
4 | var remoteVideos = [
5 | document.createElement('video'),
6 | document.createElement('video'),
7 | document.createElement('video'),
8 | document.createElement('video')
9 | ]
10 | const MAX_VIDEOS = 4
11 | var indexToBeChanged = 0
12 |
13 | function onCreateAnswerSuccess(desc) {
14 | pc2.setLocalDescription(desc);
15 | socket.emit('answer', desc);
16 | }
17 |
18 | function gotRemoteStream(e) {
19 | remoteVideos[indexToBeChanged].setAttribute('autoPlay','autoPlay')
20 | remoteVideos[indexToBeChanged].srcObject = e.stream
21 | indexToBeChanged = (indexToBeChanged + 1) % MAX_VIDEOS
22 | }
23 |
24 | socket.on('news', function (data) {
25 | console.log(data);
26 | });
27 |
28 | socket.on('emitOffer', function (data) {
29 | let servers = null
30 | if (window.RTCPeerConnection) {
31 | pc2 = new RTCPeerConnection(servers)
32 | }
33 | else {
34 | pc2 = new webkitRTCPeerConnection(servers)
35 | }
36 |
37 | pc2.onicecandidate = function(e) {
38 | if (e.candidate)
39 | socket.emit('answerCandidate', e.candidate)
40 | };
41 | pc2.getLocalStreams().forEach(function(stream) {
42 | pc2.removeStream(stream);
43 | });
44 | pc2.onaddstream = gotRemoteStream;
45 | pc2.setRemoteDescription(new RTCSessionDescription(data))
46 | pc2.createAnswer().then(
47 | onCreateAnswerSuccess,
48 | null
49 | );
50 | });
51 |
52 | socket.on('offerCandidate', function (data) {
53 | pc2.addIceCandidate(new RTCIceCandidate(data));
54 | });
55 |
56 | socket.on('cleanUp', function (data) {
57 | webrtc = webrtc.filter(function (rtc) {
58 | return rtc.connectionState() !== 'closed';
59 | })
60 | });
61 |
62 | socket.on('connect_timeout', function (data) {
63 | socket.disconnect();
64 | });
65 |
66 | export default remoteVideos
67 |
--------------------------------------------------------------------------------
/server/public/javascripts/scene/detector.js:
--------------------------------------------------------------------------------
1 | const Detector = {
2 |
3 | canvas: !! window.CanvasRenderingContext2D,
4 | webgl: ( function () { try { return !! window.WebGLRenderingContext && !! document.createElement( 'canvas' ).getContext( 'experimental-webgl' ); } catch( e ) { return false; } } )(),
5 | workers: !! window.Worker,
6 | fileapi: window.File && window.FileReader && window.FileList && window.Blob,
7 |
8 | getWebGLErrorMessage: function () {
9 |
10 | var element = document.createElement( 'div' );
11 | element.id = 'webgl-error-message';
12 | element.style.fontFamily = 'monospace';
13 | element.style.fontSize = '13px';
14 | element.style.fontWeight = 'normal';
15 | element.style.textAlign = 'center';
16 | element.style.background = '#fff';
17 | element.style.color = '#000';
18 | element.style.padding = '1.5em';
19 | element.style.width = '400px';
20 | element.style.margin = '5em auto 0';
21 |
22 | if ( ! this.webgl ) {
23 |
24 | element.innerHTML = window.WebGLRenderingContext ? [
25 | 'Your graphics card does not seem to support WebGL . ',
26 | 'Find out how to get it here .'
27 | ].join( '\n' ) : [
28 | 'Your browser does not seem to support WebGL . ',
29 | 'Find out how to get it here .'
30 | ].join( '\n' );
31 |
32 | }
33 |
34 | return element;
35 |
36 | },
37 |
38 | addGetWebGLMessage: function ( parameters ) {
39 |
40 | var parent, id, element;
41 |
42 | parameters = parameters || {};
43 |
44 | parent = parameters.parent !== undefined ? parameters.parent : document.body;
45 | id = parameters.id !== undefined ? parameters.id : 'oldie';
46 |
47 | element = Detector.getWebGLErrorMessage();
48 | element.id = id;
49 |
50 | parent.appendChild( element );
51 |
52 | }
53 |
54 | }
55 |
56 | export default Detector
57 |
--------------------------------------------------------------------------------
/server/public/javascripts/scene/mainWindow.js:
--------------------------------------------------------------------------------
1 | import Widget from './widget'
2 | import SurfaceDisplay from './surfaceDisplay'
3 |
4 | require('../helpers/THREEx.WindowResize')
5 |
6 | const DISPLAY_WIDTH = 960, DISPLAY_HEIGHT = 600, DISTANCE = 280, GAP = 1
7 |
8 | export default class MainWindow extends Widget {
9 | THREE = window.THREE || {};
10 | THREEx = window.THREEx || {};
11 |
12 | constructor(videos, width, height) {
13 | super()
14 |
15 | this.videos = videos
16 | this.windowWidth = width
17 | this.windowHeight = height
18 |
19 | this.resizeWidget(this.windowWidth, this.windowHeight)
20 |
21 | this.camera = new THREE.PerspectiveCamera( 60, DISPLAY_WIDTH/DISPLAY_HEIGHT, 0.1, 1000)
22 | this.scene = this.initScene()
23 |
24 | this.camera.position.set(0, 0, 0)
25 | this.camera.lookAt(new THREE.Vector3(0, 0, 1))
26 | this.scene.add(this.camera)
27 |
28 | // this.scene.add(new THREE.AxisHelper(50))
29 |
30 | THREEx.WindowResize(this.renderer, this.camera);
31 |
32 | this.displays = this.createDisplayGroup()
33 | }
34 |
35 | createDisplayGroup = () => {
36 | let displays = []
37 |
38 | let surfaceDisplay = new SurfaceDisplay(this.videos[0], DISPLAY_WIDTH, DISPLAY_HEIGHT)
39 | surfaceDisplay.setPosition(0, -25, -DISTANCE)
40 | this.scene.add(surfaceDisplay.getMesh())
41 | displays.push(surfaceDisplay)
42 |
43 | let surfaceDisplayLeft = new SurfaceDisplay(this.videos[2], DISPLAY_WIDTH, DISPLAY_HEIGHT)
44 | surfaceDisplayLeft.getMesh().rotateY(Math.PI / 2.6)
45 | surfaceDisplayLeft.setPosition(-DISPLAY_WIDTH/3.5, -25, -DISTANCE + 110)
46 | this.scene.add(surfaceDisplayLeft.getMesh())
47 | displays.push(surfaceDisplayLeft)
48 |
49 | let surfaceDisplayRight = new SurfaceDisplay(this.videos[3], DISPLAY_WIDTH, DISPLAY_HEIGHT)
50 | surfaceDisplayRight.getMesh().rotateY(-Math.PI / 2.6)
51 | surfaceDisplayRight.setPosition(DISPLAY_WIDTH/3.5, -25, -DISTANCE + 110)
52 | this.scene.add(surfaceDisplayRight.getMesh())
53 | displays.push(surfaceDisplayRight)
54 |
55 | let surfaceDisplayTop = new SurfaceDisplay(this.videos[1], DISPLAY_WIDTH, DISPLAY_HEIGHT)
56 | surfaceDisplayTop.getMesh().rotateX(Math.PI / 16)
57 | surfaceDisplayTop.setPosition(0, DISPLAY_HEIGHT/3.5, -DISTANCE + 8)
58 | this.scene.add(surfaceDisplayTop.getMesh())
59 | displays.push(surfaceDisplayTop)
60 |
61 |
62 | return displays
63 | }
64 |
65 | initScene = () => {
66 | let scene = new THREE.Scene()
67 |
68 | var floorMaterial = new THREE.MeshBasicMaterial( { wireframe: true, side: THREE.DoubleSide } )
69 | var floorGeometry = new THREE.PlaneGeometry(2000, 2000, 50, 50)
70 | var floor = new THREE.Mesh(floorGeometry, floorMaterial)
71 | floor.position.set(0, -DISPLAY_HEIGHT/2, 0)
72 | floor.rotation.x = Math.PI / 2
73 | // scene.add(floor)
74 |
75 | var imageLoader = new THREE.TextureLoader()
76 | imageLoader.load("/images/uni_lowfi.jpg", function(backgroundTexture) {
77 | var material = new THREE.MeshBasicMaterial({map:backgroundTexture, side: THREE.BackSide})
78 | var skyBox = new THREE.Mesh(
79 | new THREE.SphereGeometry(1000,60,40),
80 | material
81 | );
82 | skyBox.position.set(0, 0, 0)
83 | scene.add(skyBox)
84 | });
85 |
86 | return scene
87 | }
88 |
89 | setVR = (controls, effect) => {
90 | this.effect = effect
91 | this.controls = controls
92 | }
93 |
94 | update = () => {
95 | this.displays.forEach(display => display.update())
96 | this.controls.update()
97 | this.effect.render( this.scene, this.camera )
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/server/public/javascripts/scene/surfaceDisplay.js:
--------------------------------------------------------------------------------
1 | export default class SurfaceDisplay {
2 | THREE = window.THREE || {}
3 |
4 | constructor(video, width = 576, height = 360) {
5 | this.width = width
6 | this.height = height
7 |
8 | this.video = video
9 | this.video.load()
10 |
11 | this.createContextAndTexture()
12 |
13 | this.display = this.createDisplayMesh(this.videoTexture)
14 | }
15 |
16 | createContextAndTexture = () => {
17 | this.videoTexture = new THREE.Texture( this.video )
18 | this.videoTexture.minFilter = THREE.LinearFilter
19 | this.videoTexture.magFilter = THREE.LinearFilter
20 | }
21 |
22 | createDisplayMesh = (texture) => {
23 | texture.flipY = false
24 | const displayMaterial = new THREE.MeshBasicMaterial({ /*wireframe: true, */map: texture, overdraw: true, side: THREE.DoubleSide})
25 | let displayGeometry = new THREE.CylinderGeometry( 1.0, 1.0, this.height/this.width/2.0, 100.0, 100.0, true, -0.25, 0.5 );
26 | displayGeometry.rotateY(-Math.PI).rotateZ(Math.PI)
27 | displayGeometry.scale(this.height, this.height, this.height)
28 | displayGeometry.translate(0, 0, this.height)
29 | return new THREE.Mesh(displayGeometry, displayMaterial)
30 | }
31 |
32 | getMesh = () => this.display
33 |
34 | setPosition = (x, y, z) => {
35 | this.display.position.set(x, y, z)
36 | }
37 |
38 | update = () => {
39 | if (this.video.readyState === this.video.HAVE_ENOUGH_DATA) {
40 | this.videoTexture.needsUpdate = true
41 | }
42 | }
43 |
44 | }
45 |
--------------------------------------------------------------------------------
/server/public/javascripts/scene/widget.js:
--------------------------------------------------------------------------------
1 | import Detector from './detector'
2 |
3 | const SCREEN_WIDTH = window.innerWidth, SCREEN_HEIGHT = window.innerHeight;
4 | const VIEW_ANGLE = 45, ASPECT = SCREEN_WIDTH / SCREEN_HEIGHT, NEAR = 0.1, FAR = 10000;
5 |
6 | export default class Widget {
7 | THREE = window.THREE || {}
8 | constructor() {
9 | this.renderer = this.initRenderer()
10 |
11 | this.camera = new THREE.PerspectiveCamera( VIEW_ANGLE, ASPECT, NEAR, FAR)
12 | this.scene = new THREE.Scene()
13 |
14 | this.init()
15 |
16 | this.animate()
17 | }
18 |
19 | init = () => {
20 | this.camera.position.set(0,0,2)
21 | this.scene.add(this.camera)
22 | this.camera.lookAt(this.scene.position)
23 | }
24 |
25 | initRenderer = () => {
26 | let renderer = null
27 | if (Detector.webgl)
28 | renderer = new THREE.WebGLRenderer({antialias: true})
29 | else
30 | renderer = new THREE.CanvasRenderer()
31 |
32 | renderer.setSize(SCREEN_WIDTH, SCREEN_HEIGHT)
33 |
34 | let container = document.getElementById('ThreeJS')
35 | container.appendChild(renderer.domElement)
36 |
37 | return renderer
38 | }
39 |
40 | resizeWidget = (width, height) => {
41 | this.renderer.setSize(width, height)
42 | }
43 |
44 | animate = () => {
45 | requestAnimationFrame( this.animate )
46 | this.render()
47 | this.update()
48 | this.renderer.render(this.scene, this.camera)
49 | }
50 |
51 | render = () => {
52 | }
53 |
54 | update = () => {
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/server/public/javascripts/webvr/webvr-polyfill-init.js:
--------------------------------------------------------------------------------
1 | require('./webvrconfig');
2 | require('./webvr-polyfill');
3 | InitializeWebVRPolyfill();
4 |
--------------------------------------------------------------------------------
/server/public/javascripts/webvr/webvrconfig.js:
--------------------------------------------------------------------------------
1 | window.WebVRConfig = { DEFER_INITIALIZATION: true }
2 |
--------------------------------------------------------------------------------
/server/public/javascripts/webvrThree/vrcontrol.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @author dmarcos / https://github.com/dmarcos
3 | * @author mrdoob / http://mrdoob.com
4 | * @revisedBy hanystudy / https://github.com/hanystudy
5 | */
6 |
7 | var THREE = window.THREE || {};
8 |
9 | THREE.VRControls = function ( object, onError ) {
10 |
11 | var scope = this;
12 |
13 | var vrDisplay, vrDisplays;
14 |
15 | var standingMatrix = new THREE.Matrix4();
16 |
17 | var frameData = null;
18 |
19 | if ( 'VRFrameData' in window ) {
20 |
21 | frameData = new VRFrameData();
22 |
23 | }
24 |
25 | function gotVRDisplays( displays ) {
26 |
27 | vrDisplays = displays;
28 |
29 | if ( displays.length > 0 ) {
30 |
31 | vrDisplay = displays[ 0 ];
32 |
33 | } else {
34 |
35 | if ( onError ) onError( 'VR input not available.' );
36 |
37 | }
38 |
39 | }
40 |
41 | if ( navigator.getVRDisplays ) {
42 |
43 | navigator.getVRDisplays().then( gotVRDisplays ).catch ( function () {
44 |
45 | console.warn( 'THREE.VRControls: Unable to get VR Displays' );
46 |
47 | } );
48 |
49 | }
50 |
51 | // the Rift SDK returns the position in meters
52 | // this scale factor allows the user to define how meters
53 | // are converted to scene units.
54 |
55 | this.scale = 1;
56 |
57 | // If true will use "standing space" coordinate system where y=0 is the
58 | // floor and x=0, z=0 is the center of the room.
59 | this.standing = false;
60 |
61 | // Distance from the users eyes to the floor in meters. Used when
62 | // standing=true but the VRDisplay doesn't provide stageParameters.
63 | this.userHeight = 1.6;
64 |
65 | this.getVRDisplay = function () {
66 |
67 | return vrDisplay;
68 |
69 | };
70 |
71 | this.setVRDisplay = function ( value ) {
72 |
73 | vrDisplay = value;
74 |
75 | };
76 |
77 | this.getVRDisplays = function () {
78 |
79 | console.warn( 'THREE.VRControls: getVRDisplays() is being deprecated.' );
80 | return vrDisplays;
81 |
82 | };
83 |
84 | this.getStandingMatrix = function () {
85 |
86 | return standingMatrix;
87 |
88 | };
89 |
90 | this.update = function () {
91 |
92 | if ( vrDisplay ) {
93 |
94 | var pose;
95 |
96 | if ( vrDisplay.getFrameData ) {
97 |
98 | vrDisplay.getFrameData( frameData );
99 | pose = frameData.pose;
100 |
101 | } else if ( vrDisplay.getPose ) {
102 |
103 | pose = vrDisplay.getPose();
104 |
105 | }
106 |
107 | if ( pose.orientation !== null ) {
108 |
109 | object.quaternion.fromArray( pose.orientation );
110 |
111 | }
112 |
113 | if ( pose.position !== null ) {
114 |
115 | object.position.fromArray( pose.position );
116 |
117 | } else {
118 |
119 | object.position.set( 0, 0, 0 );
120 |
121 | }
122 |
123 | if ( this.standing ) {
124 |
125 | if ( vrDisplay.stageParameters ) {
126 |
127 | object.updateMatrix();
128 |
129 | standingMatrix.fromArray( vrDisplay.stageParameters.sittingToStandingTransform );
130 | object.applyMatrix( standingMatrix );
131 |
132 | } else {
133 |
134 | object.position.setY( object.position.y + this.userHeight );
135 |
136 | }
137 |
138 | }
139 |
140 | object.position.multiplyScalar( scope.scale );
141 |
142 | }
143 |
144 | };
145 |
146 | this.resetPose = function () {
147 |
148 | if ( vrDisplay ) {
149 |
150 | vrDisplay.resetPose();
151 |
152 | }
153 |
154 | };
155 |
156 | this.resetSensor = function () {
157 |
158 | console.warn( 'THREE.VRControls: .resetSensor() is now .resetPose().' );
159 | this.resetPose();
160 |
161 | };
162 |
163 | this.zeroSensor = function () {
164 |
165 | console.warn( 'THREE.VRControls: .zeroSensor() is now .resetPose().' );
166 | this.resetPose();
167 |
168 | };
169 |
170 | this.dispose = function () {
171 |
172 | vrDisplay = null;
173 |
174 | };
175 |
176 | };
177 |
--------------------------------------------------------------------------------
/server/public/javascripts/webvrThree/vreffect.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @author dmarcos / https://github.com/dmarcos
3 | * @author mrdoob / http://mrdoob.com
4 | * @revisedBy hanystudy / https://github.com/hanystudy
5 | *
6 | * WebVR Spec: http://mozvr.github.io/webvr-spec/webvr.html
7 | *
8 | * Firefox: http://mozvr.com/downloads/
9 | * Chromium: https://webvr.info/get-chrome
10 | *
11 | */
12 |
13 | var THREE = window.THREE || {};
14 |
15 | THREE.VREffect = function( renderer, onError ) {
16 |
17 | var vrDisplay, vrDisplays;
18 | var eyeTranslationL = new THREE.Vector3();
19 | var eyeTranslationR = new THREE.Vector3();
20 | var renderRectL, renderRectR;
21 |
22 | var frameData = null;
23 |
24 | if ( 'VRFrameData' in window ) {
25 |
26 | frameData = new window.VRFrameData();
27 |
28 | }
29 |
30 | function gotVRDisplays( displays ) {
31 |
32 | vrDisplays = displays;
33 |
34 | if ( displays.length > 0 ) {
35 |
36 | vrDisplay = displays[ 0 ];
37 |
38 | } else {
39 |
40 | if ( onError ) onError( 'HMD not available' );
41 |
42 | }
43 |
44 | }
45 |
46 | if ( navigator.getVRDisplays ) {
47 |
48 | navigator.getVRDisplays().then( gotVRDisplays ).catch( function() {
49 |
50 | console.warn( 'THREE.VREffect: Unable to get VR Displays' );
51 |
52 | } );
53 |
54 | }
55 |
56 | //
57 |
58 | this.isPresenting = false;
59 | this.scale = 1;
60 |
61 | var scope = this;
62 |
63 | var rendererSize = renderer.getSize();
64 | var rendererUpdateStyle = false;
65 | var rendererPixelRatio = renderer.getPixelRatio();
66 |
67 | this.getVRDisplay = function() {
68 |
69 | return vrDisplay;
70 |
71 | };
72 |
73 | this.setVRDisplay = function( value ) {
74 |
75 | vrDisplay = value;
76 |
77 | };
78 |
79 | this.getVRDisplays = function() {
80 |
81 | console.warn( 'THREE.VREffect: getVRDisplays() is being deprecated.' );
82 | return vrDisplays;
83 |
84 | };
85 |
86 | this.setSize = function( width, height, updateStyle ) {
87 |
88 | rendererSize = { width: width, height: height };
89 | rendererUpdateStyle = updateStyle;
90 |
91 | if ( scope.isPresenting ) {
92 |
93 | var eyeParamsL = vrDisplay.getEyeParameters( 'left' );
94 | renderer.setPixelRatio( 1 );
95 | renderer.setSize( eyeParamsL.renderWidth * 2, eyeParamsL.renderHeight, false );
96 |
97 | } else {
98 |
99 | renderer.setPixelRatio( rendererPixelRatio );
100 | renderer.setSize( width, height, updateStyle );
101 |
102 | }
103 |
104 | };
105 |
106 | // VR presentation
107 |
108 | var canvas = renderer.domElement;
109 | var defaultLeftBounds = [ 0.0, 0.0, 0.5, 1.0 ];
110 | var defaultRightBounds = [ 0.5, 0.0, 0.5, 1.0 ];
111 |
112 | function onVRDisplayPresentChange() {
113 |
114 | var wasPresenting = scope.isPresenting;
115 | scope.isPresenting = vrDisplay !== undefined && vrDisplay.isPresenting;
116 |
117 | if ( scope.isPresenting ) {
118 |
119 | var eyeParamsL = vrDisplay.getEyeParameters( 'left' );
120 | var eyeWidth = eyeParamsL.renderWidth;
121 | var eyeHeight = eyeParamsL.renderHeight;
122 |
123 | if ( ! wasPresenting ) {
124 |
125 | rendererPixelRatio = renderer.getPixelRatio();
126 | rendererSize = renderer.getSize();
127 |
128 | renderer.setPixelRatio( 1 );
129 | renderer.setSize( eyeWidth * 2, eyeHeight, false );
130 |
131 | }
132 |
133 | } else if ( wasPresenting ) {
134 |
135 | renderer.setPixelRatio( rendererPixelRatio );
136 | renderer.setSize( rendererSize.width, rendererSize.height, rendererUpdateStyle );
137 |
138 | }
139 |
140 | }
141 |
142 | window.addEventListener( 'vrdisplaypresentchange', onVRDisplayPresentChange, false );
143 |
144 | this.setFullScreen = function( boolean ) {
145 |
146 | return new Promise( function( resolve, reject ) {
147 |
148 | if ( vrDisplay === undefined ) {
149 |
150 | reject( new Error( 'No VR hardware found.' ) );
151 | return;
152 |
153 | }
154 |
155 | if ( scope.isPresenting === boolean ) {
156 |
157 | resolve();
158 | return;
159 |
160 | }
161 |
162 | if ( boolean ) {
163 |
164 | resolve( vrDisplay.requestPresent( [ { source: canvas } ] ) );
165 |
166 | } else {
167 |
168 | resolve( vrDisplay.exitPresent() );
169 |
170 | }
171 |
172 | } );
173 |
174 | };
175 |
176 | this.requestPresent = function() {
177 |
178 | return this.setFullScreen( true );
179 |
180 | };
181 |
182 | this.exitPresent = function() {
183 |
184 | return this.setFullScreen( false );
185 |
186 | };
187 |
188 | this.requestAnimationFrame = function( f ) {
189 |
190 | if ( vrDisplay !== undefined ) {
191 |
192 | return vrDisplay.requestAnimationFrame( f );
193 |
194 | } else {
195 |
196 | return window.requestAnimationFrame( f );
197 |
198 | }
199 |
200 | };
201 |
202 | this.cancelAnimationFrame = function( h ) {
203 |
204 | if ( vrDisplay !== undefined ) {
205 |
206 | vrDisplay.cancelAnimationFrame( h );
207 |
208 | } else {
209 |
210 | window.cancelAnimationFrame( h );
211 |
212 | }
213 |
214 | };
215 |
216 | this.submitFrame = function() {
217 |
218 | if ( vrDisplay !== undefined && scope.isPresenting ) {
219 |
220 | vrDisplay.submitFrame();
221 |
222 | }
223 |
224 | };
225 |
226 | this.autoSubmitFrame = true;
227 |
228 | // render
229 |
230 | var cameraL = new THREE.PerspectiveCamera();
231 | cameraL.layers.enable( 1 );
232 |
233 | var cameraR = new THREE.PerspectiveCamera();
234 | cameraR.layers.enable( 2 );
235 |
236 | this.render = function( scene, camera, renderTarget, forceClear ) {
237 |
238 | if ( vrDisplay && scope.isPresenting ) {
239 |
240 | var autoUpdate = scene.autoUpdate;
241 |
242 | if ( autoUpdate ) {
243 |
244 | scene.updateMatrixWorld();
245 | scene.autoUpdate = false;
246 |
247 | }
248 |
249 | var eyeParamsL = vrDisplay.getEyeParameters( 'left' );
250 | var eyeParamsR = vrDisplay.getEyeParameters( 'right' );
251 |
252 | eyeTranslationL.fromArray( eyeParamsL.offset );
253 | eyeTranslationR.fromArray( eyeParamsR.offset );
254 |
255 | if ( Array.isArray( scene ) ) {
256 |
257 | console.warn( 'THREE.VREffect.render() no longer supports arrays. Use object.layers instead.' );
258 | scene = scene[ 0 ];
259 |
260 | }
261 |
262 | // When rendering we don't care what the recommended size is, only what the actual size
263 | // of the backbuffer is.
264 | var size = renderer.getSize();
265 | var layers = vrDisplay.getLayers();
266 | var leftBounds;
267 | var rightBounds;
268 |
269 | if ( layers.length ) {
270 |
271 | var layer = layers[ 0 ];
272 |
273 | leftBounds = layer.leftBounds !== null && layer.leftBounds.length === 4 ? layer.leftBounds : defaultLeftBounds;
274 | rightBounds = layer.rightBounds !== null && layer.rightBounds.length === 4 ? layer.rightBounds : defaultRightBounds;
275 |
276 | } else {
277 |
278 | leftBounds = defaultLeftBounds;
279 | rightBounds = defaultRightBounds;
280 |
281 | }
282 |
283 | renderRectL = {
284 | x: Math.round( size.width * leftBounds[ 0 ] ),
285 | y: Math.round( size.height * leftBounds[ 1 ] ),
286 | width: Math.round( size.width * leftBounds[ 2 ] ),
287 | height: Math.round( size.height * leftBounds[ 3 ] )
288 | };
289 | renderRectR = {
290 | x: Math.round( size.width * rightBounds[ 0 ] ),
291 | y: Math.round( size.height * rightBounds[ 1 ] ),
292 | width: Math.round( size.width * rightBounds[ 2 ] ),
293 | height: Math.round( size.height * rightBounds[ 3 ] )
294 | };
295 |
296 | if ( renderTarget ) {
297 |
298 | renderer.setRenderTarget( renderTarget );
299 | renderTarget.scissorTest = true;
300 |
301 | } else {
302 |
303 | renderer.setRenderTarget( null );
304 | renderer.setScissorTest( true );
305 |
306 | }
307 |
308 | if ( renderer.autoClear || forceClear ) renderer.clear();
309 |
310 | if ( camera.parent === null ) camera.updateMatrixWorld();
311 |
312 | camera.matrixWorld.decompose( cameraL.position, cameraL.quaternion, cameraL.scale );
313 | camera.matrixWorld.decompose( cameraR.position, cameraR.quaternion, cameraR.scale );
314 |
315 | var scale = this.scale;
316 | cameraL.translateOnAxis( eyeTranslationL, scale );
317 | cameraR.translateOnAxis( eyeTranslationR, scale );
318 |
319 | if ( vrDisplay.getFrameData ) {
320 |
321 | vrDisplay.depthNear = camera.near;
322 | vrDisplay.depthFar = camera.far;
323 |
324 | vrDisplay.getFrameData( frameData );
325 |
326 | cameraL.projectionMatrix.elements = frameData.leftProjectionMatrix;
327 | cameraR.projectionMatrix.elements = frameData.rightProjectionMatrix;
328 |
329 | } else {
330 |
331 | cameraL.projectionMatrix = fovToProjection( eyeParamsL.fieldOfView, true, camera.near, camera.far );
332 | cameraR.projectionMatrix = fovToProjection( eyeParamsR.fieldOfView, true, camera.near, camera.far );
333 |
334 | }
335 |
336 | // render left eye
337 | if ( renderTarget ) {
338 |
339 | renderTarget.viewport.set( renderRectL.x, renderRectL.y, renderRectL.width, renderRectL.height );
340 | renderTarget.scissor.set( renderRectL.x, renderRectL.y, renderRectL.width, renderRectL.height );
341 |
342 | } else {
343 |
344 | renderer.setViewport( renderRectL.x, renderRectL.y, renderRectL.width, renderRectL.height );
345 | renderer.setScissor( renderRectL.x, renderRectL.y, renderRectL.width, renderRectL.height );
346 |
347 | }
348 | renderer.render( scene, cameraL, renderTarget, forceClear );
349 |
350 | // render right eye
351 | if ( renderTarget ) {
352 |
353 | renderTarget.viewport.set( renderRectR.x, renderRectR.y, renderRectR.width, renderRectR.height );
354 | renderTarget.scissor.set( renderRectR.x, renderRectR.y, renderRectR.width, renderRectR.height );
355 |
356 | } else {
357 |
358 | renderer.setViewport( renderRectR.x, renderRectR.y, renderRectR.width, renderRectR.height );
359 | renderer.setScissor( renderRectR.x, renderRectR.y, renderRectR.width, renderRectR.height );
360 |
361 | }
362 | renderer.render( scene, cameraR, renderTarget, forceClear );
363 |
364 | if ( renderTarget ) {
365 |
366 | renderTarget.viewport.set( 0, 0, size.width, size.height );
367 | renderTarget.scissor.set( 0, 0, size.width, size.height );
368 | renderTarget.scissorTest = false;
369 | renderer.setRenderTarget( null );
370 |
371 | } else {
372 |
373 | renderer.setViewport( 0, 0, size.width, size.height );
374 | renderer.setScissorTest( false );
375 |
376 | }
377 |
378 | if ( autoUpdate ) {
379 |
380 | scene.autoUpdate = true;
381 |
382 | }
383 |
384 | if ( scope.autoSubmitFrame ) {
385 |
386 | scope.submitFrame();
387 |
388 | }
389 |
390 | return;
391 |
392 | }
393 |
394 | // Regular render mode if not HMD
395 |
396 | renderer.render( scene, camera, renderTarget, forceClear );
397 |
398 | };
399 |
400 | this.dispose = function() {
401 |
402 | window.removeEventListener( 'vrdisplaypresentchange', onVRDisplayPresentChange, false );
403 |
404 | };
405 |
406 | //
407 |
408 | function fovToNDCScaleOffset( fov ) {
409 |
410 | var pxscale = 2.0 / ( fov.leftTan + fov.rightTan );
411 | var pxoffset = ( fov.leftTan - fov.rightTan ) * pxscale * 0.5;
412 | var pyscale = 2.0 / ( fov.upTan + fov.downTan );
413 | var pyoffset = ( fov.upTan - fov.downTan ) * pyscale * 0.5;
414 | return { scale: [ pxscale, pyscale ], offset: [ pxoffset, pyoffset ] };
415 |
416 | }
417 |
418 | function fovPortToProjection( fov, rightHanded, zNear, zFar ) {
419 |
420 | rightHanded = rightHanded === undefined ? true : rightHanded;
421 | zNear = zNear === undefined ? 0.01 : zNear;
422 | zFar = zFar === undefined ? 10000.0 : zFar;
423 |
424 | var handednessScale = rightHanded ? - 1.0 : 1.0;
425 |
426 | // start with an identity matrix
427 | var mobj = new THREE.Matrix4();
428 | var m = mobj.elements;
429 |
430 | // and with scale/offset info for normalized device coords
431 | var scaleAndOffset = fovToNDCScaleOffset( fov );
432 |
433 | // X result, map clip edges to [-w,+w]
434 | m[ 0 * 4 + 0 ] = scaleAndOffset.scale[ 0 ];
435 | m[ 0 * 4 + 1 ] = 0.0;
436 | m[ 0 * 4 + 2 ] = scaleAndOffset.offset[ 0 ] * handednessScale;
437 | m[ 0 * 4 + 3 ] = 0.0;
438 |
439 | // Y result, map clip edges to [-w,+w]
440 | // Y offset is negated because this proj matrix transforms from world coords with Y=up,
441 | // but the NDC scaling has Y=down (thanks D3D?)
442 | m[ 1 * 4 + 0 ] = 0.0;
443 | m[ 1 * 4 + 1 ] = scaleAndOffset.scale[ 1 ];
444 | m[ 1 * 4 + 2 ] = - scaleAndOffset.offset[ 1 ] * handednessScale;
445 | m[ 1 * 4 + 3 ] = 0.0;
446 |
447 | // Z result (up to the app)
448 | m[ 2 * 4 + 0 ] = 0.0;
449 | m[ 2 * 4 + 1 ] = 0.0;
450 | m[ 2 * 4 + 2 ] = zFar / ( zNear - zFar ) * - handednessScale;
451 | m[ 2 * 4 + 3 ] = ( zFar * zNear ) / ( zNear - zFar );
452 |
453 | // W result (= Z in)
454 | m[ 3 * 4 + 0 ] = 0.0;
455 | m[ 3 * 4 + 1 ] = 0.0;
456 | m[ 3 * 4 + 2 ] = handednessScale;
457 | m[ 3 * 4 + 3 ] = 0.0;
458 |
459 | mobj.transpose();
460 |
461 | return mobj;
462 |
463 | }
464 |
465 | function fovToProjection( fov, rightHanded, zNear, zFar ) {
466 |
467 | var DEG2RAD = Math.PI / 180.0;
468 |
469 | var fovPort = {
470 | upTan: Math.tan( fov.upDegrees * DEG2RAD ),
471 | downTan: Math.tan( fov.downDegrees * DEG2RAD ),
472 | leftTan: Math.tan( fov.leftDegrees * DEG2RAD ),
473 | rightTan: Math.tan( fov.rightDegrees * DEG2RAD )
474 | };
475 |
476 | return fovPortToProjection( fovPort, rightHanded, zNear, zFar );
477 |
478 | }
479 |
480 | };
481 |
--------------------------------------------------------------------------------
/server/public/javascripts/webvrThree/webvr.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @author mrdoob / http://mrdoob.com
3 | * @revisedBy hanystudy / https://github.com/hanystudy
4 | *
5 | * Based on @tojiro's vr-samples-utils.js
6 | */
7 |
8 | window.WEBVR = {
9 |
10 | isLatestAvailable: function () {
11 |
12 | console.warn( 'WEBVR: isLatestAvailable() is being deprecated. Use .isAvailable() instead.' );
13 | return this.isAvailable();
14 |
15 | },
16 |
17 | isAvailable: function () {
18 |
19 | return navigator.getVRDisplays !== undefined;
20 |
21 | },
22 |
23 | getMessage: function () {
24 |
25 | var message;
26 |
27 | if ( navigator.getVRDisplays ) {
28 |
29 | navigator.getVRDisplays().then( function ( displays ) {
30 |
31 | if ( displays.length === 0 ) message = 'WebVR supported, but no VRDisplays found.';
32 |
33 | } );
34 |
35 | } else {
36 |
37 | message = 'Your browser does not support WebVR. See webvr.info for assistance.';
38 |
39 | }
40 |
41 | if ( message !== undefined ) {
42 |
43 | var container = document.createElement( 'div' );
44 | container.style.position = 'absolute';
45 | container.style.left = '0';
46 | container.style.top = '0';
47 | container.style.right = '0';
48 | container.style.zIndex = '999';
49 | container.align = 'center';
50 |
51 | var error = document.createElement( 'div' );
52 | error.style.fontFamily = 'sans-serif';
53 | error.style.fontSize = '16px';
54 | error.style.fontStyle = 'normal';
55 | error.style.lineHeight = '26px';
56 | error.style.backgroundColor = '#fff';
57 | error.style.color = '#000';
58 | error.style.padding = '10px 20px';
59 | error.style.margin = '50px';
60 | error.style.display = 'inline-block';
61 | error.innerHTML = message;
62 | container.appendChild( error );
63 |
64 | return container;
65 |
66 | }
67 |
68 | },
69 |
70 | getButton: function ( effect ) {
71 |
72 | var button = document.createElement( 'button' );
73 | button.style.position = 'absolute';
74 | button.style.left = 'calc(50% - 50px)';
75 | button.style.bottom = '20px';
76 | button.style.width = '100px';
77 | button.style.border = '0';
78 | button.style.padding = '8px';
79 | button.style.cursor = 'pointer';
80 | button.style.backgroundColor = '#000';
81 | button.style.color = '#fff';
82 | button.style.fontFamily = 'sans-serif';
83 | button.style.fontSize = '13px';
84 | button.style.fontStyle = 'normal';
85 | button.style.textAlign = 'center';
86 | button.style.zIndex = '999';
87 | button.textContent = 'ENTER VR';
88 | button.onclick = function() {
89 |
90 | effect.isPresenting ? effect.exitPresent() : effect.requestPresent();
91 |
92 | };
93 |
94 | window.addEventListener( 'vrdisplaypresentchange', function ( event ) {
95 |
96 | button.textContent = effect.isPresenting ? 'EXIT VR' : 'ENTER VR';
97 |
98 | }, false );
99 |
100 | return button;
101 |
102 | }
103 |
104 | };
105 |
--------------------------------------------------------------------------------
/server/public/stylesheets/style.css:
--------------------------------------------------------------------------------
1 | body {
2 | padding: 50px;
3 | font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;
4 | }
5 |
6 | a {
7 | color: #00B7FF;
8 | }
9 |
10 | #ThreeJS {
11 | position: absolute;
12 | left:0px;
13 | top:0px;
14 | }
15 |
16 | video {
17 | display: none;
18 | }
19 |
--------------------------------------------------------------------------------
/server/routes/index.js:
--------------------------------------------------------------------------------
1 | var express = require('express');
2 | var router = express.Router();
3 |
4 | /* GET home page. */
5 | router.get('/', function(req, res, next) {
6 | res.render('index');
7 | });
8 |
9 | module.exports = router;
10 |
--------------------------------------------------------------------------------
/server/views/error.jade:
--------------------------------------------------------------------------------
1 | extends layout
2 |
3 | block content
4 | h1= message
5 | h2= error.status
6 | pre #{error.stack}
7 |
--------------------------------------------------------------------------------
/server/views/index.jade:
--------------------------------------------------------------------------------
1 | extends layout
2 |
3 | block content
4 | div#ThreeJS
5 | video#remoteVideo(autoplay="autoplay")
6 |
--------------------------------------------------------------------------------
/server/views/layout.jade:
--------------------------------------------------------------------------------
1 | doctype html
2 | html
3 | head
4 | title= title
5 | link(rel='stylesheet', href='/stylesheets/style.css')
6 | body
7 | block content
8 | script(src='/dist/bundle.js')
9 |
--------------------------------------------------------------------------------
/server/webpack.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | entry: "./public/javascripts/index.js",
3 | output: {
4 | path: __dirname,
5 | filename: "./public/dist/bundle.js"
6 | },
7 | module: {
8 | loaders: [
9 | {
10 | test: /\.(js|jsx)$/,
11 | loader: 'babel-loader',
12 | exclude: /node_modules/,
13 | query: {
14 | babelrc: false,
15 | presets: ['es2015', 'stage-1', 'react']
16 | }
17 | },
18 | ]
19 | }
20 | };
21 |
--------------------------------------------------------------------------------
/src/common/rtcClient.js:
--------------------------------------------------------------------------------
1 | import {socket} from './socketio'
2 |
3 | const offerOptions = {
4 | offerToReceiveAudio: 1,
5 | offerToReceiveVideo: 1
6 | }
7 |
8 | export const rtcClient = (function() {
9 |
10 | let webrtc = null
11 | let checkedSources = {}
12 | let checkedWebRTC = {}
13 |
14 | const init = () => {
15 | socket.on('news', function (data) {})
16 | socket.on('answer', (data) => {
17 | if (webrtc) webrtc.setRemoteDescription(new RTCSessionDescription(data))
18 | });
19 | socket.on('answerCandidate', (data) => {
20 | if (webrtc) webrtc.addIceCandidate(new RTCIceCandidate(data))
21 | });
22 | socket.on('connect_timeout', function () {
23 | socket.disconnect()
24 | })
25 | }
26 |
27 | const createRTCClient = (id, sourceObject) => {
28 | let pc1 = null
29 | checkedSources[id] = sourceObject
30 | if (window.RTCPeerConnection) {
31 | pc1 = new RTCPeerConnection(null)
32 | }
33 | else {
34 | pc1 = new webkitRTCPeerConnection(null)
35 | }
36 | webrtc = pc1
37 | checkedWebRTC[id] = pc1
38 |
39 | function onCreateOfferSuccess(desc) {
40 | pc1.setLocalDescription(desc)
41 | socket.emit('offer', desc)
42 | }
43 | function onCreateSessionDescriptionError(error) { }
44 | pc1.onicecandidate = function(e) {
45 | if (e.candidate) socket.emit('offerCandidate', e.candidate)
46 | }
47 | pc1.addStream(sourceObject.stream)
48 | pc1.createOffer(offerOptions).then(
49 | onCreateOfferSuccess,
50 | onCreateSessionDescriptionError
51 | )
52 | }
53 |
54 | const removeRTCClient = (id, sourceObject) => {
55 | let pc1 = checkedWebRTC[id]
56 | checkedSources[id] = null
57 | pc1.getLocalStreams().forEach((stream) => {
58 | pc1.removeStream(stream)
59 | })
60 | pc1.close()
61 | checkedWebRTC[id] = null
62 | }
63 |
64 | return { init, createRTCClient, removeRTCClient }
65 | })()
66 |
--------------------------------------------------------------------------------
/src/common/socketio.js:
--------------------------------------------------------------------------------
1 | let io = require('socket.io-client')
2 |
3 | export let socket = io('http://localhost:8301')
4 |
--------------------------------------------------------------------------------
/src/components/window.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | export default class Window extends React.Component {
4 | constructor() {
5 | super()
6 | this.state = { video: null }
7 | }
8 |
9 | componentDidMount() {
10 | if (this.props.source) {
11 | let source = this.props.source
12 | const handleStream = (stream) => {
13 | this.setState({
14 | video: ,
15 | title: source.name,
16 | id: source.id,
17 | stream: stream
18 | })
19 | }
20 | const handleError = (e) => {}
21 | navigator.webkitGetUserMedia({
22 | audio: false,
23 | video: {
24 | mandatory: {
25 | chromeMediaSource: 'desktop',
26 | chromeMediaSourceId: source.id,
27 | minWidth: 960,
28 | maxWidth: 960,
29 | minHeight: 600,
30 | maxHeight: 600
31 | }
32 | }
33 | }, handleStream, handleError)
34 | }
35 | }
36 |
37 | windowChange = (e) => {
38 | const target = e.target
39 | const sourceObject = {
40 | checked: target.checked,
41 | stream: this.state.stream
42 | }
43 | this.props.windowChange(target.value, sourceObject)
44 | }
45 |
46 | render() {
47 | return
48 | {this.state.video}
49 |
50 |
51 | {this.state.title}
52 |
53 |
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/components/windowContainer.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Window from './window'
3 | const {desktopCapturer} = require('electron')
4 | import {rtcClient} from '../common/rtcClient'
5 |
6 | export default class WindowContainer extends React.Component {
7 | constructor() {
8 | super()
9 | this.state = {
10 | sources: []
11 | }
12 | }
13 |
14 | componentDidMount() {
15 | rtcClient.init()
16 | desktopCapturer.getSources({types: ['window', 'screen']}, (error, sources) => {
17 | this.setState({sources})
18 | })
19 | }
20 |
21 | windowList() {
22 | return this.state.sources.map((source) => {
23 | return
24 | })
25 | }
26 |
27 | windowChange = (id, sourceObject) => {
28 | if (sourceObject.checked) {
29 | rtcClient.createRTCClient(id, sourceObject)
30 | }
31 | else {
32 | rtcClient.removeRTCClient(id, sourceObject)
33 | }
34 | }
35 |
36 | render() {
37 | return {this.windowList()}
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/renderer.js:
--------------------------------------------------------------------------------
1 | // This file is required by the index.html file and will
2 | // be executed in the renderer process for that window.
3 | // All of the Node.js APIs are available in this process.
4 | // In the renderer process.
5 | import React from 'react'
6 | import ReactDOM from 'react-dom'
7 | import WindowContainer from './components/windowContainer'
8 |
9 | ReactDOM.render(
10 | ,
11 | document.getElementById('root')
12 | )
13 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | entry: "./src/renderer.js",
3 | output: {
4 | path: __dirname,
5 | filename: "./public/bundle.js"
6 | },
7 | target: 'electron-renderer',
8 | module: {
9 | loaders: [
10 | {
11 | test: /\.(js|jsx)$/,
12 | loader: 'babel-loader',
13 | exclude: /node_modules/,
14 | query: {
15 | babelrc: false,
16 | presets: ['es2015', 'stage-1', 'react']
17 | }
18 | },
19 | ]
20 | }
21 | };
22 |
--------------------------------------------------------------------------------