├── Procfile
├── .gitignore
├── html
├── favicon.ico
├── img
│ ├── team.jpg
│ ├── devcon.png
│ └── forge.png
├── js
│ ├── google-analytics-tracker.js
│ ├── stubs.js
│ ├── vr-party-common.js
│ ├── Autodesk.ADN.Viewing.Extension.VR.js
│ ├── jquery.qrcode.min.js
│ ├── Autodesk.ADN.Toolkit.ViewData.js
│ ├── vr-party-participant.js
│ ├── vr-party-participant-stereo.js
│ ├── vr-party-presenter-stereo.js
│ └── vr-party-presenter.js
├── css
│ ├── about.css
│ ├── style.css
│ └── participant.css
├── landing.html
├── participant.html
├── participant2.html
├── index2.html
├── about.html
├── index.html
└── res
│ └── locales
│ └── en
│ └── allstrings.json
├── package.json
├── api.js
├── README.md
├── server.js
└── sockets.js
/Procfile:
--------------------------------------------------------------------------------
1 | web: node server.js
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | credentials.js
2 | node_modules/*
3 |
--------------------------------------------------------------------------------
/html/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeanW/Vrok-It/HEAD/html/favicon.ico
--------------------------------------------------------------------------------
/html/img/team.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeanW/Vrok-It/HEAD/html/img/team.jpg
--------------------------------------------------------------------------------
/html/img/devcon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeanW/Vrok-It/HEAD/html/img/devcon.png
--------------------------------------------------------------------------------
/html/img/forge.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KeanW/Vrok-It/HEAD/html/img/forge.png
--------------------------------------------------------------------------------
/html/js/google-analytics-tracker.js:
--------------------------------------------------------------------------------
1 | (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
2 | (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
3 | m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
4 | })(window,document,'script','//www.google-analytics.com/analytics.js','ga');
5 |
6 | ga('create', 'UA-63934239-1', 'auto');
7 | ga('send', 'pageview');
--------------------------------------------------------------------------------
/html/js/stubs.js:
--------------------------------------------------------------------------------
1 |
2 | (function(global) {
3 | function NoSleep() {};
4 | NoSleep.prototype.enable = function() {};
5 | NoSleep.prototype.disable = function() {};
6 | global.NoSleep = NoSleep;
7 | })(this);
8 |
9 | THREE.DeviceOrientationControls = function () {
10 | this.connect = function() {};
11 | this.update = function() { return null };
12 | this.disconnect = function() {};
13 | };
14 |
15 | VideoHelper = function () {
16 | this.start = function() {};
17 | this.isStarted = function() { return false };
18 | this.stop = function() {};
19 | };
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "VRPartyServer",
3 | "version": "0.1.0dev",
4 | "private": true,
5 | "author": "Kean Walmsley - Autodesk",
6 | "description": "Node.js Server app for the VR Party app",
7 | "contributors": [
8 | {"name": "Kean Walmsley"},
9 | {"name": "Lars Schneider"},
10 | {"name": "Oleg Dedkow"}
11 | ],
12 | "scripts": {
13 | "predeploy": "echo Deploying VRPartyServer",
14 | "postdepoly": "echo VRPartyServer Deployed"
15 | },
16 | "dependencies": {
17 | "socket.io": "*",
18 | "express": "4.x",
19 | "morgan": "1.x",
20 | "request": "*"
21 | },
22 | "engines": {
23 | "node": "0.10.x"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/html/css/about.css:
--------------------------------------------------------------------------------
1 | body {
2 | font-family: 'Merriweather', Times, serif;
3 | padding-top: 0px;
4 | margin: 0px 100px;
5 | text-align: justify;
6 | }
7 |
8 | p {
9 | line-height: 170%;
10 | }
11 |
12 | h1, h2, h3, h4, h5, h6 {
13 | font-family: 'Lato', Arial, sans-serif;
14 | }
15 |
16 | h1, p.desc {
17 | text-align: center;
18 | }
19 |
20 | p.desc {
21 | font-size: 22px;
22 | line-height: 150%;
23 | }
24 |
25 | a, a:link, a:visited, a:visited:hover, a:hover {
26 | text-decoration: none;
27 | }
28 |
29 | a {
30 | color: #006699;
31 | }
32 |
33 | a:hover {
34 | color: #000;
35 | }
36 |
37 | a:active, a:hover {
38 | outline: 0;
39 | }
40 |
41 | img {
42 | margin: 20px auto;
43 | display:block;
44 | width: 60%;
45 | border-color: #555;
46 | border-style: solid;
47 | border-width: 10px;
48 | }
49 |
50 | figure {
51 | margin: 30px;
52 | }
53 |
54 | figcaption {
55 | font-style: italic;
56 | text-align: center;
57 | }
58 |
59 | .container {
60 | position: relative;
61 | left: 10%;
62 | width: 80%;
63 | height: 0;
64 | padding-bottom: 45%;
65 | margin-top: 30px;
66 | margin-bottom: 30px;
67 | }
68 |
69 | .video {
70 | position: absolute;
71 | top: 0%;
72 | left: 0%;
73 | width: 100%;
74 | height: 100%;
75 | box-shadow: 8px 8px 4px #888888;
76 | }
--------------------------------------------------------------------------------
/html/css/style.css:
--------------------------------------------------------------------------------
1 | body {
2 | font-family: 'Helvetica Neue', Arial, sans-serif;
3 | -webkit-text-size-adjust: none;
4 | color: #333;
5 | margin: 0 auto;
6 | padding: 10px;
7 | display: block;
8 | position: relative;
9 | }
10 |
11 | p.info {
12 | font-family: 'Helvetica Neue', Arial, sans-serif;
13 | font-size: 10px;
14 | text-transform: uppercase;
15 | text-align: center;
16 | }
17 |
18 | .cmd-btn-small, .cmd-btn-small-x {
19 | font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
20 | font-size: 12px;
21 | border: 1px solid #555;
22 | border-radius: 2px;
23 | color: #555;
24 | text-transform: uppercase;
25 | text-align: center;
26 | width: 120px;
27 | padding-top: 5px;
28 | padding-bottom: 5px;
29 | }
30 |
31 | .cmd-btn-small {
32 | background: transparent;
33 | }
34 |
35 | .cmd-btn-small-x {
36 | width: 120px;
37 | background: #ccc;
38 | }
39 |
40 | .cmd-btn-small:hover, .cmd-btn-small-x:hover {
41 | cursor: pointer;
42 | color: #fff;
43 | }
44 |
45 | .cmd-btn-small:hover {
46 | background-color: #555;
47 | }
48 | .cmd-btn-small-x:hover {
49 | background-color: #222;
50 | }
51 |
52 | a {
53 | color: blue;
54 | color: hsl( 220, 90%, 40% );
55 | text-decoration: none;
56 | }
57 |
58 | a:hover {
59 | background-color: blue;
60 | background-color: hsl( 220, 90%, 50% );
61 | color: white;
62 | }
63 |
64 | .aboutLayer {
65 | text-indent: -2000px;
66 | }
67 |
68 | .full {
69 | width: 100%;
70 | height: 1800px;
71 | }
72 |
73 | .highlight {
74 | font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
75 | font-size: 18px;
76 | font-weight: bold;
77 | }
--------------------------------------------------------------------------------
/html/landing.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | VR at Autodesk
6 |
7 |
8 |
9 |
10 |
35 |
36 |
37 |
38 |
Mobile VR demos
39 |
40 |
3D Viewer
41 |
42 |
Play
43 |
44 |
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/html/participant.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Vrok-it
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/api.js:
--------------------------------------------------------------------------------
1 |
2 | ///////////////////////////////////////////////////////////////////
3 | // Copyright (c) 2014 Autodesk, Inc. All rights reserved
4 | //
5 | // Permission to use, copy, modify, and distribute this software in
6 | // object code form for any purpose and without fee is hereby granted,
7 | // provided that the above copyright notice appears in all copies and
8 | // that both that copyright notice and the limited warranty and
9 | // restricted rights notice below appear in all supporting
10 | // documentation.
11 | //
12 | // AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS.
13 | // AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF
14 | // MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC.
15 | // DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE
16 | // UNINTERRUPTED OR ERROR FREE.
17 | ///////////////////////////////////////////////////////////////////
18 |
19 | var BASE_URL = 'https://developer.api.autodesk.com';
20 |
21 | var request = require('request');
22 |
23 | function getScopedToken(res, params) {
24 | request.post(BASE_URL + '/authentication/v1/authenticate',
25 | { form: params },
26 | function (error, response, body) {
27 | if (!error && response.statusCode == 200) {
28 | var authResponse = JSON.parse(body);
29 | res.send(authResponse);
30 | }
31 | else {
32 | console.log("Token error: ");
33 | if (response && response.statusCode) {
34 | console.log (response.statusCode);
35 | }
36 | }
37 | }
38 | );
39 | }
40 |
41 | exports.getToken = function (req, res) {
42 |
43 | var params = {
44 | client_id: process.env.CONSUMER_KEY,
45 | client_secret: process.env.CONSUMER_SECRET,
46 | grant_type: 'client_credentials',
47 | scope: 'data:read'
48 | }
49 | getScopedToken(res, params);
50 | };
51 |
52 | exports.getUploadToken = function (req, res) {
53 |
54 | var params = {
55 | client_id: process.env.CONSUMER_KEY,
56 | client_secret: process.env.CONSUMER_SECRET,
57 | grant_type: 'client_credentials',
58 | scope: 'data:read data:write bucket:read bucket:create'
59 | }
60 | getScopedToken(res, params);
61 | };
62 |
63 |
--------------------------------------------------------------------------------
/html/participant2.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Vrok-it
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/html/css/participant.css:
--------------------------------------------------------------------------------
1 | html, body {
2 | font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
3 | width: 100%;
4 | height: 100%;
5 | padding: 0;
6 | margin: -1px;
7 | }
8 |
9 | .cmd-btn {
10 | font-size: 48px;
11 | border: 3px solid #555;
12 | border-radius: 5px;
13 | color: #555;
14 | background: transparent;
15 | text-transform: uppercase;
16 | text-align: center;
17 | padding: 15px;
18 | width: 350px;
19 | left: 50%;
20 | top: 50%;
21 | transform:translate(-50%,-60%);
22 | -webkit-transform:translate(-50%,-60%);
23 | position:absolute;
24 | }
25 |
26 | .message1, .message2 {
27 | font-size: 26px;
28 | color: #888;
29 | background: transparent;
30 | text-transform: uppercase;
31 | text-align: center;
32 | top: 50%;
33 | width: 50%;
34 | position: absolute;
35 | }
36 |
37 | .message1 {
38 | text-shadow: -0.035em 0 #333
39 | }
40 |
41 | .message2 {
42 | text-shadow: 0.035em 0 #333
43 | }
44 |
45 | .blink {
46 | -webkit-animation: blink 2s step-end infinite;
47 | -moz-animation: blink 2s step-end infinite;
48 | -o-animation: blink 2s step-end infinite;
49 | animation: blink 2s step-end infinite;
50 | }
51 |
52 | @-webkit-keyframes blink {
53 | 67% { opacity: 0 }
54 | }
55 |
56 | @-moz-keyframes blink {
57 | 67% { opacity: 0 }
58 | }
59 |
60 | @-o-keyframes blink {
61 | 67% { opacity: 0 }
62 | }
63 |
64 | @keyframes blink {
65 | 67% { opacity: 0 }
66 | }
67 |
68 | .control {
69 | width: 100%;
70 | height: 100%;
71 | }
72 |
73 | .cmd-btn:hover {
74 | cursor: pointer;
75 | color: #fff;
76 | background-color: #555;
77 | }
78 |
79 | .layer1, .layer2, .layer3 {
80 | margin: 3px;
81 | left: 0;
82 | right: 0;
83 | width: 100%;
84 | height: 100%;
85 | }
86 |
87 | .layer1 {
88 | z-index: 2;
89 | display: inline-block;
90 | top: 0;
91 | }
92 |
93 | .layer2 {
94 | z-index: 1;
95 | overflow: hidden;
96 | top: 0;
97 | }
98 |
99 | .layer3 {
100 | z-index: 0;
101 | top: 0;
102 | }
103 |
104 | .outer {
105 | position: relative;
106 | margin: 0;
107 | width: 100%;
108 | height: 100%;
109 | display: table;
110 | }
111 |
112 | .viewer {
113 | width: 50%;
114 | height: 100%;
115 | }
116 |
--------------------------------------------------------------------------------
/html/js/vr-party-common.js:
--------------------------------------------------------------------------------
1 | // Define a String.startsWith() function
2 |
3 | if (typeof String.prototype.startsWith != 'function') {
4 | String.prototype.startsWith = function (str) {
5 | return this.indexOf(str) === 0;
6 | };
7 | }
8 |
9 |
10 | if (typeof String.prototype.ensurePrefix != 'function') {
11 | String.prototype.ensurePrefix = function (str) {
12 | return (this.startsWith(str) ? this : str + this);
13 | };
14 | }
15 |
16 |
17 | function getURLParameter(param) {
18 | var pageURL = window.location.search.substring(1);
19 | var urlVariables = pageURL.split('&');
20 | for (var i = 0; i < urlVariables.length; i++) {
21 | var paramName = urlVariables[i].split('=');
22 | if (paramName[0] === param) {
23 | return paramName[1];
24 | }
25 | }
26 | return null;
27 | }
28 |
29 |
30 | function getViewingOptions() {
31 | var options = {};
32 | options.env = 'AutodeskProduction';
33 | options.useConsolidation = true;
34 | options.getAccessToken = function(onSuccess) {
35 | $.get(
36 | window.location.origin + '/api/token',
37 | function (accessTokenResponse) {
38 | onSuccess(
39 | accessTokenResponse.access_token,
40 | accessTokenResponse.expires_in
41 | );
42 | }
43 | );
44 | };
45 | return options;
46 | }
47 |
48 |
49 | function getScopedViewingOptions(urn) {
50 | var getToken = function() {
51 | var token = null;
52 | jQuery.ajax({
53 | url: window.location.origin + '/api/token',
54 | success: function (result) {
55 | token = result.access_token;
56 | },
57 | async: false
58 | });
59 | return token;
60 | };
61 | var options = {
62 | 'document' : urn.ensurePrefix('urn:'),
63 | 'env' : 'AutodeskProduction',
64 | 'getAccessToken' : getToken,
65 | 'refreshToken' : getToken
66 | };
67 | return options;
68 | }
69 |
70 |
71 | function loadModel(viewer, documentData) {
72 | viewer.resize();
73 | viewer.load(
74 | documentData,
75 | null,
76 | function() {
77 | viewer.navigation.setZoomTowardsPivot(true);
78 | viewer.navigation.setReverseZoomDirection(true);
79 | viewer.setLightPreset(0);
80 | }
81 | );
82 | }
83 |
84 |
85 | function getModel(documentData) {
86 | var geometryItems = [];
87 | if (geometryItems.length == 0) {
88 | geometryItems = Autodesk.Viewing.Document.getSubItemsWithProperties(
89 | documentData.getRootItem(),
90 | { 'type': 'geometry', 'role': '3d' },
91 | true
92 | );
93 | }
94 | if (geometryItems.length === 0) {
95 | return null;
96 | }
97 | return documentData.getViewablePath(geometryItems[0]);
98 | }
99 |
100 | // From http://davidwalsh.name/essential-javascript-functions
101 | //
102 | // Returns a function, that, as long as it continues to be invoked, will not
103 | // be triggered. The function will be called after it stops being called for
104 | // N milliseconds. If `immediate` is passed, trigger the function on the
105 | // leading edge, instead of the trailing.
106 | function debounce(func, wait, immediate) {
107 | var timeout;
108 | return function() {
109 | var context = this, args = arguments;
110 | var later = function() {
111 | timeout = null;
112 | if (!immediate) func.apply(context, args);
113 | };
114 | var callNow = immediate && !timeout;
115 | clearTimeout(timeout);
116 | timeout = setTimeout(later, wait);
117 | if (callNow) func.apply(context, args);
118 | };
119 | };
--------------------------------------------------------------------------------
/html/index2.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Vrok-it
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | Scan to connect with Google Cardboard then load a model from the list below
25 |
26 |
27 |
28 | Upload file
33 |
34 |
35 |
36 |
37 | New session
42 |
43 |
44 |
51 |
52 |
53 |
54 |
55 |
58 |
64 |
65 |
66 |
67 |
68 |
69 |
--------------------------------------------------------------------------------
/html/about.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Welcome to Vrok-it!
6 |
7 |
8 |
9 |
10 | Welcome to Vrok-it!
11 |
12 | "Vrok" is a combination of Virtual Reality Online Collaboration and the verb to grok . Vrok-it allows multiple people to grok a 3D model in a way that simply wasn’t possible before.
13 |
14 | Getting started
15 |
16 | Click the QR code – or scan it from your phone – to launch a stereoscopic 3D viewer for Google Cardboard * connected to this session. The 3D models you load via this page – and operations you perform on them – will be visible to all connected viewers. You can upload your own models, but for best results keep these under 2MB in size.
17 |
18 | Here's a video of Vrok-it in action showing both this master page and a connected client device:
19 |
20 |
21 |
22 | Origins
23 |
24 | Vrok-it was born in May 2015, when three software engineers from Autodesk entered a team in the VR Hackathon in San Francisco . The goal of the "VR Party" team was to enable a guide to assist someone immersed in virtual reality, essentially curating the VR experience. The guide would perform actions that the VR participant might otherwise find cumbersome, such as exploding a 3D model, zooming in on it, or cutting it with a section plane. Making VR more personal and social makes it an ideal tool for communicating design information, for architectural fly-throughs, site walk-throughs and model review.
25 |
26 |
27 | The team soon realized it would be easy to allow the guide to curate the VR experience for multiple participants, much in the same way as proposed by Google Expeditions, announced just the week after at Google I/O 2015.
28 |
29 |
30 |
31 | The team behind Vrok-it is (left to right) Lars Schneider , Kean Walmsley and Oleg Dedkow.
32 |
33 |
34 | The project was well-received and was awarded the Hackathon prize for the "Best Web-based VR Project".
35 |
36 |
37 | Over the following weeks the team implemented additional features, such as making it possible for people to create and manage simultaneous sessions. When launched "VR Party" became Vrok-it.
38 |
39 | Technology
40 |
41 | At its core, the project makes use of the Autodesk View & Data API , a web-based infrastructure for viewing 3D models, and leverages work previously published on Kean's blog . It has a Node.js back-end, with Socket.io handling the communication between presenter and participants. The site is hosted on Heroku and the complete source code for Vrok-it is available on GitHub .
42 |
43 |
44 | * We recommend these excellent googles from GoggleTech .
45 |
46 |
47 |
--------------------------------------------------------------------------------
/html/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Vrok-it
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 | Scan to connect with Google Cardboard then load a model from the list below
27 |
28 |
29 |
30 | Upload file
35 |
36 |
37 |
38 |
39 | New session
44 |
45 |
46 |
53 |
54 |
55 |
56 |
57 |
60 |
66 |
67 |
68 |
69 |
70 |
71 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Vrok-It
2 | _View and collaborate on 3D models inside Virtual Reality using Autodesk's Forge platform._
3 |
4 | ## What does Vrok mean?
5 | "Vrok" is a combination of Virtual Reality Online Collaboration and the verb to grok. Vrok-it allows multiple people to grok a 3D model in a way that simply wasn’t possible before.
6 |
7 | ## Origins
8 | Vrok-it was born in May 2015, when three software engineers from Autodesk entered a team in the VR Hackathon in San Francisco. The goal of the "VR Party" team was to enable a guide to assist someone immersed in virtual reality, essentially curating the VR experience. The guide would perform actions that the VR participant might otherwise find cumbersome, such as exploding a 3D model, zooming in on it, or cutting it with a section plane. Making VR more personal and social makes it an ideal tool for communicating design information, for architectural fly-throughs, site walk-throughs and model review.
9 |
10 | The team soon realized it would be easy to allow the guide to curate the VR experience for multiple participants, much in the same way as proposed by Google Expeditions, announced just the week after at Google I/O 2015.
11 |
12 | The project was well-received and was awarded the Hackathon prize for the "Best Web-based VR Project".
13 |
14 | Over the following weeks the team implemented additional features, such as making it possible for people to create and manage simultaneous sessions. When launched "VR Party" became Vrok-it.
15 |
16 | ## How to use Vrok-It?
17 | On your "master" system - typically a PC or tablet - head on over to http://vrok.it (or http://www.vrok.it/v2 for the experimental, single-viewer version).
18 | Click the QR code – or scan it from your phone – to launch a stereoscopic 3D viewer (typically on a secondary device, such as a Google Cardboard-ready smartphone) connected to this session. The 3D models you load via the master page – and operations you perform on them – will be visible to all connected viewers. You can upload your own models, but for best results keep these under 2MB in size.
19 |
20 | ## Technology
21 | At its core, the project makes use of Autodesk's Forge platform, a web-based infrastructure that can be used for viewing 3D models, and leverages work previously published on Kean's blog. It has a Node.js back-end, with Socket.io handling the communication between presenter and participants. The site is hosted on Heroku and the complete source code for Vrok-it is available in this repository on GitHub.
22 |
23 | ## Supporting links
24 |
25 | Here are some blog posts tracking the evolution of Vrok-It:
26 |
27 | * [Cooling down after the SF VR Hackathon] (http://through-the-interface.typepad.com/through_the_interface/2015/05/cooling-down-after-the-sf-vr-hackathon.html)
28 | * [Our collaborative VR demo from the recent Hackathon] (http://through-the-interface.typepad.com/through_the_interface/2015/06/our-collaborative-vr-demo-from-the-recent-hackathon.html)
29 | * [A fun, collaborative VR tool: can you Vrok-it?] (http://through-the-interface.typepad.com/through_the_interface/2015/06/a-fun-collaborative-vr-tool-can-you-vrok-it.html)
30 | * [WebVR in Chrome and Forge] (http://through-the-interface.typepad.com/through_the_interface/2017/02/webvr-in-chrome-and-forge.html)
31 |
32 | The project built on work done previously related to using the Forge viewer for VR:
33 |
34 | * [Gearing up for the VR Hackathon] (http://through-the-interface.typepad.com/through_the_interface/2014/10/gearing-up-for-the-vr-hackathon.html)
35 | * [Creating a stereoscopic viewer for Google Cardboard using the Autodesk 360 viewer - Part 1] (http://through-the-interface.typepad.com/through_the_interface/2014/10/creating-a-stereoscopic-viewer-for-google-cardboard-using-the-autodesk-360-viewer-part-1.html)
36 | * [Creating a stereoscopic viewer for Google Cardboard using the Autodesk 360 viewer - Part 2] (http://through-the-interface.typepad.com/through_the_interface/2014/10/creating-a-stereoscopic-viewer-for-google-cardboard-using-the-autodesk-360-viewer-part-2.html)
37 | * [Creating a stereoscopic viewer for Google Cardboard using the Autodesk 360 viewer - Part 3] (http://through-the-interface.typepad.com/through_the_interface/2014/10/creating-a-stereoscopic-viewer-for-google-cardboard-using-the-autodesk-360-viewer-part-3.html)
38 |
39 |
--------------------------------------------------------------------------------
/html/js/Autodesk.ADN.Viewing.Extension.VR.js:
--------------------------------------------------------------------------------
1 | /////////////////////////////////////////////////////////////////////
2 | // Autodesk.ADN.Viewing.Extension.VR
3 | // by Philippe Leefsma, May 2015
4 | //
5 | /////////////////////////////////////////////////////////////////////
6 | AutodeskNamespace("Autodesk.ADN.Viewing.Extension");
7 |
8 | Autodesk.ADN.Viewing.Extension.VR = function (viewer, options) {
9 |
10 | Autodesk.Viewing.Extension.call(this, viewer, options);
11 |
12 | var _panel = null;
13 |
14 | /////////////////////////////////////////////////////////////////
15 | // Extension load callback
16 | //
17 | /////////////////////////////////////////////////////////////////
18 | this.load = function () {
19 |
20 | _panel = new Panel(
21 | viewer.container,
22 | guid());
23 |
24 | if(options.requestUserGesture) {
25 |
26 | _panel.setVisible(true);
27 | }
28 | else {
29 |
30 | activateVR();
31 | }
32 |
33 | console.log('Autodesk.ADN.Viewing.Extension.VR loaded');
34 |
35 | return true;
36 | }
37 |
38 | /////////////////////////////////////////////////////////////////
39 | // Activate VR
40 | //
41 | /////////////////////////////////////////////////////////////////
42 | function activateVR() {
43 |
44 | // viewer.setProgressiveRendering(false);
45 |
46 | //hide controls
47 | $('.adsk-control-group').each(function(){
48 |
49 | $(this).find('>.adsk-button').each(function(){
50 |
51 | $(this).css({
52 | 'display':'none'
53 | });
54 | });
55 | });
56 |
57 | $('.homeViewWrapper').css({ 'display':'none' });
58 |
59 | viewer.loadExtension('Autodesk.Viewing.WebVR', { experimental: [ 'webVR_orbitModel' ] });
60 |
61 | viewer.displayViewCube(false);
62 |
63 | setTimeout(function() {
64 |
65 | //viewer.setActiveNavigationTool('vr');
66 |
67 | }, 3000);
68 | }
69 |
70 | /////////////////////////////////////////////////////////////////
71 | // Extension unload callback
72 | //
73 | /////////////////////////////////////////////////////////////////
74 | this.unload = function () {
75 |
76 | _panel.setVisible(false);
77 |
78 | console.log('Autodesk.ADN.Viewing.Extension.VR unloaded');
79 |
80 | return true;
81 | }
82 |
83 | /////////////////////////////////////////////////////////////////
84 | // Generates random guid to use as DOM id
85 | //
86 | /////////////////////////////////////////////////////////////////
87 | function guid() {
88 |
89 | var d = new Date().getTime();
90 |
91 | var guid = 'xxxx-xxxx-xxxx-xxxx'.replace(
92 | /[xy]/g,
93 | function (c) {
94 | var r = (d + Math.random() * 16) % 16 | 0;
95 | d = Math.floor(d / 16);
96 | return (c == 'x' ? r : (r & 0x7 | 0x8)).toString(16);
97 | });
98 |
99 | return guid;
100 | }
101 |
102 | /////////////////////////////////////////////////////////////////
103 | // The demo Panel
104 | //
105 | /////////////////////////////////////////////////////////////////
106 | var Panel = function(
107 | parentContainer, id) {
108 |
109 | var _thisPanel = this;
110 |
111 | _thisPanel.content = document.createElement('div');
112 |
113 | Autodesk.Viewing.UI.DockingPanel.call(
114 | this,
115 | parentContainer,
116 | id,
117 | 'Switch to VR Mode?',
118 | {shadow:true});
119 |
120 | $(_thisPanel.container).addClass('vr-selector');
121 |
122 | var w = 270;
123 | var h = 115;
124 |
125 | $(_thisPanel.container).css({
126 |
127 | 'left' : 'calc(50vw - ' + w * 0.5 + 'px)',
128 | 'top' : 'calc(50vh - ' + h * 0.5 + 'px)',
129 | 'width' : w + 'px',
130 | 'height': h + 'px',
131 | 'resize': 'none'
132 | });
133 |
134 | /////////////////////////////////////////////////////////////
135 | // Custom html
136 | //
137 | /////////////////////////////////////////////////////////////
138 | var html = [
139 |
140 | ''
157 | ];
158 |
159 | $(_thisPanel.container).append(html.join('\n'));
160 |
161 | $('#' + id + '-ok-btn').click(onOkButtonClicked);
162 |
163 | $('#' + id + '-cancel-btn').click(onCancelButtonClicked);
164 |
165 | /////////////////////////////////////////////////////////////
166 | // button clicked handler
167 | //
168 | /////////////////////////////////////////////////////////////
169 | function onOkButtonClicked(event) {
170 |
171 | event.preventDefault();
172 |
173 | _thisPanel.setVisible(false);
174 |
175 | activateVR();
176 | }
177 |
178 | function onCancelButtonClicked(event) {
179 |
180 | event.preventDefault();
181 |
182 | _thisPanel.setVisible(false);
183 | }
184 |
185 | /////////////////////////////////////////////////////////////
186 | // setVisible override (not used in that sample)
187 | //
188 | /////////////////////////////////////////////////////////////
189 | _thisPanel.setVisible = function(show) {
190 |
191 | Autodesk.Viewing.UI.DockingPanel.prototype.
192 | setVisible.call(this, show);
193 | }
194 |
195 | /////////////////////////////////////////////////////////////
196 | // initialize override
197 | //
198 | /////////////////////////////////////////////////////////////
199 | _thisPanel.initialize = function() {
200 |
201 | this.title = this.createTitleBar(
202 | this.titleLabel ||
203 | this.container.id);
204 |
205 | }
206 | }
207 |
208 | /////////////////////////////////////////////////////////////
209 | // Set up JS inheritance
210 | //
211 | /////////////////////////////////////////////////////////////
212 | Panel.prototype = Object.create(
213 | Autodesk.Viewing.UI.DockingPanel.prototype);
214 |
215 | Panel.prototype.constructor = Panel;
216 |
217 | /////////////////////////////////////////////////////////////
218 | // Add needed CSS
219 | //
220 | /////////////////////////////////////////////////////////////
221 | var css = [
222 |
223 | 'form.vr-selector-controls{',
224 | 'margin: 20px;',
225 | '}',
226 |
227 | 'input.vr-selector-name {',
228 | 'height: 30px;',
229 | 'margin-left: 5px;',
230 | 'margin-bottom: 5px;',
231 | 'margin-top: 5px;',
232 | 'border-radius:5px;',
233 | '}',
234 |
235 | 'div.vr-selector {',
236 | 'top: 0px;',
237 | 'left: 0px;',
238 | 'width: 270px;',
239 | 'height: 115px;',
240 | 'resize: none;',
241 | '}',
242 |
243 | 'div.vr-selector-minimized {',
244 | 'height: 34px;',
245 | 'min-height: 34px',
246 | '}',
247 |
248 | 'button.btn-vr-selector {',
249 | 'width: 80px',
250 | '}'
251 |
252 | ].join('\n');
253 |
254 | $('').appendTo('head');
255 | };
256 |
257 | Autodesk.ADN.Viewing.Extension.VR.prototype =
258 | Object.create(Autodesk.Viewing.Extension.prototype);
259 |
260 | Autodesk.ADN.Viewing.Extension.VR.prototype.constructor =
261 | Autodesk.ADN.Viewing.Extension.VR;
262 |
263 | Autodesk.Viewing.theExtensionManager.registerExtension(
264 | 'Autodesk.ADN.Viewing.Extension.VR',
265 | Autodesk.ADN.Viewing.Extension.VR);
--------------------------------------------------------------------------------
/server.js:
--------------------------------------------------------------------------------
1 | var express = require('express');
2 | var api = require('./api');
3 | var http = require('http');
4 | var crypto = require('crypto');
5 |
6 | // CONFIG
7 | var port = process.env.PORT || 5000;
8 |
9 | // WEB SERVER
10 | var app = express();
11 |
12 | app.use(function (req, res, next) {
13 | res.header('Access-Control-Allow-Origin', '*');
14 | res.header('Access-Control-Allow-Headers', 'X-Requested-With');
15 | next();
16 | });
17 | app.use('/', express.static(__dirname + '/html'));
18 | app.use('/v2', express.static(__dirname + '/html/index2.html'));
19 |
20 | app.get('/api/token', api.getToken);
21 |
22 | app.get('/api/uploadtoken', api.getUploadToken);
23 |
24 | app.get('/api/sessionId', function(req, res) {
25 | var sessionId;
26 | do {
27 | sessionId = randomValueBase64(6);
28 | console.log("generated session id: " + sessionId);
29 | }
30 | while (sessionIds.indexOf(sessionId) > -1);
31 | res.json(sessionId);
32 | });
33 |
34 | app.get('/join', function(req, res) {
35 | var id = req.query.id;
36 | res.redirect('/participant.html?session=' + id);
37 | });
38 |
39 | // Currently only return the URN - could also return
40 | // the various explode, zoom factors, etc.
41 | app.get('/api/getSession/:id', function(req, res) {
42 | var sessionId = req.params.id;
43 | var idx = sessionIds.indexOf(sessionId);
44 | res.json(idx < 0 ? "" : models[idx]);
45 | });
46 |
47 | var server = http.createServer(app);
48 | server.listen(port);
49 | console.log('Listening on port ' + port + '...');
50 |
51 | var sessionIds = [];
52 | var models = [];
53 | var zoomFactors = [];
54 | var explodeFactors = [];
55 | var isolateIds = [];
56 | var hideIds = [];
57 | var showIds = [];
58 | var sectionPlanes = [];
59 |
60 | var defZoom = null;
61 | var defExplode = 0;
62 | var defIsolate = [];
63 | var defHide = [];
64 | var defShow = [];
65 | var defSection = [];
66 |
67 | // WEB SOCKETS
68 | var io = require('socket.io')(server);
69 | io.on('connection', function(socket) {
70 | console.log('a user connected (id=' + socket.id +')');
71 |
72 | socket.on('create-session', function(session) {
73 | console.log('session created (id=' + session.id +')');
74 | // Add our session info to the beginning of our various arrays
75 | sessionIds.unshift(session.id);
76 | models.unshift(null);
77 | zoomFactors.unshift(defZoom);
78 | explodeFactors.unshift(defExplode);
79 | isolateIds.unshift(defIsolate);
80 | hideIds.unshift(defHide);
81 | showIds.unshift(defShow);
82 | sectionPlanes.unshift(defSection);
83 | });
84 |
85 | socket.on('join-session', function(session) {
86 | console.log('user joined session (id=' + session.id +')');
87 | var idx = sessionIds.indexOf(session.id);
88 | if (idx > -1) {
89 |
90 | // Add our user to the room for this session
91 | socket.join(session.id);
92 |
93 | if (models[idx]) {
94 | // Bring this user up to speed with the state of the session
95 | emitDirectAndLog(socket, { name: 'load', value: models[idx] });
96 | if (zoomFactors[idx] !== defZoom) {
97 | emitDirectAndLog(socket, { name: 'zoom', value: zoomFactors[idx] });
98 | }
99 | if (explodeFactors[idx] > defExplode) {
100 | emitDirectAndLog(socket, { name: 'explode', value: explodeFactors[idx] });
101 | }
102 | if (isolateIds[idx] !== defIsolate) {
103 | emitDirectAndLog(socket, { name: 'isolate', value: isolateIds[idx] });
104 | }
105 | if (hideIds[idx] !== defHide) {
106 | emitDirectAndLog(socket, { name: 'hide', value: hideIds[idx] });
107 | }
108 | if (showIds[idx] !== defShow) {
109 | emitDirectAndLog(socket, { name: 'show', value: showIds[idx] });
110 | }
111 | if (sectionPlanes[idx] !== defSection) {
112 | emitDirectAndLog(socket, { name: 'section', value: sectionPlanes[idx] });
113 | }
114 | }
115 | }
116 | else {
117 | console.log('could not find session (id=' + session.id +')');
118 | emitDirectAndLog(socket, { name: 'load' });
119 | }
120 | });
121 |
122 | socket.on('close-session', function(session) {
123 | var idx = sessionIds.indexOf(session.id);
124 | if (idx > -1) {
125 | // Clear the model for participants
126 | emitToGroupAndLog({ session: session.id, name: 'load', value: '' });
127 |
128 | // Clean up state
129 | sessionIds.splice(idx, 1);
130 | models.splice(idx, 1);
131 | zoomFactors.splice(idx, 1);
132 | explodeFactors.splice(idx, 1);
133 | isolateIds.splice(idx, 1);
134 | hideIds.splice(idx, 1);
135 | showIds.splice(idx, 1);
136 | sectionPlanes.splice(idx, 1);
137 |
138 | console.log('session closed (id=' + session.id +')');
139 | }
140 | });
141 |
142 | socket.on('disconnect', function() {
143 | console.log('a user disconnected (id=' + socket.id +')');
144 | });
145 |
146 | socket.on('lmv-command', function(command) {
147 | var idx = sessionIds.indexOf(command.session);
148 | if (idx > -1) {
149 | if (command.name === 'load') {
150 | // Create our default settings for the model
151 | models[idx] = command.value;
152 | zoomFactors[idx] = defZoom;
153 | explodeFactors[idx] = defExplode;
154 | isolateIds[idx] = defIsolate;
155 | hideIds[idx] = defHide;
156 | showIds[idx] = defShow;
157 | sectionPlanes[idx] = defSection;
158 |
159 | // Emit the load command
160 | emitToGroupAndLog(command);
161 |
162 | // Emit the defaults to the group participants (no need for hide/show)
163 | emitToGroupAndLog({ session: command.session, name: 'zoom', value: defZoom });
164 | emitToGroupAndLog({ session: command.session, name: 'explode', value: defExplode });
165 | emitToGroupAndLog({ session: command.session, name: 'isolate', value: defIsolate });
166 | emitToGroupAndLog({ session: command.session, name: 'section', value: defSection });
167 | }
168 | else {
169 | if (command.name === 'zoom') {
170 | zoomFactors[idx] = command.value;
171 | }
172 | else if (command.name === 'explode') {
173 | explodeFactors[idx] = command.value;
174 | }
175 | else if (command.name === 'isolate') {
176 | isolateIds[idx] = command.value;
177 | if (command.value == defIsolate) {
178 | hideIds[idx] = defHide;
179 | showIds[idx] = defShow;
180 | }
181 | }
182 | else if (command.name === 'hide') {
183 | hideIds[idx] = hideIds[idx].concat(command.value);
184 | showIds[idx] = stripIds(showIds[idx], command.value);
185 | }
186 | else if (command.name === 'show') {
187 | showIds[idx] = showIds[idx].concat(command.value);
188 | hideIds[idx] = stripIds(hideIds[idx], command.value);
189 | }
190 | else if (command.name === 'section') {
191 | sectionPlanes[idx] = command.value;
192 | }
193 | emitToGroupAndLog(command);
194 | }
195 | }
196 | else {
197 | console.log('could not find session (id=' + command.session +')');
198 | }
199 | });
200 | });
201 |
202 | function stripIds(existing, ids) {
203 | for (var i = 0; i < ids.length; i++) {
204 | var idx = existing.indexOf(ids[i]);
205 | if (idx > -1) {
206 | existing.splice(idx, 1);
207 | }
208 | }
209 | return existing;
210 | }
211 |
212 | function emitDirectAndLog(socket, command) {
213 | socket.emit('lmv-command', command);
214 | console.log(command);
215 | }
216 |
217 | function emitToGroupAndLog(command) {
218 | io.to(command.session).emit('lmv-command', command);
219 | console.log(command);
220 | }
221 |
222 | function randomValueBase64 (len) {
223 | return crypto.randomBytes(Math.ceil(len * 3 / 4))
224 | .toString('base64') // convert to base64 format
225 | .slice(0, len) // return required number of characters
226 | .replace(/\+/g, '0') // replace '+' with '0'
227 | .replace(/\//g, '0'); // replace '/' with '0'
228 | }
--------------------------------------------------------------------------------
/sockets.js:
--------------------------------------------------------------------------------
1 | var socketio = require('socket.io').listen(5001);
2 | var express = require('express');
3 | var request = require('request');
4 | var path = require('path');
5 |
6 | var router = express.Router();
7 |
8 | ///////////////////////////////////////////////////////////////////////////////
9 | //
10 | //
11 | ///////////////////////////////////////////////////////////////////////////////
12 |
13 | router.get('/', function(req, res) {
14 |
15 | var rootPath = path.join(
16 | __dirname,
17 | '.');
18 |
19 | res.sendFile('index.html', { root: rootPath });
20 | });
21 |
22 | ///////////////////////////////////////////////////////////////////////////////
23 | //
24 | //
25 | ///////////////////////////////////////////////////////////////////////////////
26 | router.initializeSocket = function(serverApp) {
27 |
28 | var tracker = {};
29 |
30 | var showcaseData = null;
31 |
32 | ///////////////////////////////////////////////////////////////////////
33 | //
34 | //
35 | ///////////////////////////////////////////////////////////////////////
36 | function initShowcaseData() {
37 |
38 | showcaseData = {
39 | controllingUser: null,
40 | urn: '',
41 | view: null,
42 | isolateIds: null,
43 | explodeScale: 0.0
44 | };
45 | }
46 |
47 | ///////////////////////////////////////////////////////////////////////
48 | //
49 | //
50 | ///////////////////////////////////////////////////////////////////////
51 | function removeUser(socketId, user) {
52 |
53 | tracker[socketId].user = null;
54 |
55 | emitAll('removeUser', user);
56 |
57 | var msg = {
58 | user: user,
59 | text: '> ' + '' + user.name + ' ' +
60 | ' left the showcase '
61 | }
62 |
63 | var activeUsers = 0;
64 |
65 | for (var key in tracker) {
66 |
67 | if(tracker[key].user)
68 | ++activeUsers;
69 |
70 | tracker[key].socket.emit('chatMessage', msg);
71 | }
72 |
73 | // clears showcase data if no more active users
74 | if(activeUsers == 0) {
75 |
76 | initShowcaseData();
77 | }
78 | }
79 |
80 | ///////////////////////////////////////////////////////////////////////
81 | //
82 | //
83 | ///////////////////////////////////////////////////////////////////////
84 | function emitAll(signalId, data) {
85 |
86 | for (var key in tracker) {
87 |
88 | tracker[key].socket.emit(signalId, data);
89 | }
90 | }
91 |
92 | ///////////////////////////////////////////////////////////////////////
93 | //
94 | //
95 | ///////////////////////////////////////////////////////////////////////
96 | function emitExclude(socketId, signalId, data) {
97 |
98 | for (var key in tracker) {
99 |
100 | if(key !== socketId) {
101 |
102 | tracker[key].socket.emit(signalId, data);
103 | }
104 | }
105 | }
106 |
107 | ///////////////////////////////////////////////////////////////////////////
108 | //
109 | //
110 | ///////////////////////////////////////////////////////////////////////////
111 |
112 | initShowcaseData();
113 |
114 | var io = socketio.listen(serverApp, { log: false });
115 |
116 | io.sockets.on('connection', function (socket) {
117 |
118 | console.log('Incoming socket connection: ' + socket.id);
119 |
120 | tracker[socket.id] = {
121 | socket: socket,
122 | user: null
123 | };
124 |
125 | ///////////////////////////////////////////////////////////////////////
126 | //
127 | //
128 | ///////////////////////////////////////////////////////////////////////
129 | function buildInitData() {
130 |
131 | var users = [];
132 |
133 | for (var key in tracker) {
134 |
135 | if(tracker[key].user)
136 | users.push(tracker[key].user);
137 | }
138 |
139 | var initData = {
140 | users: users,
141 | socketId: socket.id,
142 | showcaseData: showcaseData
143 | };
144 |
145 | return initData;
146 | }
147 |
148 | ///////////////////////////////////////////////////////////////////////
149 | //
150 | //
151 | ///////////////////////////////////////////////////////////////////////
152 | socket.on('requestData', function () {
153 |
154 | socket.emit('showcaseData', buildInitData());
155 | });
156 |
157 | ///////////////////////////////////////////////////////////////////////
158 | //
159 | //
160 | ///////////////////////////////////////////////////////////////////////
161 | socket.on('requestControl', function (user) {
162 |
163 | // grants control with no further check
164 |
165 | if(user.hasControl) {
166 |
167 | if(showcaseData.controllingUser) {
168 |
169 | //current controlling user looses control
170 | showcaseData.controllingUser.hasControl = false;
171 |
172 | emitAll('controlEvent', showcaseData.controllingUser);
173 | }
174 |
175 | showcaseData.controllingUser = user;
176 |
177 | var msg = {
178 | text: '> ' + '' + user.name + ' ' +
179 | ' has taken control' + ' '
180 | };
181 |
182 | emitAll('chatMessage', msg);
183 | }
184 | else {
185 | if(showcaseData.controllingUser) {
186 | if(showcaseData.controllingUser.socketId === socket.id) {
187 | showcaseData.controllingUser = null;
188 | }
189 | }
190 | }
191 |
192 | tracker[socket.id].user = user;
193 |
194 | emitAll('controlEvent', user);
195 | });
196 |
197 | ///////////////////////////////////////////////////////////////////////
198 | //
199 | //
200 | ///////////////////////////////////////////////////////////////////////
201 | socket.on('cameraChanged', function (data) {
202 |
203 | showcaseData.view = data.view;
204 |
205 | emitExclude(socket.id, 'cameraChanged', data);
206 | });
207 |
208 | ///////////////////////////////////////////////////////////////////////
209 | //
210 | //
211 | ///////////////////////////////////////////////////////////////////////
212 | socket.on('explode', function (data) {
213 |
214 | showcaseData.explodeScale = data.explodeScale;
215 |
216 | emitExclude(socket.id, 'explode', data);
217 | });
218 |
219 | ///////////////////////////////////////////////////////////////////////
220 | //
221 | //
222 | ///////////////////////////////////////////////////////////////////////
223 | socket.on('isolate', function (data) {
224 |
225 | showcaseData.isolateIds = data.isolateIds;
226 |
227 | emitExclude(socket.id, 'isolate', data);
228 | });
229 |
230 | ///////////////////////////////////////////////////////////////////////
231 | //
232 | //
233 | ///////////////////////////////////////////////////////////////////////
234 | socket.on('sendMessage', function (msg) {
235 |
236 | msg.text = '> ' + '' + msg.user.name + ' ' + ' says: ' +
237 | msg.text + ' ';
238 |
239 | emitAll('chatMessage', msg);
240 | });
241 |
242 | ///////////////////////////////////////////////////////////////////////
243 | //
244 | //
245 | ///////////////////////////////////////////////////////////////////////
246 | socket.on('addUser', function (user) {
247 |
248 | user.socketId = socket.id;
249 |
250 | tracker[socket.id].user = user;
251 |
252 | emitAll('addUser', user);
253 |
254 | var msg = {
255 | user: user,
256 | text: '> ' + '' + user.name + ' ' +
257 | ' joined the showcase '
258 | }
259 |
260 | emitAll('chatMessage', msg);
261 | });
262 |
263 | ///////////////////////////////////////////////////////////////////////
264 | //
265 | //
266 | ///////////////////////////////////////////////////////////////////////
267 | socket.on('removeUser', function (user) {
268 |
269 | removeUser(socket.id, user);
270 | });
271 |
272 | socket.on('disconnect', function () {
273 |
274 | //console.log('Socket disconnection: ' + socket.id);
275 |
276 | if(tracker[socket.id].user) {
277 | removeUser(socket.id, tracker[socket.id].user);
278 | }
279 |
280 | delete tracker[socket.id];
281 | });
282 |
283 | ///////////////////////////////////////////////////////////////////////
284 | //
285 | //
286 | ///////////////////////////////////////////////////////////////////////
287 | socket.on('loadDocument', function (urn) {
288 |
289 | showcaseData.urn = urn;
290 | showcaseData.view = null;
291 | showcaseData.isolateIds = null;
292 | showcaseData.explodeScale = 0.0;
293 |
294 | emitExclude(socket.id, 'loadDocument', urn);
295 |
296 | var msg = {
297 | text: '> Loading document... '
298 | }
299 |
300 | emitAll('chatMessage', msg);
301 | });
302 |
303 | ///////////////////////////////////////////////////////////////////////
304 | //
305 | //
306 | ///////////////////////////////////////////////////////////////////////
307 | socket.on('closeDocument', function () {
308 |
309 | showcaseData.urn = '';
310 | showcaseData.view = null;
311 | showcaseData.isolateIds = null;
312 | showcaseData.explodeScale = 0.0;
313 |
314 | emitAll('closeDocument');
315 | });
316 | });
317 | }
318 |
319 | module.exports = router;
--------------------------------------------------------------------------------
/html/js/jquery.qrcode.min.js:
--------------------------------------------------------------------------------
1 | (function(r){r.fn.qrcode=function(h){var s;function u(a){this.mode=s;this.data=a}function o(a,c){this.typeNumber=a;this.errorCorrectLevel=c;this.modules=null;this.moduleCount=0;this.dataCache=null;this.dataList=[]}function q(a,c){if(void 0==a.length)throw Error(a.length+"/"+c);for(var d=0;da||this.moduleCount<=a||0>c||this.moduleCount<=c)throw Error(a+","+c);return this.modules[a][c]},getModuleCount:function(){return this.moduleCount},make:function(){if(1>this.typeNumber){for(var a=1,a=1;40>a;a++){for(var c=p.getRSBlocks(a,this.errorCorrectLevel),d=new t,b=0,e=0;e=d;d++)if(!(-1>=a+d||this.moduleCount<=a+d))for(var b=-1;7>=b;b++)-1>=c+b||this.moduleCount<=c+b||(this.modules[a+d][c+b]=
5 | 0<=d&&6>=d&&(0==b||6==b)||0<=b&&6>=b&&(0==d||6==d)||2<=d&&4>=d&&2<=b&&4>=b?!0:!1)},getBestMaskPattern:function(){for(var a=0,c=0,d=0;8>d;d++){this.makeImpl(!0,d);var b=j.getLostPoint(this);if(0==d||a>b)a=b,c=d}return c},createMovieClip:function(a,c,d){a=a.createEmptyMovieClip(c,d);this.make();for(c=0;c=f;f++)for(var i=-2;2>=i;i++)this.modules[b+f][e+i]=-2==f||2==f||-2==i||2==i||0==f&&0==i?!0:!1}},setupTypeNumber:function(a){for(var c=
7 | j.getBCHTypeNumber(this.typeNumber),d=0;18>d;d++){var b=!a&&1==(c>>d&1);this.modules[Math.floor(d/3)][d%3+this.moduleCount-8-3]=b}for(d=0;18>d;d++)b=!a&&1==(c>>d&1),this.modules[d%3+this.moduleCount-8-3][Math.floor(d/3)]=b},setupTypeInfo:function(a,c){for(var d=j.getBCHTypeInfo(this.errorCorrectLevel<<3|c),b=0;15>b;b++){var e=!a&&1==(d>>b&1);6>b?this.modules[b][8]=e:8>b?this.modules[b+1][8]=e:this.modules[this.moduleCount-15+b][8]=e}for(b=0;15>b;b++)e=!a&&1==(d>>b&1),8>b?this.modules[8][this.moduleCount-
8 | b-1]=e:9>b?this.modules[8][15-b-1+1]=e:this.modules[8][15-b-1]=e;this.modules[this.moduleCount-8][8]=!a},mapData:function(a,c){for(var d=-1,b=this.moduleCount-1,e=7,f=0,i=this.moduleCount-1;0g;g++)if(null==this.modules[b][i-g]){var n=!1;f>>e&1));j.getMask(c,b,i-g)&&(n=!n);this.modules[b][i-g]=n;e--; -1==e&&(f++,e=7)}b+=d;if(0>b||this.moduleCount<=b){b-=d;d=-d;break}}}};o.PAD0=236;o.PAD1=17;o.createData=function(a,c,d){for(var c=p.getRSBlocks(a,
9 | c),b=new t,e=0;e8*a)throw Error("code length overflow. ("+b.getLengthInBits()+">"+8*a+")");for(b.getLengthInBits()+4<=8*a&&b.put(0,4);0!=b.getLengthInBits()%8;)b.putBit(!1);for(;!(b.getLengthInBits()>=8*a);){b.put(o.PAD0,8);if(b.getLengthInBits()>=8*a)break;b.put(o.PAD1,8)}return o.createBytes(b,c)};o.createBytes=function(a,c){for(var d=
10 | 0,b=0,e=0,f=Array(c.length),i=Array(c.length),g=0;g>>=1;return c},getPatternPosition:function(a){return j.PATTERN_POSITION_TABLE[a-1]},getMask:function(a,c,d){switch(a){case 0:return 0==(c+d)%2;case 1:return 0==c%2;case 2:return 0==d%3;case 3:return 0==(c+d)%3;case 4:return 0==(Math.floor(c/2)+Math.floor(d/3))%2;case 5:return 0==c*d%2+c*d%3;case 6:return 0==(c*d%2+c*d%3)%2;case 7:return 0==(c*d%3+(c+d)%2)%2;default:throw Error("bad maskPattern:"+
14 | a);}},getErrorCorrectPolynomial:function(a){for(var c=new q([1],0),d=0;dc)switch(a){case 1:return 10;case 2:return 9;case s:return 8;case 8:return 8;default:throw Error("mode:"+a);}else if(27>c)switch(a){case 1:return 12;case 2:return 11;case s:return 16;case 8:return 10;default:throw Error("mode:"+a);}else if(41>c)switch(a){case 1:return 14;case 2:return 13;case s:return 16;case 8:return 12;default:throw Error("mode:"+
15 | a);}else throw Error("type:"+c);},getLostPoint:function(a){for(var c=a.getModuleCount(),d=0,b=0;b=g;g++)if(!(0>b+g||c<=b+g))for(var h=-1;1>=h;h++)0>e+h||c<=e+h||0==g&&0==h||i==a.isDark(b+g,e+h)&&f++;5a)throw Error("glog("+a+")");return l.LOG_TABLE[a]},gexp:function(a){for(;0>a;)a+=255;for(;256<=a;)a-=255;return l.EXP_TABLE[a]},EXP_TABLE:Array(256),
17 | LOG_TABLE:Array(256)},m=0;8>m;m++)l.EXP_TABLE[m]=1<m;m++)l.EXP_TABLE[m]=l.EXP_TABLE[m-4]^l.EXP_TABLE[m-5]^l.EXP_TABLE[m-6]^l.EXP_TABLE[m-8];for(m=0;255>m;m++)l.LOG_TABLE[l.EXP_TABLE[m]]=m;q.prototype={get:function(a){return this.num[a]},getLength:function(){return this.num.length},multiply:function(a){for(var c=Array(this.getLength()+a.getLength()-1),d=0;d
18 | this.getLength()-a.getLength())return this;for(var c=l.glog(this.get(0))-l.glog(a.get(0)),d=Array(this.getLength()),b=0;b>>7-a%8&1)},put:function(a,c){for(var d=0;d>>c-d-1&1))},getLengthInBits:function(){return this.length},putBit:function(a){var c=Math.floor(this.length/8);this.buffer.length<=c&&this.buffer.push(0);a&&(this.buffer[c]|=128>>>this.length%8);this.length++}};"string"===typeof h&&(h={text:h});h=r.extend({},{render:"canvas",width:256,height:256,typeNumber:-1,
26 | correctLevel:2,background:"#ffffff",foreground:"#000000"},h);return this.each(function(){var a;if("canvas"==h.render){a=new o(h.typeNumber,h.correctLevel);a.addData(h.text);a.make();var c=document.createElement("canvas");c.width=h.width;c.height=h.height;for(var d=c.getContext("2d"),b=h.width/a.getModuleCount(),e=h.height/a.getModuleCount(),f=0;f").css("width",h.width+"px").css("height",h.height+"px").css("border","0px").css("border-collapse","collapse").css("background-color",h.background);d=h.width/a.getModuleCount();b=h.height/a.getModuleCount();for(e=0;e").css("height",b+"px").appendTo(c);for(i=0;i").css("width",
28 | d+"px").css("background-color",a.isDark(e,i)?h.foreground:h.background).appendTo(f)}}a=c;jQuery(a).appendTo(this)})}})(jQuery);
29 |
--------------------------------------------------------------------------------
/html/js/Autodesk.ADN.Toolkit.ViewData.js:
--------------------------------------------------------------------------------
1 | ///////////////////////////////////////////////////////////////////////////////
2 | // Copyright (c) Autodesk, Inc. All rights reserved
3 | // Written by Philippe Leefsma 2014 - ADN/Developer Technical Services
4 | //
5 | // Permission to use, copy, modify, and distribute this software in
6 | // object code form for any purpose and without fee is hereby granted,
7 | // provided that the above copyright notice appears in all copies and
8 | // that both that copyright notice and the limited warranty and
9 | // restricted rights notice below appear in all supporting
10 | // documentation.
11 | //
12 | // AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS.
13 | // AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF
14 | // MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC.
15 | // DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE
16 | // UNINTERRUPTED OR ERROR FREE.
17 | ///////////////////////////////////////////////////////////////////////////////
18 |
19 | ///////////////////////////////////////////////////////////////////////////////
20 | // Namespace declaration
21 | //
22 | ///////////////////////////////////////////////////////////////////////////////
23 | var Autodesk = Autodesk || {};
24 | Autodesk.ADN = Autodesk.ADN || {};
25 | Autodesk.ADN.Toolkit = Autodesk.ADN.Toolkit || {};
26 | Autodesk.ADN.Toolkit.ViewData = Autodesk.ADN.Toolkit.ViewData || {};
27 |
28 | ///////////////////////////////////////////////////////////////////////////////
29 | // Autodesk.ADN.Toolkit.ViewData.AdnViewDataClient
30 | //
31 | // Parameters:
32 | // baseUrl: url of view and data API service,
33 | // for production environment, it is 'https://developer.api.autodesk.com'
34 | // accessTokenOrUrl : An url which returns the access token in JSON foramt,
35 | // for example: http://still-spire-1606.herokuapp.com/api/rawtoken,
36 | // it returns token like :
37 | // {"token_type":"Bearer",
38 | // "expires_in":1799,
39 | // "access_token":"nTeOdsiNRckNbiBF7lzdEZ3yjHRx"}
40 | ///////////////////////////////////////////////////////////////////////////////
41 | Autodesk.ADN.Toolkit.ViewData.AdnViewDataClient = function (
42 | baseUrl,
43 | accessTokenResponse) {
44 |
45 | ///////////////////////////////////////////////////////////////////////////
46 | // Private Members
47 | //
48 | ///////////////////////////////////////////////////////////////////////////
49 | var _accessTokenResponse = accessTokenResponse;
50 |
51 | var _baseUrl = baseUrl;
52 |
53 |
54 | ///////////////////////////////////////////////////////////////////////////
55 | // Set the cookie upon server response
56 | // Subsequent http requests to this domain
57 | // will automatically send the cookie for authentication
58 | //
59 | ///////////////////////////////////////////////////////////////////////////
60 | this.setToken = function () {
61 |
62 | var xhr = new XMLHttpRequest();
63 |
64 | xhr.open('POST',
65 | _baseUrl + '/utility/v1/settoken',
66 | false);
67 |
68 | xhr.setRequestHeader(
69 | 'Content-Type',
70 | 'application/x-www-form-urlencoded');
71 |
72 | xhr.withCredentials = true;
73 | xhr.send("access-token=" + _accessTokenResponse.access_token);
74 | };
75 |
76 | ///////////////////////////////////////////////////////////////////////////
77 | // Use:
78 | // Create bucket
79 | //
80 | // bucketCreationData = {
81 | // bucketKey : "bucketKey",
82 | // servicesAllowed: {},
83 | // policy: "temporary/transient/persistent
84 | // }
85 | //
86 | // API:
87 | // POST /oss/{apiversion}/buckets
88 | //
89 | // Response:
90 | //
91 | // "{
92 | // "key":"bucketKey",
93 | // "owner":"tAp1fqjjtcgqS4CKpCYDjAyNbKW4IVCC",
94 | // "createDate":1404984496468,
95 | // "permissions":[{
96 | // "serviceId":"tAp1fqjjtcgqS4CKpCYDjAyNbKW4IVCC",
97 | // "access":"full"}],
98 | // "policyKey":"persistent"
99 | // }"
100 | ///////////////////////////////////////////////////////////////////////////
101 | this.createBucketAsync = function (
102 | bucketCreationData,
103 | onSuccess,
104 | onError) {
105 |
106 | var xhr = new XMLHttpRequest();
107 |
108 | xhr.open('POST',
109 | _baseUrl + "/oss/v1/buckets",
110 | true);
111 |
112 | xhr.setRequestHeader(
113 | 'Authorization',
114 | 'Bearer ' + _accessTokenResponse.access_token);
115 |
116 | xhr.setRequestHeader(
117 | 'Content-Type',
118 | 'application/json');
119 |
120 | xhr.onreadystatechange = function () {
121 | if (xhr.readyState == 4) {
122 | if(xhr.status == 200) {
123 | onSuccess(JSON.parse(xhr.responseText));
124 | }
125 | else {
126 | onError(JSON.parse(xhr.responseText));
127 | }
128 | }
129 | }
130 |
131 | try {
132 | xhr.send(JSON.stringify(bucketCreationData));
133 | }
134 | catch (ex) {
135 | onError(ex);
136 | }
137 | };
138 |
139 | ///////////////////////////////////////////////////////////////////////////
140 | // Use:
141 | // Get bucket details
142 | //
143 | // API:
144 | // GET /oss/{apiversion}/buckets/{bucketkey}/details
145 | //
146 | // Response:
147 | //
148 | // "{
149 | // "key":"bucketKey",
150 | // "owner":"tAp1fqjjtcgqS4CKpCYDjAyNbKW4IVCC",
151 | // "createDate":1404984496468,
152 | // "permissions":[{
153 | // "serviceId":"tAp1fqjjtcgqS4CKpCYDjAyNbKW4IVCC",
154 | // "access":"full"}],
155 | // "policyKey":"persistent"
156 | // }"
157 | ///////////////////////////////////////////////////////////////////////////
158 | this.getBucketDetailsAsync = function (
159 | bucketKey,
160 | onSuccess,
161 | onError) {
162 |
163 | var xhr = new XMLHttpRequest();
164 |
165 | xhr.open('GET',
166 | _baseUrl + "/oss/v1/buckets/" + bucketKey + "/details",
167 | true);
168 |
169 | xhr.setRequestHeader(
170 | 'Authorization',
171 | 'Bearer ' + _accessTokenResponse.access_token);
172 |
173 | xhr.setRequestHeader(
174 | 'Content-Type',
175 | 'application/json');
176 |
177 | xhr.onreadystatechange = function () {
178 | if (xhr.readyState == 4) {
179 | if (xhr.status == 200) {
180 | onSuccess(JSON.parse(xhr.responseText));
181 | }
182 | else {
183 | onError(JSON.parse(xhr.responseText));
184 | }
185 | }
186 | }
187 |
188 | try
189 | {
190 | xhr.send();
191 | }
192 | catch (ex)
193 | {
194 | onError(ex);
195 | }
196 | };
197 |
198 | ///////////////////////////////////////////////////////////////////////////
199 | // Use:
200 | // Upload a file to bucket
201 | //
202 | // API:
203 | // PUT /oss/{apiversion}/buckets/{bucketkey}/objects/{objectkey}
204 | //
205 | // Response:
206 | //
207 | // "{ "bucket-key" : "adn-10.07.2014-11.28.15",
208 | // "file": file,
209 | // "objects" : [ {
210 | // "location" : "baseUrl/oss/v1/buckets/bucketKey/objects/file.name",
211 | // "size" : 1493911,
212 | // "key" : "file.name",
213 | // "id" : "urn:adsk.objects:os.object:bucketKey/file.name",
214 | // "sha-1" : "ba824b22a6df9d0fc30943ffcf8129e2b9de80f6",
215 | // "content-type" : "application/stream" } ]
216 | // }"
217 | ///////////////////////////////////////////////////////////////////////////
218 | this.uploadFileAsync = function (
219 | file,
220 | bucketKey,
221 | objectKey,
222 | onSuccess,
223 | onError) {
224 |
225 | var xhr = new XMLHttpRequest();
226 |
227 | xhr.open('PUT',
228 | _baseUrl + '/oss/v1/buckets/' + bucketKey + '/objects/' + objectKey,
229 | true);
230 |
231 | xhr.setRequestHeader(
232 | 'Authorization',
233 | 'Bearer ' + _accessTokenResponse.access_token);
234 |
235 | xhr.setRequestHeader(
236 | 'Content-Type',
237 | 'application/stream');
238 |
239 | xhr.onload = function () {
240 | var response = JSON.parse(xhr.responseText);
241 | response.file = file;
242 |
243 | onSuccess(response);
244 | };
245 |
246 | var reader = new FileReader();
247 |
248 | reader.onerror = onError;
249 | reader.onabort = onError;
250 |
251 | reader.onloadend = function (event) {
252 | if (event.target.readyState == FileReader.DONE) {
253 | try {
254 | xhr.send(event.target.result);
255 | }
256 | catch (ex) {
257 | onError(ex);
258 | }
259 | }
260 | else {
261 | onError(event);
262 | }
263 | };
264 |
265 | var blob = file.slice(0, file.size);
266 |
267 | reader.readAsArrayBuffer(blob);
268 | };
269 |
270 | ///////////////////////////////////////////////////////////////////////////
271 | // Use:
272 | // Register an uploaded file
273 | //
274 | // API:
275 | // POST /viewingservice/{apiversion}/register
276 | //
277 | // Response:
278 | //
279 | // "{"Result":"Success"}"
280 | ///////////////////////////////////////////////////////////////////////////
281 | this.register = function (fileId) {
282 |
283 | var xhr = new XMLHttpRequest();
284 |
285 | xhr.open('POST',
286 | _baseUrl + '/viewingservice/v1/register',
287 | false);
288 |
289 | xhr.setRequestHeader(
290 | 'Authorization',
291 | 'Bearer ' + _accessTokenResponse.access_token);
292 |
293 | xhr.setRequestHeader(
294 | 'Content-Type',
295 | 'application/json');
296 |
297 | //xhr.onreadystatechange = ...;
298 |
299 | var body = {
300 | urn: this.toBase64(fileId)
301 | };
302 |
303 | try {
304 |
305 | xhr.send(JSON.stringify(body));
306 |
307 | return JSON.parse(xhr.responseText);
308 | }
309 | catch (ex) {
310 | return ex;
311 | }
312 | };
313 |
314 | ///////////////////////////////////////////////////////////////////////////
315 | // Use:
316 | // Get model thumbnail
317 | //
318 | // API:
319 | // GET /viewingservice/{apiversion}/thumbnails/{urn}?
320 | // guid=$GUID$ & width=$WIDTH$ & height=$HEIGHT$ (& type=$TYPE$)
321 | //
322 | // Response:
323 | //
324 | //
325 | ///////////////////////////////////////////////////////////////////////////
326 | this.getThumbnailAsync = function (
327 | fileId,
328 | onSuccess,
329 | onError,
330 | width,
331 | height,
332 | guid) {
333 |
334 | var parameters =
335 | '?width=' + (width ? width : '150') +
336 | '&height=' + (height ? height : '150') +
337 | (guid ? '&guid=' + guid : '');
338 |
339 | var xhr = new XMLHttpRequest();
340 |
341 | xhr.open('GET',
342 | _baseUrl +
343 | "/viewingservice/v1/thumbnails/" +
344 | this.toBase64(fileId),
345 | true);
346 |
347 | xhr.setRequestHeader(
348 | 'Authorization',
349 | 'Bearer ' + _accessTokenResponse.access_token);
350 |
351 | xhr.responseType = 'arraybuffer';
352 |
353 | xhr.onload = function (e) {
354 | if (this.status == 200) {
355 |
356 | //converts raw data to base64 img
357 | var base64 = btoa(String.fromCharCode.apply(
358 | null, new Uint8Array(this.response)));
359 |
360 | onSuccess(base64);
361 | }
362 | };
363 |
364 | try {
365 | xhr.send();
366 | }
367 | catch (ex) {
368 | onError(ex);
369 | }
370 | };
371 |
372 | ///////////////////////////////////////////////////////////////////////////
373 | // Use:
374 | // Get model viewable
375 | //
376 | // API:
377 | // GET /viewingservice/{apiversion}/{urn}?guid=$GUID$
378 | // GET /viewingservice/{apiversion}/{urn}/status?guid=$GUID$
379 | // GET /viewingservice/{apiversion}/{urn}/all?guid=$GUID$
380 | //
381 | // Response:
382 | //
383 | //{"guid":"dXJuOmFkc2sub...","type":"design","hasThumbnail":"true",
384 | //"progress":"complete","startedAt":"Mon Jun 23 11:28:18 UTC 2014",
385 | //"status":"success","success":"100%","urn":"dXJuOmFkc2sub...",
386 | //"children":[{"guid":"4cf5994a25ba","type":"folder","hasThumbnail":"true",
387 | //"name":"Trailer.dwf","progress":"complete","role":"viewable",
388 | //"status":"success","success":"100%","version":"2.0",
389 | //"children":[{"guid":"12EB52A2-7281-44D8-A704-02D8D4A2C69C_Sheets",
390 | //"type":"folder","hasThumbnail":"true","name":"Sheets",
391 | //"progress":"complete","status":"success","success":"100%",
392 | //"children":[{"guid":"com.autodesk.dwf.eModel","type":"geometry",
393 | //"hasThumbnail":"true","name":"Trailer.iam","order":0,"progress":"Complete",
394 | //"role":"3d","status":"Success","viewableID":"com.autodesk.dwf.eModel",
395 | //"properties":{"":{"Description":""},
396 | //"hidden":{"_InitialURI":"presentation=49c8fb97,d4121f60",
397 | //"_LabelIconResourceID":"12EB52A6"}},
398 | //"children":[{"guid":"d1fe2007","type":"view",
399 | //"camera":[5,-7,5,-0.1,-2,-5,-0.1,0.2,0.1,0.3,0.4,483.5,1],
400 | //"hasThumbnail":"false","name":"Home View","role":"3d"},{"guid":"45291936",
401 | //"type":"resource","hasThumbnail":"false",
402 | //"mime":"application/autodesk-svf","role":"graphics",
403 | //"urn":"urn:adsk.viewing:fs.file:sfsfsghs==/output/com.autodesk/0.svf"},
404 | //{"guid":"9a5c1bcb","type":"resource",
405 | //"mime":"application/autodesk-db",
406 | //"role":"Autodesk.CloudPlatform.PropertyDatabase",
407 | //"urn":"urn:...ssdd==/output/com.autodesk.dwf/section_properties.db"}]}]}]}]}
408 | //
409 | ///////////////////////////////////////////////////////////////////////////
410 | this.getViewableAsync = function (
411 | fileId,
412 | onSuccess,
413 | onError,
414 | option,
415 | guid) {
416 |
417 | var parameters = (guid ? '?guid=' + guid : '');
418 |
419 | var optionStr = "";
420 |
421 | switch (option) {
422 |
423 | case 'status':
424 | optionStr = "/status";
425 | break;
426 |
427 | case 'all':
428 | optionStr = "/all";
429 | break;
430 |
431 | default:
432 | break;
433 | }
434 |
435 | var xhr = new XMLHttpRequest();
436 |
437 | xhr.open('GET',
438 | _baseUrl +
439 | "/viewingservice/v1/" +
440 | this.toBase64(fileId) +
441 | optionStr +
442 | parameters,
443 | true);
444 |
445 | xhr.setRequestHeader(
446 | 'Authorization',
447 | 'Bearer ' + _accessTokenResponse.access_token);
448 |
449 | xhr.onreadystatechange = function () {
450 | if (xhr.readyState == 4) {
451 | if (xhr.status == 200) {
452 | onSuccess(JSON.parse(xhr.responseText));
453 | }
454 | else {
455 | onError(JSON.parse(xhr.responseText));
456 | }
457 | }
458 | }
459 |
460 | try {
461 | xhr.send();
462 | }
463 | catch (ex) {
464 | onError(ex);
465 | }
466 | };
467 |
468 | ///////////////////////////////////////////////////////////////////////////
469 | // Get subitems with properties
470 | //
471 | ///////////////////////////////////////////////////////////////////////////
472 | this.getSubItemsWithProperties = function (
473 | fileId,
474 | properties,
475 | onSuccess,
476 | onError) {
477 |
478 | function hasProperties(item, properties) {
479 |
480 | var hasProperties = true;
481 |
482 | for(var propName in properties) {
483 |
484 | if(!item.hasOwnProperty(propName)) {
485 |
486 | hasProperties = false;
487 | break;
488 | }
489 |
490 | if(item[propName] !== properties[propName]) {
491 |
492 | hasProperties = false;
493 | break;
494 | }
495 | }
496 |
497 | return hasProperties;
498 | }
499 |
500 | function getSubItemsWithPropertiesRec(
501 | viewable, properties) {
502 |
503 | var items = [];
504 |
505 | if(hasProperties(viewable, properties)) {
506 |
507 | items.push(viewable);
508 | }
509 |
510 | if (typeof viewable.children !== 'undefined') {
511 |
512 | for (var i=0; i 0 && typeof ids[0] === 'number') {
301 |
302 | // getNodesByIds can throw an exception when the model isn't sufficiently loaded
303 | // Catch it and try to apply the viewer state again in a second
304 | try {
305 | ids = _viewerLeft.model.getNodesByIds(ids);
306 | }
307 | catch (ex) {
308 | success = false;
309 | }
310 | }
311 | if (success) {
312 | try {
313 | viewersApply(prop, ids);
314 | }
315 | catch (ex) {
316 | success = false;
317 | }
318 | }
319 | return success;
320 | }
321 |
322 |
323 | function viewersApply(func){
324 | //if (_viewerLeft && _viewerRight && _leftLoaded && _rightLoaded) {
325 | var val = Array.prototype.slice.call(arguments, 1);
326 | _viewerLeft[func].apply(_viewerLeft, val);
327 | _viewerRight[func].apply(_viewerRight, val);
328 | //}
329 | }
330 |
331 |
332 | // Progress listener to set the view once the data has started
333 | // loading properly (we get a 5% notification early on that we
334 | // need to ignore - it comes too soon)
335 | function progressListener(e) {
336 | if (e.percent >= 10) {
337 | if (e.target.clientContainer.id === 'viewerLeft') {
338 | _viewerLeft.getObjectTree(
339 | function() {
340 | _leftLoaded = true;
341 | console.log('Left has an instance tree');
342 | setTimeout(finishProgress, 100);
343 | },
344 | function() {
345 | _leftLoaded = false;
346 | console.log('Cannot get left instance tree');
347 | }
348 | );
349 | _viewerLeft.removeEventListener('progress', progressListener);
350 | }
351 | else if (e.target.clientContainer.id === 'viewerRight') {
352 | _viewerRight.getObjectTree(
353 | function() {
354 | _rightLoaded = true;
355 | console.log('Right has an instance tree');
356 | setTimeout(finishProgress, 100);
357 | },
358 | function() {
359 | _rightLoaded = false;
360 | console.log('Cannot get right instance tree');
361 | }
362 | );
363 | _viewerRight.removeEventListener('progress', progressListener);
364 | }
365 | }
366 | }
367 |
368 |
369 | function finishProgress() {
370 |
371 | if (_leftLoaded && _rightLoaded) {
372 |
373 | if (!_orbitInitialPosition) {
374 | _orbitInitialPosition = _viewerLeft.navigation.getPosition();
375 | }
376 | var vec = _viewerLeft.model.getUpVector();
377 | _upVector = new THREE.Vector3(vec[0], vec[1], vec[2]);
378 |
379 | //unwatchProgress();
380 | watchCameras();
381 | watchTilt();
382 |
383 | _readyToApplyEvents = true;
384 | viewersApplyState();
385 | }
386 | }
387 |
388 |
389 | function watchProgress() {
390 | _viewerLeft.addEventListener('progress', progressListener);
391 | _viewerRight.addEventListener('progress', progressListener);
392 | }
393 |
394 |
395 | function unwatchProgress() {
396 | if (_viewerLeft) {
397 | _viewerLeft.removeEventListener('progress', progressListener);
398 | }
399 | if (_viewerRight) {
400 | _viewerRight.removeEventListener('progress', progressListener);
401 | }
402 | }
403 |
404 |
405 | function watchCameras() {
406 | _viewerLeft.addEventListener('cameraChanged', left2right);
407 | _viewerRight.addEventListener('cameraChanged', right2left);
408 | }
409 |
410 |
411 | function unwatchCameras() {
412 | if (_viewerLeft) {
413 | _viewerLeft.removeEventListener('cameraChanged', left2right);
414 | }
415 |
416 | if (_viewerRight) {
417 | _viewerRight.removeEventListener('cameraChanged', right2left);
418 | }
419 | }
420 |
421 |
422 | function watchTilt() {
423 | if (window.DeviceOrientationEvent) {
424 | window.addEventListener('deviceorientation', orb);
425 | }
426 | }
427 |
428 |
429 | function unwatchTilt() {
430 | if (window.DeviceOrientationEvent) {
431 | window.removeEventListener('deviceorientation', orb);
432 | }
433 | }
434 |
435 |
436 | // Event handlers for the cameraChanged events
437 |
438 |
439 | function left2right() {
440 | if (_viewerLeft && _viewerRight && !_updatingRight) {
441 | _updatingLeft = true;
442 | transferCameras(true);
443 | setTimeout(function() { _updatingLeft = false; }, 500);
444 | }
445 | }
446 |
447 |
448 | function right2left() {
449 | if (_viewerLeft && _viewerRight && !_updatingLeft) {
450 | _updatingRight = true;
451 | transferCameras(false);
452 | setTimeout(function() { _updatingRight = false; }, 500);
453 | }
454 | }
455 |
456 |
457 | function transferCameras(leftToRight) {
458 | // The direction argument dictates the source and target
459 | var source = leftToRight ? _viewerLeft : _viewerRight;
460 | var target = leftToRight ? _viewerRight : _viewerLeft;
461 |
462 | var pos = source.navigation.getPosition();
463 | var trg = source.navigation.getTarget();
464 |
465 | // Set the up vector manually for both cameras
466 | source.navigation.setWorldUpVector(_upVector);
467 | target.navigation.setWorldUpVector(_upVector);
468 |
469 | // Get the new position for the target camera
470 | var up = source.navigation.getCameraUpVector();
471 |
472 | // Get the position of the target camera
473 | var newPos = offsetCameraPos(source, pos, trg, leftToRight);
474 |
475 | // Zoom to the new camera position in the target
476 | zoom(target, newPos, trg, up);
477 | }
478 |
479 |
480 | // And for the deviceorientation event
481 |
482 |
483 | function orb(e) {
484 | if (!e.alpha || !e.beta || !e.gamma || _updatingLeft || _updatingRight) return;
485 |
486 | // Remove our handlers watching for camera updates,
487 | // as we'll make any changes manually
488 | // (we won't actually bother adding them back, afterwards,
489 | // as this means we're in mobile mode and probably inside
490 | // a Google Cardboard holder)
491 | unwatchCameras();
492 |
493 | // gamma is the front-to-back in degrees (with
494 | // this screen orientation) with +90/-90 being
495 | // vertical and negative numbers being 'downwards'
496 | // with positive being 'upwards'
497 | var ab = Math.abs(e.beta);
498 | var flipped = (ab < 90 && e.gamma < 0) || (ab > 90 && e.gamma > 0);
499 | var vert = ((flipped ? e.gamma : -e.gamma) + (ab < 90 ? 90 : -90)) * _deg2rad;
500 |
501 | // When the orientation changes, reset the base direction
502 | if (_wasFlipped != flipped) {
503 | // If the angle goes below/above the horizontal, we don't
504 | // flip direction (we let it go a bit further)
505 | if (Math.abs(e.gamma) < 45) {
506 | flipped = _wasFlipped;
507 | } else {
508 | // Our base direction allows us to make relative horizontal
509 | // rotations when we rotate left & right
510 | _wasFlipped = flipped;
511 | _baseDir = e.alpha;
512 | }
513 | }
514 |
515 | // alpha is the compass direction the device is
516 | // facing in degrees. This equates to the
517 | // left - right rotation in landscape
518 | // orientation (with 0-360 degrees)
519 | var horiz = (e.alpha - _baseDir) * _deg2rad;
520 |
521 | // Save the latest horiz and vert values for use in zoom
522 | _lastHoriz = horiz;
523 | _lastVert = vert;
524 |
525 | orbitViews(vert, horiz);
526 | }
527 |
528 |
529 | function orbitViews(vert, horiz) {
530 | // We'll rotate our position based on the initial position
531 | // and the target will stay the same
532 |
533 | if (_orbitInitialPosition === null)
534 | return;
535 |
536 | var pos = _orbitInitialPosition.clone();
537 | var trg = _viewerLeft.navigation.getTarget();
538 |
539 | // Start by applying the left/right orbit
540 | // (we need to check the up/down value, though)
541 | if (vert < 0 && !Autodesk.Viewing.isIOSDevice()) {
542 | horiz = horiz + Math.PI;
543 | }
544 |
545 | var zAxis = _upVector.clone();
546 | pos.applyAxisAngle(zAxis, horiz);
547 |
548 | // Now add the up/down rotation
549 | var axis = trg.clone().sub(pos).normalize();
550 | axis.cross(zAxis);
551 | pos.applyAxisAngle(axis, -vert);
552 |
553 | // Determine the camera up vector: this is important if
554 | // getting to the extremities, to stop direction flipping
555 | var camUp = pos.clone().sub(trg).normalize();
556 | camUp.cross(axis).normalize();
557 |
558 | // Zoom in with the lefthand view
559 | zoom(_viewerLeft, pos, trg, camUp);
560 |
561 | // Get a camera slightly to the right
562 | var pos2 = offsetCameraPos(_viewerLeft, pos, trg, true);
563 |
564 | // And zoom in with that on the righthand view, too
565 | zoom(_viewerRight, pos2, trg, camUp);
566 | }
567 |
568 |
569 | function offsetCameraPos(source, pos, trg, leftToRight) {
570 | // Use a small fraction of the distance for the camera offset
571 | var disp = pos.distanceTo(trg) * 0.04;
572 |
573 | // Clone the camera and return its X translated position
574 | var clone = source.autocamCamera.clone();
575 | clone.translateX(leftToRight ? disp : -disp);
576 | return clone.position;
577 | }
578 |
579 |
580 | function zoom(viewer, pos, trg, up) {
581 | // Make sure our up vector is correct for this model
582 | viewer.navigation.setWorldUpVector(_upVector, true);
583 | viewer.navigation.setView(pos, trg);
584 | viewer.navigation.setCameraUpVector(up);
585 | }
586 |
--------------------------------------------------------------------------------
/html/js/vr-party-participant-stereo.js:
--------------------------------------------------------------------------------
1 | var _viewer;
2 | var _updating;
3 | var _loaded;
4 | var _baseDir;
5 | var _upVector;
6 | var _deg2rad = Math.PI / 180;
7 | var _rad2deg = 1 / _deg2rad;
8 | var _wasFlipped;
9 | var _readyToApplyEvents = false;
10 | var _model_state = {};
11 | var _orbitInitialPosition;
12 | var _lastVert, _lastHoriz;
13 | var _socket = io();
14 | var _sessionId;
15 | var _rotation;
16 |
17 | // Get the VRDisplay and save it for later.
18 | var _vrDisplay = null;
19 | navigator.getVRDisplays().then(function(displays) {
20 | if (displays.length > 0) {
21 | _vrDisplay = displays[0];
22 | }
23 | });
24 |
25 | function initialize() {
26 |
27 | _sessionId = getURLParameter('session');
28 | if (_sessionId) {
29 | /*
30 | var buttonName = 'Connect';
31 | var panel = document.getElementById('control');
32 | var button = document.createElement('div');
33 | button.classList.add('cmd-btn');
34 |
35 | button.innerHTML = buttonName;
36 | button.onclick = connect;
37 |
38 | panel.appendChild(button);
39 | */
40 | connect();
41 | }
42 | }
43 |
44 |
45 | function connect() {
46 | $('#layer1').hide();
47 |
48 | try {
49 | // _vrDisplay.requestPresent([{source: $('#layer2')[0]}]);
50 | }
51 | catch (ex) {}
52 |
53 | //launchFullscreen($('#layer2')[0]);
54 |
55 | Autodesk.Viewing.Initializer(getViewingOptions(), function() {
56 | var avp = Autodesk.Viewing.Private;
57 | avp.GPU_OBJECT_LIMIT = 100000;
58 | avp.onDemandLoading = false;
59 |
60 | showMessage('Waiting...');
61 |
62 | _socket.emit('join-session', { id: _sessionId });
63 |
64 | initConnection();
65 | });
66 | }
67 |
68 |
69 | function showMessage(text, removeBlink) {
70 | $('#layer2').hide();
71 | var messages = $('#messageLeft,#messageRight');
72 | if (removeBlink) {
73 | messages.removeClass('blink');
74 | }
75 | messages.html(text);
76 | $('#layer3').show();
77 | }
78 |
79 |
80 | function clearMessage() {
81 | $('#layer3').hide();
82 | $('#layer2').show();
83 | }
84 |
85 |
86 | function launchScopedViewer(urn) {
87 | _baseDir = null;
88 | _loaded = false;
89 | _updating = false;
90 | _upVector = new THREE.Vector3(0, 1, 0);
91 | _orbitInitialPosition = null;
92 |
93 | // Make sure the VR extension doesn't change the fullscreen settings
94 | // when it loads ot unloads (these can only be called from a UI callback)
95 | // Also stub out the HUD message function, to stop those from being shown
96 |
97 | window.launchFullscreen = function() {};
98 | window.exitFullscreen = function() {};
99 | Autodesk.Viewing.Private.HudMessage.displayMessage = function() {};
100 |
101 | if (urn) {
102 | // Remove all event listeners
103 | unwatchTilt;
104 | unwatchProgress();
105 |
106 | clearMessage();
107 |
108 | urn = urn.ensurePrefix('urn:');
109 | Autodesk.Viewing.Initializer(
110 | getScopedViewingOptions(urn),
111 | function() {
112 | //_viewer.initialize();
113 | Autodesk.Viewing.Document.load(
114 | urn,
115 | function(documentData) {
116 | var model = getModel(documentData);
117 | if (!model) return;
118 |
119 | // Uninitializing the viewers helps with stability
120 |
121 | if (_viewer) {
122 | _viewer.finish();
123 | _viewer = null;
124 | }
125 |
126 | if (!_viewer) {
127 | _viewer = new Autodesk.Viewing.Private.GuiViewer3D($('#viewer')[0], { wantInfoButton : false });
128 | _viewer.start();
129 |
130 | // Added for WebVR support
131 |
132 | /*
133 | _viewer.impl.setRenderCallback(
134 | function(fn) {
135 | var pose = _vrDisplay.getPose();
136 | var quat = new THREE.Quaternion().fromArray(pose.orientation);
137 | orbitByPose(quat);
138 | _vrDisplay.requestAnimationFrame(fn);
139 | }
140 | );
141 | */
142 |
143 | //_viewer.setQualityLevel(false, false);
144 | //_viewer.setGroundShadow(true);
145 | _viewer.setGroundReflection(false);
146 | _viewer.setProgressiveRendering(false);
147 | }
148 |
149 | watchProgress();
150 |
151 | _viewer.prefs.remove("fusionOrbit", false);
152 | _viewer.prefs.remove("fusionOrbitConstrained", false);
153 |
154 | loadModel(_viewer, model);
155 |
156 | // Hide the viewer's toolbar and the home button
157 |
158 | $('.adsk-control-group').each(function(){
159 | $(this).find('>.adsk-button').each(function(){
160 | $(this).css({ 'display':'none' });
161 | });
162 | });
163 |
164 | // Needed for v2.2+
165 |
166 | $('#guiviewer3d-toolbar').css({ 'display':'none' });
167 | $('.homeViewWrapper').css({ 'display':'none' });
168 | $('.homeViewMenu').css({ 'display':'none' });
169 | }
170 | );
171 | }
172 | );
173 | }
174 | else {
175 |
176 | showMessage('Disconnected', true);
177 |
178 | _viewer.uninitialize();
179 | _viewer = new Autodesk.Viewing.Private.GuiViewer3D($('#viewer')[0], {
180 | wantInfoButton : false,
181 | extensions: [ 'Autodesk.Viewing.WebVR' ],
182 | experimental: [ 'webVR_orbitModel' ]
183 | });
184 | }
185 | }
186 |
187 |
188 | function launchViewer(urn) {
189 | _baseDir = null;
190 | _loaded = false;
191 | _updating = false;
192 | _upVector = new THREE.Vector3(0, 1, 0);
193 | _orbitInitialPosition = null;
194 |
195 | // Make sure the VR extension doesn't change the fullscreen settings
196 | // when it loads ot unloads (these can only be called from a UI callback)
197 | // Also stub out the HUD message function, to stop those from being shown
198 |
199 | window.launchFullscreen = function() {};
200 | window.exitFullscreen = function() {};
201 | Autodesk.Viewing.Private.HudMessage.displayMessage = function() {};
202 |
203 | if (urn) {
204 | // Remove all event listeners
205 | unwatchTilt;
206 | unwatchProgress();
207 |
208 | clearMessage();
209 |
210 | urn = urn.ensurePrefix('urn:');
211 |
212 | Autodesk.Viewing.Document.load(
213 | urn,
214 | function(documentData) {
215 | var model = getModel(documentData);
216 | if (!model) return;
217 |
218 | // Uninitializing the viewers helps with stability
219 |
220 | if (_viewer) {
221 | _viewer.finish();
222 | _viewer = null;
223 | }
224 |
225 | if (!_viewer) {
226 | _viewer = new Autodesk.Viewing.Private.GuiViewer3D($('#viewer')[0], { wantInfoButton : false });
227 | _viewer.start();
228 |
229 | // Added for WebVR support
230 |
231 | _viewer.impl.setRenderCallback(
232 | function(fn) {
233 | var pose = _vrDisplay.getPose();
234 | var quat = new THREE.Quaternion().fromArray(pose.orientation);
235 | orbitByPose(quat);
236 | _vrDisplay.requestAnimationFrame(fn);
237 | }
238 | );
239 |
240 | //_viewer.setQualityLevel(false, false);
241 | //_viewer.setGroundShadow(true);
242 | _viewer.setGroundReflection(false);
243 | _viewer.setProgressiveRendering(false);
244 | }
245 |
246 | watchProgress();
247 |
248 | _viewer.prefs.remove("fusionOrbit", false);
249 | _viewer.prefs.remove("fusionOrbitConstrained", false);
250 |
251 | loadModel(_viewer, model);
252 |
253 | // Hide the viewer's toolbar and the home button
254 |
255 | $('.adsk-control-group').each(function(){
256 | $(this).find('>.adsk-button').each(function(){
257 | $(this).css({ 'display':'none' });
258 | });
259 | });
260 |
261 | // Needed for v2.2+
262 |
263 | $('#guiviewer3d-toolbar').css({ 'display':'none' });
264 | $('.homeViewWrapper').css({ 'display':'none' });
265 | $('.homeViewMenu').css({ 'display':'none' });
266 | }
267 | );
268 | }
269 | else {
270 |
271 | showMessage('Disconnected', true);
272 |
273 | _viewer.uninitialize();
274 | _viewer = new Autodesk.Viewing.Private.GuiViewer3D($('#viewer')[0], { wantInfoButton : false});
275 | }
276 | }
277 |
278 | function initConnection() {
279 | _socket.on('lmv-command', function(msg) {
280 | if (msg.name === 'load') {
281 | launchScopedViewer(msg.value, msg.disconnecting);
282 | }
283 | else if (msg.name === 'zoom') {
284 | _model_state.zoom_factor = parseFloat(msg.value);
285 | }
286 | else if (msg.name === 'explode') {
287 | _model_state.explode_factor = parseFloat(msg.value);
288 | }
289 | else if (msg.name === 'isolate') {
290 | _model_state.isolate_ids = msg.value;
291 | }
292 | else if (msg.name === 'hide') {
293 | _model_state.hide_ids = msg.value;
294 | }
295 | else if (msg.name === 'show') {
296 | _model_state.show_ids = msg.value;
297 | }
298 | else if (msg.name == 'section') {
299 | _model_state.cut_planes = msg.value.map(function(vec) {
300 | return new THREE.Vector4(vec.x, vec.y, vec.z, vec.w);
301 | });
302 | }
303 | else if (msg.name === 'render') {
304 | _model_state.lighting = msg.value;
305 | }
306 | viewerApplyState();
307 | });
308 | }
309 |
310 |
311 | function viewerApplyState() {
312 | var not_ready = false;
313 |
314 | if (!_loaded || !_readyToApplyEvents) {
315 | return;
316 | }
317 |
318 | if (_model_state.zoom_factor !== undefined && !isNaN(_model_state.zoom_factor)) {
319 |
320 | unwatchTilt();
321 |
322 | var previousUpdating = _updating;
323 |
324 | var newPos = zoomInOrOut(_viewer, _orbitInitialPosition, _model_state.zoom_factor);
325 | _viewer.navigation.setPosition(newPos);
326 |
327 | console.log("Zoomed: " + _model_state.zoom_factor);
328 |
329 | _orbitInitialPosition = newPos;
330 |
331 | _updating = previousUpdating;
332 |
333 | _model_state.zoom_factor = undefined;
334 |
335 | if (_lastVert && _lastHoriz) {
336 | orbitViews(_lastVert, _lastHoriz);
337 | }
338 |
339 | console.log('Applied zoom');
340 |
341 | watchTilt();
342 | }
343 |
344 | if (_model_state.explode_factor !== undefined) {
345 | viewersApply('explode', _model_state.explode_factor);
346 | _model_state.explode_factor = undefined;
347 | console.log('Applied explode');
348 | }
349 |
350 | if (_model_state.isolate_ids !== undefined) {
351 | var worked = tryToApplyIds('isolate', _model_state.isolate_ids);
352 | if (worked) {
353 | _model_state.isolate_ids = undefined;
354 | console.log('Applied isolate');
355 | }
356 | else
357 | console.log('Not ready to isolate');
358 | not_ready = not_ready || !worked;
359 | }
360 |
361 | if (!not_ready && _model_state.show_ids !== undefined) {
362 | var worked = tryToApplyIds('show', _model_state.show_ids);
363 | if (worked) {
364 | _model_state.show_ids = undefined;
365 | console.log('Applied show');
366 | }
367 | else
368 | console.log('Not ready to show');
369 | not_ready = not_ready || !worked;
370 | }
371 |
372 | if (!not_ready && _model_state.hide_ids !== undefined) {
373 | var worked = tryToApplyIds('hide', _model_state.hide_ids);
374 | if (worked) {
375 | _model_state.hide_ids = undefined;
376 | console.log('Applied hide');
377 | }
378 | else
379 | console.log('Not ready to hide');
380 | not_ready = not_ready || !worked;
381 | }
382 |
383 | if (_model_state.cut_planes !== undefined) {
384 | viewersApply('setCutPlanes', _model_state.cut_planes);
385 | _model_state.cut_planes = undefined;
386 | console.log('Applied section');
387 | }
388 |
389 | if (_model_state.lighting !== undefined) {
390 | viewersApply('setLightPreset', _model_state.lighting);
391 | _model_state.lighting = undefined;
392 | console.log('Applied lighting');
393 | }
394 |
395 | if (not_ready) {
396 | setTimeout(function() { viewerApplyState(); }, 1000);
397 | }
398 | }
399 |
400 |
401 | function tryToApplyIds(prop, ids) {
402 | var success = true;
403 | try {
404 | viewersApply(prop, ids);
405 | }
406 | catch (ex) {
407 | success = false;
408 | }
409 | return success;
410 | }
411 |
412 |
413 | function viewersApply(func){
414 | var val = Array.prototype.slice.call(arguments, 1);
415 | _viewer[func].apply(_viewer, val);
416 | }
417 |
418 |
419 | // Progress listener to set the view once the data has started
420 | // loading properly (we get a 5% notification early on that we
421 | // need to ignore - it comes too soon)
422 | function progressListener(e) {
423 | if (e.percent >= 10) {
424 | if (e.target.clientContainer.id === 'viewer') {
425 | _viewer.getObjectTree(
426 | function() {
427 | _loaded = true;
428 | console.log('Viewer has an instance tree');
429 | setTimeout(finishProgress, 100);
430 | },
431 | function() {
432 | _loaded = false;
433 | console.log('Cannot get instance tree');
434 | }
435 | );
436 | _viewer.removeEventListener('progress', progressListener);
437 | }
438 | }
439 | }
440 |
441 | function finishProgress() {
442 |
443 | if (_loaded) {
444 |
445 | if (!_orbitInitialPosition) {
446 | _orbitInitialPosition = _viewer.navigation.getPosition();
447 | }
448 | var vec = _viewer.model.getUpVector();
449 | _upVector = new THREE.Vector3(vec[0], vec[1], vec[2]);
450 |
451 | _viewer.loadExtension('Autodesk.ADN.Viewing.Extension.VR', {});
452 |
453 | watchTilt();
454 |
455 | _readyToApplyEvents = true;
456 | viewerApplyState();
457 | }
458 | }
459 |
460 |
461 | function watchProgress() {
462 | _viewer.addEventListener('progress', progressListener);
463 | }
464 |
465 |
466 | function unwatchProgress() {
467 | if (_viewer) {
468 | _viewer.removeEventListener('progress', progressListener);
469 | }
470 | }
471 |
472 |
473 | function watchTilt() {
474 | if (window.DeviceOrientationEvent) {
475 | //window.addEventListener('deviceorientation', orb);
476 | }
477 | }
478 |
479 |
480 | function unwatchTilt() {
481 | if (window.DeviceOrientationEvent) {
482 | //window.removeEventListener('deviceorientation', orb);
483 | }
484 | }
485 |
486 |
487 | // And for the deviceorientation event
488 |
489 |
490 | function orb(e) {
491 | if (!e.alpha || !e.beta || !e.gamma || _updating) return;
492 |
493 | // gamma is the front-to-back in degrees (with
494 | // this screen orientation) with +90/-90 being
495 | // vertical and negative numbers being 'downwards'
496 | // with positive being 'upwards'
497 | var ab = Math.abs(e.beta);
498 | var flipped = (ab < 90 && e.gamma < 0) || (ab > 90 && e.gamma > 0);
499 | var vert = ((flipped ? e.gamma : -e.gamma) + (ab < 90 ? 90 : -90)) * _deg2rad;
500 |
501 | // When the orientation changes, reset the base direction
502 | if (_wasFlipped != flipped) {
503 | // If the angle goes below/above the horizontal, we don't
504 | // flip direction (we let it go a bit further)
505 | if (Math.abs(e.gamma) < 45) {
506 | flipped = _wasFlipped;
507 | } else {
508 | // Our base direction allows us to make relative horizontal
509 | // rotations when we rotate left & right
510 | _wasFlipped = flipped;
511 | _baseDir = e.alpha;
512 | }
513 | }
514 |
515 | // alpha is the compass direction the device is
516 | // facing in degrees. This equates to the
517 | // left - right rotation in landscape
518 | // orientation (with 0-360 degrees)
519 | var horiz = (e.alpha - _baseDir) * _deg2rad;
520 |
521 | // Save the latest horiz and vert values for use in zoom
522 | _lastHoriz = horiz;
523 | _lastVert = vert;
524 |
525 | orbitViews(vert, horiz);
526 | }
527 |
528 | function orbitByPose(q) {
529 | var x = q.x;
530 | var y = q.y;
531 | var z = q.z;
532 | var w = q.w;
533 |
534 | var roll = Math.atan2(2*y*w - 2*x*z, 1 - 2*y*y - 2*z*z);
535 | var pitch = Math.atan2(2*x*w - 2*y*z, 1 - 2*x*x - 2*z*z);
536 | var yaw = Math.asin(2*x*y + 2*z*w);
537 |
538 | //console.log("Roll: " + roll + ", Pitch: " + pitch + ", Yaw: " + yaw);
539 |
540 | if (pitch < 0) {
541 | roll = roll + Math.PI;
542 | }
543 | orbitViews(-pitch, Math.PI + roll);
544 | }
545 |
546 | function orbitViews(vert, horiz) {
547 | // We'll rotate our position based on the initial position
548 | // and the target will stay the same
549 |
550 | if (_orbitInitialPosition === null)
551 | return;
552 |
553 | var pos = _orbitInitialPosition.clone();
554 | var trg = _viewer.navigation.getTarget();
555 |
556 | // Start by applying the left/right orbit
557 | // (we need to check the up/down value, though)
558 | if (vert < 0 && !isIOSDevice()) {
559 | horiz = horiz + Math.PI;
560 | }
561 |
562 | var zAxis = _upVector.clone();
563 | pos.applyAxisAngle(zAxis, horiz);
564 |
565 | // Now add the up/down rotation
566 | var axis = trg.clone().sub(pos).normalize();
567 | axis.cross(zAxis);
568 | pos.applyAxisAngle(axis, -vert);
569 |
570 | // Determine the camera up vector: this is important if
571 | // getting to the extremities, to stop direction flipping
572 | var camUp = pos.clone().sub(trg).normalize();
573 | camUp.cross(axis).normalize();
574 |
575 | // Zoom in to this location
576 | zoom(_viewer, pos, trg, camUp);
577 | }
578 |
579 |
580 | function zoom(viewer, pos, trg, up) {
581 | // Make sure our up vector is correct for this model
582 | viewer.navigation.setWorldUpVector(_upVector, true);
583 | viewer.navigation.setView(pos, trg);
584 | viewer.navigation.setCameraUpVector(up);
585 | }
586 |
587 |
588 | function zoomInOrOut(viewer, pos, factor) {
589 | var direction = new THREE.Vector3();
590 | var target = new THREE.Vector3(); //_viewer.navigation.getTarget();
591 | direction.subVectors(pos, target);
592 | direction.normalize();
593 | direction.multiplyScalar(factor);
594 | return direction.add(target);
595 | }
--------------------------------------------------------------------------------
/html/js/vr-party-presenter-stereo.js:
--------------------------------------------------------------------------------
1 | var _socket = io();
2 | var _sessionId;
3 | var _viewer;
4 | var _last_distance_to_target;
5 | var _view_data_bucket = 'vrparty';
6 | var _default_models = {
7 | 'robot arm' : 'dXJuOmFkc2sub2JqZWN0czpvcy5vYmplY3Q6c3RlYW1idWNrL1JvYm90QXJtLmR3Zng=',
8 | 'welding robot' : 'dXJuOmFkc2sub2JqZWN0czpvcy5vYmplY3Q6dnJwYXJ0eS9BQkJfcm9ib3QuZHdm',
9 | 'ergon chair' : 'dXJuOmFkc2sub2JqZWN0czpvcy5vYmplY3Q6c3RlYW1idWNrL0VyZ29uLnppcA==',
10 | 'differential' : 'dXJuOmFkc2sub2JqZWN0czpvcy5vYmplY3Q6c3RlYW1idWNrL0RpZmYuZHdmeA==',
11 | 'suspension' : 'dXJuOmFkc2sub2JqZWN0czpvcy5vYmplY3Q6c3RlYW1idWNrL1N1c3BlbnNpb24uZHdm',
12 | 'house' : 'dXJuOmFkc2sub2JqZWN0czpvcy5vYmplY3Q6c3RlYW1idWNrL2hvdXNlLmR3Zng=',
13 | 'flyer one' : 'dXJuOmFkc2sub2JqZWN0czpvcy5vYmplY3Q6c3RlYW1idWNrL0ZseWVyT25lLmR3Zng=',
14 | 'motorcycle' : 'dXJuOmFkc2sub2JqZWN0czpvcy5vYmplY3Q6c3RlYW1idWNrL01vdG9yY3ljbGUuZHdmeA==',
15 | 'V8 engine' : 'dXJuOmFkc2sub2JqZWN0czpvcy5vYmplY3Q6c3RlYW1idWNrL1Y4RW5naW5lLnN0cA==',
16 | //'aotea' : 'dXJuOmFkc2sub2JqZWN0czpvcy5vYmplY3Q6c3RlYW1idWNrL2FvdGVhMy5kd2Y=',
17 | //'dinghy' : 'dXJuOmFkc2sub2JqZWN0czpvcy5vYmplY3Q6c3RlYW1idWNrL2RpbmdoeS5mM2Q=',
18 | 'column' : 'dXJuOmFkc2sub2JqZWN0czpvcy5vYmplY3Q6c3RlYW1idWNrL3RhYmxldDIuemlw',
19 | 'tablet' : 'dXJuOmFkc2sub2JqZWN0czpvcy5vYmplY3Q6c3RlYW1idWNrL2VneXB0NC56aXA=',
20 | //'trophy' : 'dXJuOmFkc2sub2JqZWN0czpvcy5vYmplY3Q6dnJwYXJ0eS9Ucm9waHlfQW5nZWxIYWNrLmYzZA==',
21 | //'cake' : 'dXJuOmFkc2sub2JqZWN0czpvcy5vYmplY3Q6dnJwYXJ0eS9IQkM0LmR3Zng='
22 | 'movement' : 'dXJuOmFkc2sub2JqZWN0czpvcy5vYmplY3Q6dnJwYXJ0eS9FVEFfNjQ5Ny0xX01vdmVtZW50X0NvcnJlY3RlZF8zLmR3Zg'
23 | };
24 | var _hosts = [ 'vr-party.herokuapp.com', 'www.vrok.it' ];
25 |
26 | //
27 | // Initialize
28 | //
29 |
30 | function initialize() {
31 |
32 | _sessionId = getURLParameter('session');
33 | if (_sessionId) {
34 | // Only generate the UI if a session ID was passed in via the URL
35 |
36 | // Populate our initial UI with a set of buttons, one for each function in the Buttons object
37 | var panel = document.getElementById('control');
38 | for (var name in _default_models) {
39 | var urn = _default_models[name];
40 | addButton(panel, name, function(urn) { return function() { launchUrn(urn); } }(urn));
41 | }
42 |
43 | var base_url = window.location.origin;
44 | if (_hosts.indexOf(window.location.hostname) > -1) {
45 | // Apparently some phone browsers don't like the mix of http and https
46 | // Default to https on Heroku deployment
47 | base_url = 'https://' + window.location.hostname;
48 | }
49 |
50 | var url = base_url + '/participant2.html?session=' + _sessionId;
51 | $('#url').attr('href', url);
52 | $('#qrcode').qrcode(url);
53 |
54 | // If the provided session exists then load its data (right now just its URN)
55 | $.get(
56 | window.location.origin + '/api/getSession/' + _sessionId,
57 | function(req2, res2) {
58 | if (res2 === "success") {
59 |
60 | readCookiesForCustomModel();
61 | initializeSelectFilesDialog();
62 |
63 | if (req2 !== "") {
64 | Autodesk.Viewing.Initializer(getViewingOptions(), function() {
65 | launchUrn(req2);
66 | });
67 | }
68 | else {
69 | // Otherwise we'll create a session with this name
70 | // (we may want to disable this for security reasons,
71 | // but it's actually a nice way to create sessions
72 | // with custom names)
73 | _socket.emit('create-session', { id: _sessionId });
74 |
75 | // Initialize viewing but don't start a viewer
76 | Autodesk.Viewing.Initializer(getViewingOptions(), function() {
77 | showAbout();
78 | !function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0],p=/^http:/.test(d.location)?'http':'https';if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src=p+'://platform.twitter.com/widgets.js';fjs.parentNode.insertBefore(js,fjs);}}(document, 'script', 'twitter-wjs');
79 | });
80 | }
81 | }
82 | }
83 | );
84 | }
85 | else {
86 | // If no session was provided, redirect the browser to a session
87 | // generated by the server
88 | $.get(
89 | window.location.origin + '/api/sessionId',
90 | function(res) {
91 | _sessionId = res;
92 | window.location.href = window.location.href + "?session=" + _sessionId;
93 | //window.location.replace(window.location.origin + "?session=" + _sessionId);
94 | }
95 | );
96 | }
97 | }
98 |
99 |
100 | //
101 | // Terminate
102 | //
103 |
104 | function terminate() {
105 | if (_sessionId) {
106 | _socket.emit('close-session', { id: _sessionId });
107 | }
108 | }
109 |
110 |
111 | function addButton(panel, buttonName, loadFunction) {
112 | var button = document.createElement('div');
113 | button.classList.add('cmd-btn-small');
114 |
115 | button.innerHTML = buttonName;
116 | button.onclick = loadFunction;
117 |
118 | panel.appendChild(button);
119 | }
120 |
121 |
122 | function launchUrn(urn) {
123 |
124 | var viewerToClose;
125 |
126 | // Uninitializing the viewer helps with stability
127 | if (_viewer) {
128 | viewerToClose = _viewer;
129 | _viewer = null;
130 | }
131 |
132 | if (urn) {
133 |
134 | $('#aboutDiv').hide();
135 | $('#3dViewDiv').show();
136 |
137 | _socket.emit('lmv-command', { session: _sessionId, name: 'load', value: urn });
138 |
139 | urn = urn.ensurePrefix('urn:');
140 |
141 | Autodesk.Viewing.Document.load(
142 | urn,
143 | function(documentData) {
144 | var model = getModel(documentData);
145 | if (!model) return;
146 |
147 | _viewer = new Autodesk.Viewing.Private.GuiViewer3D($('#3dViewDiv')[0]);
148 | _viewer.start();
149 | _viewer.addEventListener(Autodesk.Viewing.CAMERA_CHANGE_EVENT, onCameraChange);
150 | _viewer.addEventListener(Autodesk.Viewing.ISOLATE_EVENT, onIsolate);
151 | _viewer.addEventListener(Autodesk.Viewing.HIDE_EVENT, onHide);
152 | _viewer.addEventListener(Autodesk.Viewing.SHOW_EVENT, onShow);
153 | _viewer.addEventListener(Autodesk.Viewing.EXPLODE_CHANGE_EVENT, onExplode);
154 | _viewer.addEventListener(Autodesk.Viewing.CUTPLANES_CHANGE_EVENT,onSection);
155 | _viewer.addEventListener(Autodesk.Viewing.RENDER_OPTION_CHANGED_EVENT, onRenderOption);
156 |
157 | resetSize(_viewer.container);
158 |
159 | if (viewerToClose) {
160 | viewerToClose.finish();
161 | }
162 |
163 | loadModel(_viewer, model);
164 | }
165 | );
166 | }
167 | else {
168 | // Create a blank viewer on first load
169 | _viewer = new Autodesk.Viewing.Private.GuiViewer3D($('#3dViewDiv')[0]);
170 | resetSize(_viewer.container);
171 | }
172 | }
173 |
174 |
175 | function resetSize(elem, fullHeight) {
176 | elem.style.width = window.innerWidth - 360 + 'px'; // subtract the left column
177 | if (fullHeight) {
178 | elem.style.height = '';
179 | }
180 | else {
181 | elem.style.height = (window.innerHeight - 40) + 'px'; // subtract the table padding
182 | }
183 | }
184 |
185 |
186 | //
187 | // Viewer3D events
188 | //
189 |
190 | function onCameraChange(event) {
191 |
192 | // With OBJ models the target moves to keep equidistant from the camera
193 | // So we just check the distance from the origin rather than the target
194 | // It seems to work, anyway!
195 | var distance_to_target = _viewer.navigation.getPosition().length(); //distanceTo(_viewer.navigation.getTarget());
196 | if (_last_distance_to_target === undefined || Math.abs(distance_to_target - _last_distance_to_target) > 0.1) {
197 | _socket.emit('lmv-command', { session: _sessionId, name: 'zoom', value: distance_to_target });
198 | _last_distance_to_target = distance_to_target;
199 | }
200 | }
201 |
202 |
203 | // Translate a list of objects (for R13 & R14) to a list of IDs
204 | // Socket.io prefers not to have binary content to transfer, it seems
205 | function getIdList(ids) {
206 | if (ids.length > 0 && typeof ids[0] === 'object') {
207 | ids = ids.map(function(obj) { return obj.dbId;});
208 | }
209 | return ids;
210 | }
211 |
212 | function onIsolate(event) {
213 | _socket.emit('lmv-command', { session: _sessionId, name: 'isolate', value: getIdList(event.nodeIdArray) });
214 | }
215 |
216 |
217 | function onHide(event) {
218 | _socket.emit('lmv-command', { session: _sessionId, name: 'hide', value: getIdList(event.nodeIdArray) });
219 | }
220 |
221 |
222 | function onShow(event) {
223 | _socket.emit('lmv-command', { session: _sessionId, name: 'show', value: getIdList(event.nodeIdArray) });
224 | }
225 |
226 |
227 | function onExplode() {
228 | _socket.emit('lmv-command', { session: _sessionId, name: 'explode', value: _viewer.getExplodeScale() });
229 | }
230 |
231 |
232 | function onSection(event) {
233 | _socket.emit('lmv-command', { session: _sessionId, name: 'section', value: _viewer.getCutPlanes() });
234 | }
235 |
236 |
237 | function onRenderOption(event) {
238 | _socket.emit('lmv-command', { session: _sessionId, name: 'render', value: _viewer.impl.currentLightPreset() });
239 | }
240 |
241 |
242 | //
243 | // Models upload
244 | //
245 |
246 | function onFileSelect() {
247 | var el = document.getElementById('fileElem');
248 | if (el) {
249 | el.click();
250 | }
251 | }
252 |
253 |
254 | function cancel() {
255 | $(this).dialog('close');
256 | $('#upload-button').html('Upload file');
257 | }
258 |
259 |
260 | function upload() {
261 |
262 | $('#upload-button').html('Uploading...');
263 |
264 | var filteredForUpload = new Array();
265 |
266 | $(':checkbox').each(function() {
267 | if ($(this).is(':checked')) {
268 | // 'filesToUpload' seems to be not a regular array, 'filter()'' function is undefined
269 | for (var i = 0; i < filesToUpload.length; ++i) {
270 | var file = filesToUpload[i];
271 | if (file.name == $(this).val()) {
272 | filteredForUpload.push(file);
273 | }
274 | }
275 | }
276 | });
277 |
278 | console.log("Filtered for upload");
279 | for (var i = 0; i < filteredForUpload.length; ++i) {
280 | var file = filteredForUpload[i];
281 | console.log('Selected file: ' + file.name + ' size: ' + file.size);
282 | }
283 |
284 | onUpload(filteredForUpload);
285 |
286 | $(this).dialog('close');
287 | }
288 |
289 |
290 | function deselectAllFiles() {
291 | $(':checkbox').prop('checked', false);
292 | $(":button:contains('OK')").prop("disabled", true).addClass("ui-state-disabled");
293 | }
294 |
295 |
296 | function selectAllFiles() {
297 | $(':checkbox').prop('checked', true);
298 | $(":button:contains('OK')").prop("disabled", false).removeClass("ui-state-disabled");
299 | }
300 |
301 |
302 | function initializeSelectFilesDialog() {
303 | var dlg = document.getElementsByName("upload-files");
304 |
305 | if (dlg.length == 0) {
306 |
307 | var dlgDiv = document.createElement("div");
308 | dlgDiv.id = "upload-files";
309 | dlgDiv.title='Uploading files';
310 | document.getElementsByTagName("body")[0].appendChild(dlgDiv);
311 |
312 | $('#upload-files').append("The following files are larger than 2MB. Are you sure you want to upload them?
");
313 |
314 | var buttons = {
315 | Cancel: cancel,
316 | 'OK': upload,
317 | 'Deselect All': deselectAllFiles,
318 | 'Select All': selectAllFiles
319 | };
320 |
321 | $('#upload-files').dialog({
322 | autoOpen: false,
323 | modal: true,
324 | buttons: buttons,
325 | width:"auto",
326 | resizable: false,
327 | });
328 | }
329 | }
330 |
331 |
332 | function clearCheckBoxes() {
333 | var checkboxes = document.getElementById("checkboxes");
334 | if (checkboxes) {
335 | checkboxes.parentNode.removeChild(checkboxes);
336 | }
337 |
338 | checkboxes = document.createElement('div');
339 | checkboxes.id = "checkboxes";
340 | $('#upload-files').append(checkboxes);
341 | }
342 |
343 |
344 | function createCheckBox(fileName) {
345 | var id = "filename-checkbox-" + fileName;
346 | var checkbox = document.createElement('input');
347 | checkbox.id = id;
348 | checkbox.type = "checkbox";
349 | checkbox.name = "upload-files";
350 | checkbox.value = fileName;
351 |
352 | $("#upload-files").change(function() {
353 | var numberChecked = $("input[name='upload-files']:checked").size();
354 | if (numberChecked > 0) {
355 | $(":button:contains('OK')").prop("disabled", false).removeClass("ui-state-disabled");
356 | } else {
357 | $(":button:contains('OK')").prop("disabled", true).addClass("ui-state-disabled");
358 | }
359 | });
360 |
361 | var label = document.createElement('label');
362 | label.htmlFor = id;
363 | label.appendChild(document.createTextNode(fileName));
364 |
365 | var br = document.createElement('br');
366 |
367 | $('#checkboxes').append(checkbox);
368 | $('#checkboxes').append(label);
369 | $('#checkboxes').append(br);
370 | }
371 |
372 |
373 | function resetSelectedFiles() {
374 | var fileElem = $("#fileElem");
375 | fileElem.wrap("