Compare commits
7 Commits
Author | SHA1 | Date |
---|---|---|
Unboring SL | 61b54a2a10 | 5 years ago |
Unboring SL | 23f961b19c | 5 years ago |
Unboring SL | c96d57ccb2 | 5 years ago |
Unboring SL | 103e404dd9 | 5 years ago |
Unboring SL | f781760a45 | 5 years ago |
Unboring SL | 1f6f070c18 | 5 years ago |
Unboring SL | 07c397dc56 | 5 years ago |
@ -0,0 +1,2 @@ |
|||||||
|
node_modules |
||||||
|
package-lock.json |
@ -0,0 +1,2 @@ |
|||||||
|
{ |
||||||
|
} |
@ -1,21 +0,0 @@ |
|||||||
MIT License |
|
||||||
|
|
||||||
Copyright (c) 2020 Fabien Benetou |
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy |
|
||||||
of this software and associated documentation files (the "Software"), to deal |
|
||||||
in the Software without restriction, including without limitation the rights |
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
|
||||||
copies of the Software, and to permit persons to whom the Software is |
|
||||||
furnished to do so, subject to the following conditions: |
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all |
|
||||||
copies or substantial portions of the Software. |
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
|
||||||
SOFTWARE. |
|
@ -0,0 +1,2 @@ |
|||||||
|
CreativeCommons Attribution-NonCommercial-ShareAlike 4.0 International. |
||||||
|
http://creativecommons.org/licenses/by-nc-sa/4.0/ |
@ -1,35 +1,16 @@ |
|||||||
# relax-plus-think-space |
# relax-plus-think-space |
||||||
an infinite space for your big ideas |
|
||||||
|
|
||||||
## Principle |
An infinite space for your big ideas |
||||||
Natural interaction (6DoF controllers) |
|
||||||
in an immersive environment (relaxing setup to induce state of flow) |
|
||||||
with work related content (e.g. Github issues, photos of work posters and post-it notes) |
|
||||||
to be freely organised in visual categories (e.g. kanban). |
|
||||||
|
|
||||||
## UX flow for demos and tests |
## Development |
||||||
### new user on phone without own photos : |
|
||||||
1. visit https://learnwebvr.xyz and get redirected to https://learnwebvr.xyz/setup/ |
|
||||||
1. upload photos |
|
||||||
1. generate a personalised link |
|
||||||
1. share that link to target device (e.g. Firefox Send Tab to Devices on Oculus Quest) |
|
||||||
1. experience on device |
|
||||||
1. remove device and visit 2D links e.g. https://learnwebvr.xyz/flat.html?email=fabien@benetou.fr |
|
||||||
|
|
||||||
### returning user on 6DoF device |
Have Node (< v12, recommended v11) and npm installed. |
||||||
1. visit https://learnwebvr.xyz |
|
||||||
1 ... |
|
||||||
|
|
||||||
### new user on 6DoF device |
Change 192.168.0.12 to your local IP on package.json scripts/start |
||||||
1. visit https://learnwebvr.xyz |
|
||||||
1 ... |
|
||||||
|
|
||||||
## Code |
``` |
||||||
* frontend : `index.html` and `setup/index.html` |
npm install |
||||||
* backend : `setup/server/upload.php` |
npm run start |
||||||
|
``` |
||||||
|
|
||||||
## License |
Then head to `https://[YOUR LOCAL IP]:3000` in your browser. |
||||||
MIT. |
|
||||||
|
|
||||||
## Property and rights |
|
||||||
Iterative Explorations SCS based in Belgium |
|
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 68 KiB |
After Width: | Height: | Size: 92 KiB |
After Width: | Height: | Size: 92 KiB |
@ -0,0 +1,32 @@ |
|||||||
|
<!DOCTYPE html> |
||||||
|
<html lang="en"> |
||||||
|
<head> |
||||||
|
<title>relax-plus-think-space</title> |
||||||
|
<meta charset="utf-8"> |
||||||
|
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0"> |
||||||
|
<meta name="theme-color" content="#353449"> |
||||||
|
<style> |
||||||
|
body { |
||||||
|
margin: 0; |
||||||
|
} |
||||||
|
canvas { |
||||||
|
display: block; |
||||||
|
-webkit-touch-callout: none; |
||||||
|
-webkit-user-select: none; |
||||||
|
-khtml-user-select: none; |
||||||
|
-moz-user-select: none; |
||||||
|
-ms-user-select: none; |
||||||
|
user-select: none; |
||||||
|
outline: none; |
||||||
|
-webkit-tap-highlight-color: rgba(255, 255, 255, 0); /* mobile webkit */ |
||||||
|
} |
||||||
|
</style> |
||||||
|
<link rel="shortcut icon" href="assets/img/favicon.png" type="image/x-icon"> |
||||||
|
<script src="aframe-master.js"></script> |
||||||
|
<script src="bundle.js"></script> |
||||||
|
</head> |
||||||
|
<body> |
||||||
|
<div id="app"></div> |
||||||
|
<a id="vrButton" href="#" title="Enter VR / Fullscreen"></a> |
||||||
|
</body> |
||||||
|
</html> |
@ -1,111 +0,0 @@ |
|||||||
<!DOCTYPE html> |
|
||||||
<html lang="en"> |
|
||||||
<head> |
|
||||||
<!-- Required meta tags --> |
|
||||||
<meta charset="utf-8"> |
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> |
|
||||||
|
|
||||||
<!-- Bootstrap CSS --> |
|
||||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css" integrity="sha384-/Y6pD6FV/Vv2HJnA6t+vslU6fwYXjCFtcEpHbNJ0lyAFsXTsjBbfaDjzALeQsN6M" crossorigin="anonymous"> |
|
||||||
<link href="https://getbootstrap.com/docs/4.0/examples/signin/signin.css" rel="stylesheet"> |
|
||||||
|
|
||||||
<script src="setup/upload/server/php/files/usersdb.js"></script> |
|
||||||
<script src="https://aframe.io/releases/1.0.0/aframe.min.js"></script> |
|
||||||
</head> |
|
||||||
<style> |
|
||||||
body { background-color: transparent; } |
|
||||||
|
|
||||||
#email, #sms { |
|
||||||
font-size: xx-large; |
|
||||||
} |
|
||||||
</style> |
|
||||||
|
|
||||||
<body> |
|
||||||
|
|
||||||
<img src="setup/productlogo.png"> |
|
||||||
|
|
||||||
<h3>flat viewer by category sorted by position</h3> |
|
||||||
|
|
||||||
<div id="spacesholder">Your spaces: |
|
||||||
<ul id="spaces"></ul> |
|
||||||
</div> |
|
||||||
|
|
||||||
<div>Already have an account? <span onclick="login()" style="text-decoration: underline;">Log-in</span> |
|
||||||
<form> |
|
||||||
<div id="login" style="display:none"><input id="useremail"/> |
|
||||||
<button style="margin-top:2px;" type="button" onclick="loginViaEmail()" id="loginemail" class="btn btn-lg btn-primary btn-block">Login</button> |
|
||||||
</div> |
|
||||||
</form> |
|
||||||
<div style="display:none" id="nouser">User not found. Double check your email address then contact fabien@iterative-explorations.com</a></div> |
|
||||||
</div> |
|
||||||
|
|
||||||
<script> |
|
||||||
const urlParams = new URLSearchParams(window.location.search); |
|
||||||
const email = urlParams.get('email'); |
|
||||||
if (email) loginViaEmail() |
|
||||||
|
|
||||||
var uploadedURL = "setup/upload/server/php/files/"; |
|
||||||
var images = [] |
|
||||||
var url = "/" |
|
||||||
var urlParameters = "?customimages=" |
|
||||||
var userspace |
|
||||||
|
|
||||||
function login(){ |
|
||||||
document.querySelector("#login").style.display = "block" |
|
||||||
} |
|
||||||
|
|
||||||
function findCategory(image){ |
|
||||||
if (!userspace) return |
|
||||||
console.log(image.filename) |
|
||||||
var imagePos = new THREE.Vector3(); |
|
||||||
imagePos.copy ( AFRAME.utils.coordinates.parse(image.position) ) |
|
||||||
|
|
||||||
var closest |
|
||||||
var smallestDistance = 1000 |
|
||||||
for (var category of userspace.categories){ |
|
||||||
var categoryPos = new THREE.Vector3(); |
|
||||||
categoryPos.copy ( AFRAME.utils.coordinates.parse(category.position) ) |
|
||||||
var distance = categoryPos.distanceTo( imagePos ) |
|
||||||
if (distance < smallestDistance){ |
|
||||||
smallestDistance = distance |
|
||||||
closest = category.label |
|
||||||
} |
|
||||||
console.log(distance, category.label) |
|
||||||
} |
|
||||||
|
|
||||||
return closest |
|
||||||
} |
|
||||||
|
|
||||||
function loginViaEmail(){ |
|
||||||
var path = "setup/" |
|
||||||
if (!email) |
|
||||||
email = document.querySelector("#useremail").value |
|
||||||
userspace = database[email] |
|
||||||
if (!userspace){ |
|
||||||
document.querySelector("#nouser").style.display = "block" |
|
||||||
return |
|
||||||
} |
|
||||||
|
|
||||||
document.querySelector("#spacesholder").style.display = "block" |
|
||||||
var spaces = document.querySelector("#spaces") |
|
||||||
var space = document.createElement("li") |
|
||||||
var spacelink = document.createElement("a") |
|
||||||
var images = userspace.files |
|
||||||
spacelink.href = url + urlParameters |
|
||||||
spacelink.target = "_blank" |
|
||||||
spacelink.innerHTML = userspace.last_login |
|
||||||
space.appendChild(spacelink) |
|
||||||
for (var image of images){ |
|
||||||
urlParameters += image.filename + "," |
|
||||||
space.innerHTML += path + image.filename + " " + findCategory( image ) |
|
||||||
} |
|
||||||
console.log(space) |
|
||||||
spaces.appendChild(space) |
|
||||||
console.log("userspace", userspace) |
|
||||||
} |
|
||||||
</script> |
|
||||||
|
|
||||||
<h3 style="position:absolute; bottom:0px; right:0px;">A product by <a href="https://iterative-explorations.com"><img width="200px" src="https://iterative-explorations.com/logo.svg"></a>.</h3> |
|
||||||
|
|
||||||
</body> |
|
||||||
</html> |
|
@ -1,150 +0,0 @@ |
|||||||
<head> |
|
||||||
<meta charset="UTF-8"> |
|
||||||
<title>Think + Relax space by Iterative Explorations</title> |
|
||||||
<script src="https://aframe.io/releases/1.0.0/aframe.min.js"></script> |
|
||||||
<script src="https://rawgit.com/feiss/aframe-environment-component/master/dist/aframe-environment-component.min.js"></script> |
|
||||||
<script src="https://cdn.rawgit.com/donmccurdy/aframe-extras/v4.1.2/dist/aframe-extras.min.js"></script> |
|
||||||
<script src="https://unpkg.com/super-hands@3.0.0/dist/super-hands.min.js"></script> |
|
||||||
<script src="pimvrhelpers.js"></script> |
|
||||||
</head> |
|
||||||
<script> |
|
||||||
// could add an in VR edit mode for the categories, to position them, name them, etc |
|
||||||
// should be mostly for adjustments |
|
||||||
var timer = AFRAME.utils.getUrlParameter('timer') |
|
||||||
if (!timer) timer = 300 //5min |
|
||||||
|
|
||||||
function saveNewItemsPosition(){ |
|
||||||
var selector = ".notes"; |
|
||||||
var pos = "" |
|
||||||
document.querySelectorAll(selector).forEach( |
|
||||||
e => pos += "\n" + AFRAME.utils.coordinates.stringify( e.getAttribute("position") ) |
|
||||||
) |
|
||||||
pimvrSaveRemote("WeWorkTest", pos) // from pimvrhelpers.js |
|
||||||
// should be saved by username by space |
|
||||||
// could be pointed at from the database file (thus having multiple storages) |
|
||||||
} |
|
||||||
|
|
||||||
AFRAME.registerComponent("watch", { |
|
||||||
init: function() { |
|
||||||
document.querySelector("#timer").setAttribute("text","value:"+timer) |
|
||||||
this.tick = AFRAME.utils.throttleTick(this.tick, 1000, this); |
|
||||||
// details https://aframe.io/docs/1.0.0/core/utils.html#aframe-utils-throttle-function-minimuminterval-optionalcontext |
|
||||||
}, |
|
||||||
|
|
||||||
tick: function (t, dt) { |
|
||||||
var time = Number( document.querySelector("#timer").getAttribute("text").value ) |
|
||||||
time-- |
|
||||||
document.querySelector("#timer").setAttribute("text","value:"+(time)) |
|
||||||
}, |
|
||||||
}) |
|
||||||
|
|
||||||
AFRAME.registerComponent("relaxing-introduction", { |
|
||||||
init: function() { |
|
||||||
// get in flow state |
|
||||||
// for now controlled just with AFrame animation |
|
||||||
// cf https://aframe.io/docs/1.0.0/components/animation.html |
|
||||||
|
|
||||||
setTimeout(function(){ |
|
||||||
for (var note of document.querySelectorAll(".notes")){ note.emit("fadein") } |
|
||||||
}, 10 * 1000) // arbitrary 10s, should be instead after the introduction is finished |
|
||||||
} |
|
||||||
}) |
|
||||||
|
|
||||||
AFRAME.registerComponent("decompression-conclusion", { |
|
||||||
init: function() { |
|
||||||
// cf PoC at 2017 W3C web authoring workshop with Roland Dubois |
|
||||||
setTimeout(function(){ |
|
||||||
var intructions = document.querySelector("#instructions") |
|
||||||
instructions.setAttribute("position", "-1 1.5 -1") // assumes the user is roughtly centered... could force on camera instead |
|
||||||
instructions.setAttribute("text", "value", "Your session will end soon\n\nVisit https://learnWebVR.xyz/flat.html\n\nto work outside of VR.") |
|
||||||
instructions.setAttribute("opacity", "1") // could be animated instead |
|
||||||
|
|
||||||
console.log("X min voice over and notes fade-out with contextual information (time, weather, etc)") |
|
||||||
|
|
||||||
document.querySelector("[environment]").setAttribute("environment", "preset:checkerboard") |
|
||||||
}, timer * 1000); |
|
||||||
|
|
||||||
} |
|
||||||
}) |
|
||||||
|
|
||||||
AFRAME.registerComponent("environment-by-url", { |
|
||||||
schema: {}, |
|
||||||
init: function() { |
|
||||||
var environment = AFRAME.utils.getUrlParameter('environment') |
|
||||||
if (environment) document.querySelector("[environment]").setAttribute("environment", "preset:" + environment) |
|
||||||
} |
|
||||||
}) |
|
||||||
|
|
||||||
AFRAME.registerComponent("photo-importer", { |
|
||||||
schema: {}, |
|
||||||
init: function() { |
|
||||||
var baseURL = "setup/" |
|
||||||
var images = AFRAME.utils.getUrlParameter('customimages').split(','); |
|
||||||
if (images.length == 1) window.location = baseURL; // no images? must be configured first |
|
||||||
// could also display a tutorial instead |
|
||||||
var i=1 |
|
||||||
for (var image of images){ |
|
||||||
if (image){ |
|
||||||
var id = "placeholder"+i |
|
||||||
var placeholder = document.querySelector("#"+id) |
|
||||||
if (!placeholder){ |
|
||||||
// TODO somehow not interactable anymore?! |
|
||||||
// had a similar silly issue before but can't recall how I fixed it :( |
|
||||||
// something basic about entities or hierarchy... |
|
||||||
placeholder = document.createElement("a-box") |
|
||||||
placeholder.id = id |
|
||||||
placeholder.setAttribute("material", "shader:flat") |
|
||||||
placeholder.setAttribute("hoverable", "") |
|
||||||
placeholder.setAttribute("grabbable", "") |
|
||||||
placeholder.setAttribute("draggable", "") |
|
||||||
placeholder.setAttribute("dropppable", "") |
|
||||||
// somehow does appear visually correct but are NOT interactable! |
|
||||||
placeholder.setAttribute("position", "" + i/1.5-1.5 + " 1.5 -0.7") |
|
||||||
placeholder.setAttribute("scale", "0.5 0.3 0.01") |
|
||||||
// assume 1 * 1.5 photo ratio (landscape) |
|
||||||
AFRAME.scenes[0].appendChild(placeholder) |
|
||||||
} |
|
||||||
placeholder.className += "notes" |
|
||||||
placeholder.setAttribute("opacity", "0") |
|
||||||
placeholder.setAttribute("animation", "property: components.material.material.opacity; to: 1; dur: 1500; easing: linear; startEvents: fadein;") |
|
||||||
placeholder.setAttribute("src", baseURL+image) |
|
||||||
i++ |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
}) |
|
||||||
|
|
||||||
|
|
||||||
</script> |
|
||||||
<body> |
|
||||||
<a-scene photo-importer environment-by-url relaxing-introduction decompression-conclusion> |
|
||||||
<a-entity environment="preset: forest;"></a-entity> |
|
||||||
|
|
||||||
<a-entity sphere-collider="objects: a-box" super-hands hand-controls="left"></a-entity> |
|
||||||
<a-entity sphere-collider="objects: a-box" super-hands hand-controls="right"></a-entity> |
|
||||||
|
|
||||||
<!-- could be wrist watch as I've done via Twitter years ago --> |
|
||||||
<a-text watch id="timer" rotation="180 0 180" position="0 1 2" value="time"></a-text> |
|
||||||
|
|
||||||
<a-text id="instructions" position="-1 1.5 -0.65" value="welcome to your\n\nthink+relax space" opacity="0" |
|
||||||
animation__fadein="property: components.text.material.uniforms.opacity.value; to: 0.99; dur: 1500; easing: linear" |
|
||||||
animation__fadeout="property: components.text.material.uniforms.opacity.value; to: 0.01; dur: 1500; easing: linear; startEvents: fadeout;" |
|
||||||
animation__leave="property: object3D.position.z; to: -100; dur: 15000; easing: linear; delay: 2500;" |
|
||||||
></a-text> |
|
||||||
|
|
||||||
|
|
||||||
<a-text rotation="-20 20 0" position="-2 0.2 -2" scale="1 1 1" value="waiting"></a-text> |
|
||||||
<a-box material="wireframe:true" color="orange" position="-2 1 -2" scale="2 2 2"></a-box> |
|
||||||
<a-text rotation="-20 0 0" position=" 0 0.2 -2" scale="1 1 1" value="in progress"></a-text> |
|
||||||
<a-box material="wireframe:true" color="red" position="0 1 -2" scale="2 2 2"></a-box> |
|
||||||
<a-text rotation="-20 -20 0" position=" 2 0.2 -2" scale="1 1 1" value="completed"></a-text> |
|
||||||
<a-box material="wireframe:true" color="green" position="2 1 -2" scale="2 2 2"></a-box> |
|
||||||
|
|
||||||
<a-box material="shader:flat" id="placeholder1" hoverable grabbable stretchable draggable dropppable position="0.5 1.5 -0.5" scale="0.5 0.3 0.01"></a-box> |
|
||||||
<a-box material="shader:flat" id="placeholder2" hoverable grabbable stretchable draggable dropppable position="0 1.9 -0.7" scale="0.5 0.3 0.01"></a-box> |
|
||||||
<a-box material="shader:flat" id="placeholder3" hoverable grabbable stretchable draggable dropppable position="-0.5 1.5 -0.7" scale="0.5 0.3 0.01"></a-box> |
|
||||||
<!-- somehow creating dynamically fails... even though it did (!) work at some point (but wasn't saved via git)--> |
|
||||||
<!-- until then will rely on a pool of objects --> |
|
||||||
|
|
||||||
</a-scene> |
|
||||||
</body> |
|
@ -0,0 +1,42 @@ |
|||||||
|
{ |
||||||
|
"name": "relax-plus-think-space", |
||||||
|
"version": "0.0.1", |
||||||
|
"license": "CC-BY-NC-SA-4.0", |
||||||
|
"description": "An infinite space for your big ideas", |
||||||
|
"repository": { |
||||||
|
"type": "git", |
||||||
|
"url": "" |
||||||
|
}, |
||||||
|
"main": "index.js", |
||||||
|
"dependencies": {}, |
||||||
|
"scripts": { |
||||||
|
"start": "webpack-dev-server --mode development --host 0.0.0.0", |
||||||
|
"build": "webpack --mode production --config webpack.config.js" |
||||||
|
}, |
||||||
|
"keywords": [], |
||||||
|
"author": "Arturo Paracuellos", |
||||||
|
"devDependencies": { |
||||||
|
"aframe-environment-component": "^2.0.0", |
||||||
|
"aframe-event-set-component": "^5.0.0", |
||||||
|
"aframe-super-hot-loader": "^1.7.0", |
||||||
|
"aframe-super-hot-html-loader": "^2.1.0", |
||||||
|
"babel-core": "^6.26.3", |
||||||
|
"babel-loader": "^7.1.4", |
||||||
|
"babel-preset-env": "^1.7.0", |
||||||
|
"css-loader": "^3.4.1", |
||||||
|
"html-require-loader": "^1.0.1", |
||||||
|
"json-loader": "^0.5.7", |
||||||
|
"style-loader": "^0.23.1", |
||||||
|
"url-loader": "^1.1.2", |
||||||
|
"webpack": "^4.39.3", |
||||||
|
"webpack-cli": "^3.3.7", |
||||||
|
"webpack-dev-server": "^3.8.0", |
||||||
|
"webpack-glsl-loader": "^1.0.1" |
||||||
|
}, |
||||||
|
"standard": { |
||||||
|
"globals": [ |
||||||
|
"AFRAME", |
||||||
|
"THREE" |
||||||
|
] |
||||||
|
} |
||||||
|
} |
@ -1,173 +0,0 @@ |
|||||||
//---------PIM helper functions-----------------------------------------------------------
|
|
||||||
/* |
|
||||||
currently PmWiki backend |
|
||||||
could use http://fabien.benetou.fr/Site/AllRecentChanges?action=source to check for update
|
|
||||||
heavy but nearly no processing required |
|
||||||
enough if done once per minute or so |
|
||||||
if update, request serverrender on modified page |
|
||||||
IFF it's being displayed |
|
||||||
planned Evernote backend |
|
||||||
https://github.com/evernote/evernote-sdk-js
|
|
||||||
https://github.com/wanasit/everest-js
|
|
||||||
https://stackoverflow.com/questions/24580588/how-to-list-all-the-notes-from-an-evernote-notebook-javascript-node-js
|
|
||||||
https://dev.evernote.com/doc/articles/polling_notification.php
|
|
||||||
or other popular PIMs |
|
||||||
https://developers.trello.com/
|
|
||||||
http://www.xmind.net/developer/
|
|
||||||
ideally with webhooks on a backend abstraction with coherent API
|
|
||||||
|
|
||||||
*/ |
|
||||||
|
|
||||||
function pimvrSaveItemsStates(callback) { |
|
||||||
function updateItemsStates(globalStates){
|
|
||||||
let pageStates = {}; |
|
||||||
let [group, page] = getPageGroup(); |
|
||||||
let elements = document.body.querySelectorAll('.pimvr-item'); |
|
||||||
for (let item of elements) { |
|
||||||
let id = item.getAttribute("id"); |
|
||||||
let position = item.getComputedAttribute("position"); |
|
||||||
pageStates[id] = {"position": position}; |
|
||||||
} |
|
||||||
globalStates[group+"_"+page] = pageStates; |
|
||||||
pimvrSaveRemote("ItemsStates", JSON.stringify(globalStates)); |
|
||||||
return "Items states saved"; |
|
||||||
|
|
||||||
} |
|
||||||
pimvrLoadRemote("ItemsStates", updateItemsStates); |
|
||||||
}
|
|
||||||
|
|
||||||
function pimvrSaveConfiguration(callback) { |
|
||||||
let configuration = {}; |
|
||||||
let elements = document.body.querySelectorAll('.pimvr-configuration'); |
|
||||||
for (let item of elements) { |
|
||||||
let id = item.getAttribute("id"); |
|
||||||
let position = item.getComputedAttribute("position"); |
|
||||||
configuration[id] = {"position": position}; |
|
||||||
} |
|
||||||
|
|
||||||
pimvrSaveRemote("Configuration", JSON.stringify(configuration)); |
|
||||||
return "Configuration saved"; |
|
||||||
} |
|
||||||
|
|
||||||
function pimvrLoadIoTData(callback) { |
|
||||||
// should give min/max ranges, here seems to be 0-1010
|
|
||||||
// to use (once normalized) as an attribute value
|
|
||||||
// used on http://jsbin.com/nucanat/edit?html,output
|
|
||||||
// warning HTTPS on tick is really hammering
|
|
||||||
const readURL = "https://fabien.benetou.fr/PIMVRdata/IoTData?action=source"; |
|
||||||
|
|
||||||
var myRequest = new XMLHttpRequest(); |
|
||||||
myRequest.open('GET', readURL); |
|
||||||
myRequest.onreadystatechange = function () { |
|
||||||
if (myRequest.readyState === 4) { |
|
||||||
callback(myRequest.responseText); |
|
||||||
} |
|
||||||
}; |
|
||||||
myRequest.send(); |
|
||||||
}
|
|
||||||
|
|
||||||
function pimvrServerRender(group, page, callback) { |
|
||||||
const readURL = "https://fabien.benetou.fr/"+group+"/"+page+"?action=serverrender"; |
|
||||||
|
|
||||||
var myRequest = new XMLHttpRequest(); |
|
||||||
myRequest.open('GET', readURL); |
|
||||||
myRequest.onreadystatechange = function () { |
|
||||||
if (myRequest.readyState === 4) { |
|
||||||
callback(JSON.parse(myRequest.responseText).res); |
|
||||||
} |
|
||||||
}; |
|
||||||
myRequest.send(); |
|
||||||
} |
|
||||||
|
|
||||||
function pimvrLoadRemoteSmarthWatchConfiguration(callback) { |
|
||||||
|
|
||||||
const readURL = "https://fabien.benetou.fr/PIMVRdata/SmartWatchConfiguration?action=source"; |
|
||||||
|
|
||||||
var myRequest = new XMLHttpRequest(); |
|
||||||
myRequest.open('GET', readURL); |
|
||||||
myRequest.onreadystatechange = function () { |
|
||||||
if (myRequest.readyState === 4) { |
|
||||||
callback(JSON.parse(myRequest.responseText)); |
|
||||||
} |
|
||||||
}; |
|
||||||
myRequest.send(); |
|
||||||
} |
|
||||||
//pimvrLoadRemoteSmarthWatchConfiguration(console.log);
|
|
||||||
// usage unclear, can be used as
|
|
||||||
// haptic feedback on interactible items e.g. vibrate on gaze
|
|
||||||
// controller backup e.g. gaze+click
|
|
||||||
// controller locator e.g. making 2 bright columns
|
|
||||||
// heart rate monitor (sadly not with PebbleTime) to reshape experience
|
|
||||||
|
|
||||||
function pimvrLoadRemoteMetadata(group, page, callback, query) { |
|
||||||
|
|
||||||
const readURL = "https://fabien.benetou.fr/"+group+"/"+page+"?action=metajson"; |
|
||||||
|
|
||||||
var myRequest = new XMLHttpRequest(); |
|
||||||
myRequest.open('GET', readURL+"&query="+query, true); |
|
||||||
myRequest.onreadystatechange = function () { |
|
||||||
if (myRequest.readyState === 4) { |
|
||||||
callback(JSON.parse(myRequest.responseText)); |
|
||||||
} |
|
||||||
}; |
|
||||||
myRequest.send(); |
|
||||||
} |
|
||||||
|
|
||||||
function pimvrLoadRemote(page, callback) { |
|
||||||
|
|
||||||
const readURL = "https://fabien.benetou.fr/PIMVRdata/"+page+"?action=source"; |
|
||||||
// assumes JSON
|
|
||||||
|
|
||||||
var myRequest = new XMLHttpRequest(); |
|
||||||
myRequest.open('GET', readURL); |
|
||||||
myRequest.onreadystatechange = function () { |
|
||||||
if (myRequest.readyState === 4) { |
|
||||||
callback(JSON.parse(myRequest.responseText)); |
|
||||||
} |
|
||||||
}; |
|
||||||
myRequest.send(); |
|
||||||
} |
|
||||||
|
|
||||||
function pimvrSaveRemote(page, data) { |
|
||||||
const writeURL = "https://fabien.benetou.fr/PIMVRdata/"+page+"?action=edit"; |
|
||||||
|
|
||||||
var myWriteRequest = new XMLHttpRequest(); |
|
||||||
myWriteRequest.open('POST', writeURL, true); |
|
||||||
myWriteRequest.setRequestHeader("Content-Type", |
|
||||||
"application/x-www-form-urlencoded"); |
|
||||||
myWriteRequest.onreadystatechange = function () { |
|
||||||
if (myWriteRequest.readyState === 4) { |
|
||||||
//console.log(myWriteRequest.responseText);
|
|
||||||
console.log("Save on "+page+" sucessful"); |
|
||||||
} |
|
||||||
}; |
|
||||||
console.log("trying to open "+writeURL+"post=1&author=PIMVR&authpw=edit_password&text="+data) |
|
||||||
myWriteRequest.send("post=1&author=PIMVR&authpw=edit_password&text="+data); |
|
||||||
// cf http://www.pmwiki.org/wiki/PmWiki/EditingAPI
|
|
||||||
} |
|
||||||
|
|
||||||
function loadRemoteGraph(callback, params){ |
|
||||||
const myDataURL = "https://vatelier.net/MyDemo/newtooling/wiki_graph.json"; |
|
||||||
// not that as agressive as it gets cached
|
|
||||||
|
|
||||||
var myRequest = new XMLHttpRequest(); |
|
||||||
myRequest.open('GET', myDataURL); |
|
||||||
myRequest.onreadystatechange = function () { |
|
||||||
if (myRequest.readyState === 4) { |
|
||||||
//window.PIMgraph = JSON.parse(myRequest.responseText).Nodes;
|
|
||||||
callback(JSON.parse(myRequest.responseText).Nodes, params); |
|
||||||
} |
|
||||||
}; |
|
||||||
myRequest.send(); |
|
||||||
} |
|
||||||
|
|
||||||
function getMyNeighbours(nodes, page){ |
|
||||||
console.log(nodes[page].Targets); |
|
||||||
} |
|
||||||
|
|
||||||
function getPageGroup(){
|
|
||||||
let group = QueryString.group || "Main"; |
|
||||||
let page = QueryString.page || "HomePage"; |
|
||||||
return [group, page]; |
|
||||||
} |
|
||||||
|
|
@ -1,187 +0,0 @@ |
|||||||
<!DOCTYPE html> |
|
||||||
<html lang="en"> |
|
||||||
<head> |
|
||||||
<!-- Required meta tags --> |
|
||||||
<meta charset="utf-8"> |
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> |
|
||||||
|
|
||||||
<!-- Bootstrap CSS --> |
|
||||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css" integrity="sha384-/Y6pD6FV/Vv2HJnA6t+vslU6fwYXjCFtcEpHbNJ0lyAFsXTsjBbfaDjzALeQsN6M" crossorigin="anonymous"> |
|
||||||
<link href="https://getbootstrap.com/docs/4.0/examples/signin/signin.css" rel="stylesheet"> |
|
||||||
|
|
||||||
<script src="clipboard.min.js"></script> |
|
||||||
<script src="upload/server/php/files/usersdb.js"></script> |
|
||||||
</head> |
|
||||||
<style> |
|
||||||
body { background-color: transparent; } |
|
||||||
|
|
||||||
#email, #sms { |
|
||||||
font-size: xx-large; |
|
||||||
} |
|
||||||
</style> |
|
||||||
|
|
||||||
<body> |
|
||||||
|
|
||||||
<img src="productlogo.png"> |
|
||||||
|
|
||||||
<script> |
|
||||||
var tickMark = "✔"; |
|
||||||
var uploadedURL = "upload/server/php/files/"; |
|
||||||
var images = [] |
|
||||||
var url = "/" |
|
||||||
var urlParameters = "?customimages=" |
|
||||||
|
|
||||||
function generateURL(){ |
|
||||||
//var url = "https://learnwebvr.xyz/importer.html" |
|
||||||
for (var image of images){ |
|
||||||
urlParameters += image + "," |
|
||||||
} |
|
||||||
console.log ("URL", urlParameters ); |
|
||||||
|
|
||||||
var encodedURL = url + urlParameters |
|
||||||
|
|
||||||
encodedURL += "&environment=" + document.querySelector("#environment").value |
|
||||||
document.querySelector("#link").href = encodedURL |
|
||||||
document.querySelector("#copyBtn").setAttribute("data-clipboard-text", encodedURL) |
|
||||||
document.querySelector("#generateURL").style.display = "block" |
|
||||||
|
|
||||||
document.querySelector("#email").href = "mailto:?&subject=session&body=" + encodeURIComponent( encodedURL ) |
|
||||||
// tested on iPhone 6S and Pixel2 and desktop |
|
||||||
|
|
||||||
if( /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) ) { |
|
||||||
} |
|
||||||
else { |
|
||||||
document.querySelector("#copydiv").className = "cold-md-6" |
|
||||||
document.querySelector("#emaildiv").className = "cold-md-6" |
|
||||||
} |
|
||||||
} |
|
||||||
</script> |
|
||||||
<div class="container"> |
|
||||||
|
|
||||||
<form class="form-signin"> |
|
||||||
|
|
||||||
Environment: |
|
||||||
<select id="environment"> |
|
||||||
<option name="contact">contact</option> |
|
||||||
<option name="egypt">egypt</option> |
|
||||||
<option name="checkerboard">checkerboard</option> |
|
||||||
<option name="forest" selected="selected">forest</option> |
|
||||||
<option name="goaland">goaland</option> |
|
||||||
<option name="yavapai">yavapai</option> |
|
||||||
<option name="goldmine">goldmine</option> |
|
||||||
<option name="threetowers">threetowers</option> |
|
||||||
<option name="poison">poison</option> |
|
||||||
<option name="arches">arches</option> |
|
||||||
<option name="tron">tron</option> |
|
||||||
<option name="japan">japan</option> |
|
||||||
<option name="dream">dream</option> |
|
||||||
<option name="volcano">volcano</option> |
|
||||||
<option name="starry">starry</option> |
|
||||||
<option name="osiris">osiris</option> |
|
||||||
</select> |
|
||||||
|
|
||||||
<button style="margin-top:2px;" type="button" onclick="unhide('#fileupload2');" class="btn btn-primary btn-block">Upload your office photos<br/>(post-its, white board, etc)</button> |
|
||||||
|
|
||||||
No content available? Try <a target="_blank" href="https://learnwebvr.xyz/?customimages=upload/server/php/files/photo6035074301452988871.jpg,upload/server/php/files/photo6035074301452988870.jpg,upload/server/php/files/IMG_6496 (1).jpg"> this example instead</a>! |
|
||||||
|
|
||||||
<input style="display:none;" id="fileupload2" type="file" name="files[]" data-url="upload/server/php/" multiple> |
|
||||||
<div style="display:none;" id="fileuploaded2">Files uploaded: </div> |
|
||||||
<div style="opacity:0.7;" id="uploadrate"></div> |
|
||||||
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script> |
|
||||||
<script src="upload/js/vendor/jquery.ui.widget.js"></script> |
|
||||||
<script src="upload/js/jquery.iframe-transport.js"></script> |
|
||||||
<script src="upload/js/jquery.fileupload.js"></script> |
|
||||||
<script> |
|
||||||
|
|
||||||
function unhide( element ){ |
|
||||||
document.querySelector(element).style.display = "block"; |
|
||||||
} |
|
||||||
|
|
||||||
$(function () { |
|
||||||
|
|
||||||
$('#fileupload2').fileupload({ |
|
||||||
dataType: 'json', |
|
||||||
done: function (e, data) { |
|
||||||
$.each(data.result.files, function (index, file) { |
|
||||||
var imageURL = uploadedURL + (file.name) |
|
||||||
images.push(imageURL) |
|
||||||
var uploaded = document.querySelector("#fileuploaded2"); |
|
||||||
uploaded.style.display = "block"; |
|
||||||
uploaded.innerHTML += tickMark; |
|
||||||
}); |
|
||||||
} |
|
||||||
}); |
|
||||||
|
|
||||||
}); |
|
||||||
</script> |
|
||||||
|
|
||||||
<button style="margin-top:2px;" type="button" onclick="generateURL()" id="generate-simulation" class="btn btn-lg btn-primary btn-block">Generate your<br/>relax+think space</button> |
|
||||||
</form> |
|
||||||
</div> |
|
||||||
|
|
||||||
<div id="generateURL" style="display:none; text-align:center;" class="container"> |
|
||||||
<div class="container"> |
|
||||||
<a id="link" target="_blank" href=""><button type="button" class="btn btn-lg btn-primary btn-block">Launch relax+think space</button></a> |
|
||||||
</div> |
|
||||||
<br/> |
|
||||||
<div class="container"> |
|
||||||
<div class="row"> |
|
||||||
<div class="col-md-4" id="copydiv"> |
|
||||||
<button class="btn" data-clipboard-text="" id="copyBtn">Copy to clipboard</button> |
|
||||||
<script> |
|
||||||
new Clipboard('.btn'); |
|
||||||
</script> |
|
||||||
</div> |
|
||||||
<div class="col-md-4" id="emaildiv"> |
|
||||||
<a id="email" href="" target="_blank">email link</a> |
|
||||||
</div> |
|
||||||
</div> |
|
||||||
</div> |
|
||||||
</div> |
|
||||||
|
|
||||||
<script> |
|
||||||
function login(){ |
|
||||||
document.querySelector("#login").style.display = "block" |
|
||||||
} |
|
||||||
function loginViaEmail(){ |
|
||||||
var email = document.querySelector("#useremail").value |
|
||||||
var userspace = database[email] |
|
||||||
if (!userspace){ |
|
||||||
document.querySelector("#nouser").style.display = "block" |
|
||||||
return |
|
||||||
} |
|
||||||
|
|
||||||
document.querySelector("#spacesholder").style.display = "block" |
|
||||||
var spaces = document.querySelector("#spaces") |
|
||||||
var space = document.createElement("li") |
|
||||||
var spacelink = document.createElement("a") |
|
||||||
var images = userspace.files |
|
||||||
for (var image of images){ |
|
||||||
urlParameters += image.filename + "," |
|
||||||
} |
|
||||||
spacelink.href = url + urlParameters |
|
||||||
spacelink.target = "_blank" |
|
||||||
spacelink.innerHTML = userspace.last_login |
|
||||||
space.appendChild(spacelink) |
|
||||||
console.log(space) |
|
||||||
spaces.appendChild(space) |
|
||||||
console.log("userspace", userspace) |
|
||||||
} |
|
||||||
</script> |
|
||||||
|
|
||||||
<div>Already have an account? <span onclick="login()" style="text-decoration: underline;">Log-in</span> |
|
||||||
<form> |
|
||||||
<div id="login" style="display:none"><input id="useremail"/> |
|
||||||
<button style="margin-top:2px;" type="button" onclick="loginViaEmail()" id="loginemail" class="btn btn-lg btn-primary btn-block">Login</button> |
|
||||||
</div> |
|
||||||
</form> |
|
||||||
<div style="display:none" id="nouser">User not found. Double check your email address then contact fabien@iterative-explorations.com</a></div> |
|
||||||
<div style="display:none" id="spacesholder">Your spaces: |
|
||||||
<ul id="spaces"></ul> |
|
||||||
</div> |
|
||||||
</div> |
|
||||||
|
|
||||||
<h3 style="position:absolute; bottom:0px; right:0px;">A product by <a href="https://iterative-explorations.com"><img width="200px" src="https://iterative-explorations.com/logo.svg"></a>.</h3> |
|
||||||
|
|
||||||
</body> |
|
||||||
</html> |
|
File diff suppressed because it is too large
Load Diff
@ -1,18 +0,0 @@ |
|||||||
var database = { |
|
||||||
"fabien@benetou.fr" : { |
|
||||||
"username": "fabien", |
|
||||||
"password": "test", |
|
||||||
"last_login": 1576749381516, |
|
||||||
"environment": "forest", |
|
||||||
"files" : [ |
|
||||||
{ "position": "-2 1 -2", "filename" : "upload/server/php/files/C1C4E2F7-865A-4DB4-9274-1EA69B6C4A7E.jpeg" }, |
|
||||||
{ "position": "0 1 -2", "filename" : "upload/server/php/files/4A21A76C-F375-4884-9040-BC51A24057A6.jpeg" }, |
|
||||||
{ "position": "2 1 -2", "filename" : "upload/server/php/files/2CD9932F-891C-4BA5-BDDA-A3B39B39CF09.jpeg" } |
|
||||||
], |
|
||||||
"categories" : [ |
|
||||||
{ "position": "-2 1 -2", "color" : "orange", "label" : "waiting" }, |
|
||||||
{ "position": "0 1 -2", "color" : "red", "label" : "in progress" }, |
|
||||||
{ "position": "2 1 -2", "color" : "green", "label" : "completed" } |
|
||||||
] |
|
||||||
} |
|
||||||
} |
|
@ -1,17 +0,0 @@ |
|||||||
<?php |
|
||||||
/* |
|
||||||
* jQuery File Upload Plugin PHP Example |
|
||||||
* https://github.com/blueimp/jQuery-File-Upload |
|
||||||
* |
|
||||||
* Copyright 2010, Sebastian Tschan |
|
||||||
* https://blueimp.net |
|
||||||
* |
|
||||||
* Licensed under the MIT license: |
|
||||||
* https://opensource.org/licenses/MIT |
|
||||||
*/ |
|
||||||
|
|
||||||
error_reporting(E_ALL | E_STRICT); |
|
||||||
require('UploadHandler.php'); |
|
||||||
$upload_handler = new UploadHandler(array( |
|
||||||
'accept_file_types' => '/\.(png|jpe?g|mp4)$/i' |
|
||||||
)); |
|
@ -0,0 +1,105 @@ |
|||||||
|
/* global AFRAME, THREE */ |
||||||
|
|
||||||
|
/** |
||||||
|
* Implement AABB collision detection for entities with a mesh. |
||||||
|
* (https://en.wikipedia.org/wiki/Minimum_bounding_box#Axis-aligned_minimum_bounding_box)
|
||||||
|
* It sets the specified state on the intersected entities. |
||||||
|
* |
||||||
|
* @property {string} objects - Selector of the entities to test for collision. |
||||||
|
* @property {string} state - State to set on collided entities. |
||||||
|
* |
||||||
|
*/ |
||||||
|
AFRAME.registerComponent('aabb-collider', { |
||||||
|
schema: { |
||||||
|
objects: {default: ''}, |
||||||
|
state: {default: 'collided'} |
||||||
|
}, |
||||||
|
|
||||||
|
init: function () { |
||||||
|
this.els = []; |
||||||
|
this.collisions = []; |
||||||
|
this.elMax = new THREE.Vector3(); |
||||||
|
this.elMin = new THREE.Vector3(); |
||||||
|
}, |
||||||
|
|
||||||
|
/** |
||||||
|
* Update list of entities to test for collision. |
||||||
|
*/ |
||||||
|
update: function () { |
||||||
|
var data = this.data; |
||||||
|
var objectEls; |
||||||
|
|
||||||
|
// Push entities into list of els to intersect.
|
||||||
|
if (data.objects) { |
||||||
|
objectEls = this.el.sceneEl.querySelectorAll(data.objects); |
||||||
|
} else { |
||||||
|
// If objects not defined, intersect with everything.
|
||||||
|
objectEls = this.el.sceneEl.children; |
||||||
|
} |
||||||
|
// Convert from NodeList to Array
|
||||||
|
this.els = Array.prototype.slice.call(objectEls); |
||||||
|
}, |
||||||
|
|
||||||
|
tick: (function () { |
||||||
|
var boundingBox = new THREE.Box3(); |
||||||
|
return function () { |
||||||
|
var collisions = []; |
||||||
|
var el = this.el; |
||||||
|
var mesh = el.getObject3D('mesh'); |
||||||
|
var self = this; |
||||||
|
// No mesh, no collisions
|
||||||
|
if (!mesh) { return; } |
||||||
|
// Update the bounding box to account for rotations and
|
||||||
|
// position changes.
|
||||||
|
updateBoundingBox(); |
||||||
|
// Update collisions.
|
||||||
|
this.els.forEach(intersect); |
||||||
|
// Emit events.
|
||||||
|
collisions.forEach(handleHit); |
||||||
|
// No collisions.
|
||||||
|
if (collisions.length === 0) { self.el.emit('hit', {el: null}); } |
||||||
|
// Updated the state of the elements that are not intersected anymore.
|
||||||
|
this.collisions.filter(function (el) { |
||||||
|
return collisions.indexOf(el) === -1; |
||||||
|
}).forEach(function removeState (el) { |
||||||
|
el.removeState(self.data.state); |
||||||
|
el.emit('hitend'); |
||||||
|
}); |
||||||
|
// Store new collisions
|
||||||
|
this.collisions = collisions; |
||||||
|
|
||||||
|
// AABB collision detection
|
||||||
|
function intersect (el) { |
||||||
|
var intersected; |
||||||
|
var mesh = el.getObject3D('mesh'); |
||||||
|
var elMin; |
||||||
|
var elMax; |
||||||
|
if (!mesh) { return; } |
||||||
|
boundingBox.setFromObject(mesh); |
||||||
|
elMin = boundingBox.min; |
||||||
|
elMax = boundingBox.max; |
||||||
|
// Bounding boxes are always aligned with the world coordinate system.
|
||||||
|
// The collision test checks for the conditions where cubes intersect.
|
||||||
|
// It's an extension to 3 dimensions of this approach (with the condition negated)
|
||||||
|
// https://www.youtube.com/watch?v=ghqD3e37R7E
|
||||||
|
intersected = (self.elMin.x <= elMax.x && self.elMax.x >= elMin.x) && |
||||||
|
(self.elMin.y <= elMax.y && self.elMax.y >= elMin.y) && |
||||||
|
(self.elMin.z <= elMax.z && self.elMax.z >= elMin.z); |
||||||
|
if (!intersected) { return; } |
||||||
|
collisions.push(el); |
||||||
|
} |
||||||
|
|
||||||
|
function handleHit (hitEl) { |
||||||
|
hitEl.emit('hit'); |
||||||
|
hitEl.addState(self.data.state); |
||||||
|
self.el.emit('hit', {el: hitEl}); |
||||||
|
} |
||||||
|
|
||||||
|
function updateBoundingBox () { |
||||||
|
boundingBox.setFromObject(mesh); |
||||||
|
self.elMin.copy(boundingBox.min); |
||||||
|
self.elMax.copy(boundingBox.max); |
||||||
|
} |
||||||
|
}; |
||||||
|
})() |
||||||
|
}); |
@ -0,0 +1,89 @@ |
|||||||
|
/* global AFRAME, THREE */ |
||||||
|
|
||||||
|
/** |
||||||
|
* Handles events coming from the hand-controls. |
||||||
|
* Determines if the entity is grabbed or released. |
||||||
|
* Updates its position to move along the controller. |
||||||
|
*/ |
||||||
|
AFRAME.registerComponent('grab', { |
||||||
|
init: function () { |
||||||
|
this.GRABBED_STATE = 'grabbed'; |
||||||
|
// Bind event handlers
|
||||||
|
this.onHit = this.onHit.bind(this); |
||||||
|
this.onGripOpen = this.onGripOpen.bind(this); |
||||||
|
this.onGripClose = this.onGripClose.bind(this); |
||||||
|
this.currentPosition = new THREE.Vector3(); |
||||||
|
}, |
||||||
|
|
||||||
|
play: function () { |
||||||
|
var el = this.el; |
||||||
|
el.addEventListener('hit', this.onHit); |
||||||
|
el.addEventListener('buttondown', this.onGripClose); |
||||||
|
el.addEventListener('buttonup', this.onGripOpen); |
||||||
|
}, |
||||||
|
|
||||||
|
pause: function () { |
||||||
|
var el = this.el; |
||||||
|
el.removeEventListener('hit', this.onHit); |
||||||
|
el.addEventListener('buttondown', this.onGripClose); |
||||||
|
el.addEventListener('buttonup', this.onGripOpen); |
||||||
|
}, |
||||||
|
|
||||||
|
onGripClose: function (evt) { |
||||||
|
if (this.grabbing) { return; } |
||||||
|
this.grabbing = true; |
||||||
|
this.pressedButtonId = evt.detail.id; |
||||||
|
delete this.previousPosition; |
||||||
|
}, |
||||||
|
|
||||||
|
onGripOpen: function (evt) { |
||||||
|
var hitEl = this.hitEl; |
||||||
|
if (this.pressedButtonId !== evt.detail.id) { return; } |
||||||
|
this.grabbing = false; |
||||||
|
if (!hitEl) { return; } |
||||||
|
hitEl.removeState(this.GRABBED_STATE); |
||||||
|
hitEl.emit('grabend'); |
||||||
|
this.hitEl = undefined; |
||||||
|
}, |
||||||
|
|
||||||
|
onHit: function (evt) { |
||||||
|
var hitEl = evt.detail.el; |
||||||
|
// If the element is already grabbed (it could be grabbed by another controller).
|
||||||
|
// If the hand is not grabbing the element does not stick.
|
||||||
|
// If we're already grabbing something you can't grab again.
|
||||||
|
if (!hitEl || hitEl.is(this.GRABBED_STATE) || !this.grabbing || this.hitEl) { return; } |
||||||
|
hitEl.addState(this.GRABBED_STATE); |
||||||
|
this.hitEl = hitEl; |
||||||
|
}, |
||||||
|
|
||||||
|
tick: function () { |
||||||
|
var hitEl = this.hitEl; |
||||||
|
var position; |
||||||
|
if (!hitEl) { return; } |
||||||
|
this.updateDelta(); |
||||||
|
position = hitEl.getAttribute('position'); |
||||||
|
hitEl.setAttribute('position', { |
||||||
|
x: position.x + this.deltaPosition.x, |
||||||
|
y: position.y + this.deltaPosition.y, |
||||||
|
z: position.z + this.deltaPosition.z |
||||||
|
}); |
||||||
|
}, |
||||||
|
|
||||||
|
updateDelta: function () { |
||||||
|
var currentPosition = this.currentPosition; |
||||||
|
this.el.object3D.updateMatrixWorld(); |
||||||
|
currentPosition.setFromMatrixPosition(this.el.object3D.matrixWorld); |
||||||
|
if (!this.previousPosition) { |
||||||
|
this.previousPosition = new THREE.Vector3(); |
||||||
|
this.previousPosition.copy(currentPosition); |
||||||
|
} |
||||||
|
var previousPosition = this.previousPosition; |
||||||
|
var deltaPosition = { |
||||||
|
x: currentPosition.x - previousPosition.x, |
||||||
|
y: currentPosition.y - previousPosition.y, |
||||||
|
z: currentPosition.z - previousPosition.z |
||||||
|
}; |
||||||
|
this.previousPosition.copy(currentPosition); |
||||||
|
this.deltaPosition = deltaPosition; |
||||||
|
} |
||||||
|
}); |
@ -0,0 +1,32 @@ |
|||||||
|
AFRAME.registerComponent("watch", { |
||||||
|
init: function() { |
||||||
|
this.timer = AFRAME.utils.getUrlParameter('timer') |
||||||
|
if (!this.timer) this.timer = 300 //5min
|
||||||
|
document.querySelector("#watch").setAttribute("text","value:" + this.formatSeconds(this.timer)) |
||||||
|
this.tick = AFRAME.utils.throttleTick(this.tick, 1000, this); |
||||||
|
// details https://aframe.io/docs/1.0.0/core/utils.html#aframe-utils-throttle-function-minimuminterval-optionalcontext
|
||||||
|
}, |
||||||
|
|
||||||
|
formatSeconds: function(secs) { |
||||||
|
function pad(n) { |
||||||
|
return (n < 10 ? "0" + n : n) |
||||||
|
} |
||||||
|
|
||||||
|
var m = Math.floor(secs / 60); |
||||||
|
var s = Math.floor(secs - m * 60); |
||||||
|
|
||||||
|
return pad(m) + ":" + pad(s); |
||||||
|
}, |
||||||
|
|
||||||
|
tick: function (t, dt) { |
||||||
|
var time = Number( this.timer ) |
||||||
|
console.log(this.timer); |
||||||
|
if(this.timer > 0){ |
||||||
|
this.timer-- |
||||||
|
} else { |
||||||
|
this.timer = 0 |
||||||
|
document.querySelector("#endMessage").setAttribute("visible","true") |
||||||
|
} |
||||||
|
document.querySelector("#watch").setAttribute("text","value:"+ this.formatSeconds(time)) |
||||||
|
}, |
||||||
|
}); |
@ -0,0 +1,72 @@ |
|||||||
|
<a-scene background="color: #FAFAFA" vr-mode-ui="enterVRButton: #vrButton"> |
||||||
|
<a-assets> |
||||||
|
<a-mixin id="sticker" |
||||||
|
event-set__grab="material.color: #FFEF4F" |
||||||
|
event-set__grabend="material.color: #F2E646" |
||||||
|
event-set__hit="material.color: #F2E646" |
||||||
|
event-set__hitend="material.color: #FEFEFE" |
||||||
|
geometry="primitive: box; height: 0.2; width: 0.2; depth: 0.01" |
||||||
|
material="color: #FEFEFE;"> |
||||||
|
</a-mixin> |
||||||
|
</a-assets> |
||||||
|
<!-- Extra lights --> |
||||||
|
<a-light type="directional" color="#ffffff" intensity="0.3" position="0.5 2.2 2.866"></a-light> |
||||||
|
<!-- Board --> |
||||||
|
<a-entity position="0 0 -0.5"> |
||||||
|
<a-entity |
||||||
|
position="-1 2 0" |
||||||
|
text="color: white; align: center; width: 2; font: exo2semibold; |
||||||
|
value: To Do" |
||||||
|
></a-entity> |
||||||
|
<a-plane position="-0.5 1.25 0" width="0.01" height="1.6" material="shader: flat; color: #ffffff"></a-plane> |
||||||
|
<a-entity |
||||||
|
position="0 2 0" |
||||||
|
text="color: white; align: center; width: 2; font: exo2semibold; |
||||||
|
value: In Progress" |
||||||
|
></a-entity> |
||||||
|
<a-plane position="0.5 1.25 0" width="0.01" height="1.6" material="shader: flat; color: #ffffff"></a-plane> |
||||||
|
<a-entity |
||||||
|
position="1 2 0" |
||||||
|
text="color: white; align: center; width: 2; font: exo2semibold; |
||||||
|
value: Done" |
||||||
|
></a-entity> |
||||||
|
</a-entity> |
||||||
|
<!-- End message--> |
||||||
|
<a-entity id="endMessage" |
||||||
|
position="0 2.2 -1" |
||||||
|
text="color: #FFCC00; align: center; width: 4; font: exo2semibold; |
||||||
|
value: SESSION FINISHED" visible="false" |
||||||
|
></a-entity> |
||||||
|
<!-- Stickers --> |
||||||
|
|
||||||
|
<a-entity position="0 1.5 -0.2"> |
||||||
|
<a-entity class="sticker" mixin="sticker" position="-0.30 0 0"> |
||||||
|
<a-entity |
||||||
|
position="0 0 0.005" |
||||||
|
text="color: black; align: center; width: 1; font: exo2semibold; |
||||||
|
value: Task #1" |
||||||
|
></a-entity> |
||||||
|
</a-entity> |
||||||
|
<a-entity class="sticker" mixin="sticker" position="0 0 0"> |
||||||
|
<a-entity |
||||||
|
position="0 0 0.005" |
||||||
|
text="color: black; align: center; width: 1; font: exo2semibold; |
||||||
|
value: Task #2" |
||||||
|
></a-entity> |
||||||
|
</a-entity> |
||||||
|
<a-entity class="sticker" mixin="sticker" position="0.30 0 0"> |
||||||
|
<a-entity |
||||||
|
position="0 0 0.005" |
||||||
|
text="color: black; align: center; width: 1; font: exo2semibold; |
||||||
|
value: Task #3" |
||||||
|
></a-entity> |
||||||
|
</a-entity> |
||||||
|
</a-entity> |
||||||
|
|
||||||
|
<a-entity environment="preset: forest;"></a-entity> |
||||||
|
<a-entity hand-controls="left" aabb-collider="objects: .sticker;" grab> |
||||||
|
<a-entity watch id="watch" position="-0.02 -0.005 0.16" rotation="0 -90 180" text="color: white; align: center; width: .5; font: exo2semibold; |
||||||
|
value: 00:59"></a-entity> |
||||||
|
</a-entity> |
||||||
|
<a-entity hand-controls="right" aabb-collider="objects: .sticker;" grab></a-entity> |
||||||
|
</a-scene> |
@ -0,0 +1,58 @@ |
|||||||
|
html { |
||||||
|
background: #000; |
||||||
|
} |
||||||
|
|
||||||
|
#vrButton { |
||||||
|
position: absolute; |
||||||
|
background: url('../dist/assets/img/enter-vr-button-background.png') no-repeat; |
||||||
|
background-size: cover; |
||||||
|
border: 0; |
||||||
|
cursor: pointer; |
||||||
|
right: 20px; |
||||||
|
bottom: 20px; |
||||||
|
text-decoration: none; |
||||||
|
z-index: 9999999; |
||||||
|
width: 64px; |
||||||
|
height: 64px; |
||||||
|
} |
||||||
|
|
||||||
|
#vrButton.a-hidden { |
||||||
|
visibility: hidden; |
||||||
|
} |
||||||
|
|
||||||
|
#vrButton:active { |
||||||
|
border: 0; |
||||||
|
} |
||||||
|
|
||||||
|
#vrButton:hover { |
||||||
|
background-position: 0 -64.5px; |
||||||
|
} |
||||||
|
|
||||||
|
#vrButton p { |
||||||
|
bottom: 45px; |
||||||
|
color: #FFF; |
||||||
|
font-size: 12px; |
||||||
|
font-family: monospace; |
||||||
|
font-weight: bold; |
||||||
|
text-align: center; |
||||||
|
text-transform: uppercase; |
||||||
|
position: relative; |
||||||
|
} |
||||||
|
|
||||||
|
.a-loader-title { |
||||||
|
animation: loaderTitle 1s infinite alternate; |
||||||
|
color: rgba(0,0,0,0); |
||||||
|
background: none; |
||||||
|
background-image: url('../dist/assets/img/loadingLogo.png'); |
||||||
|
height: 36.2vh; |
||||||
|
background-repeat: no-repeat; |
||||||
|
background-position: center; |
||||||
|
margin-top: 0; |
||||||
|
background-size: contain; |
||||||
|
} |
||||||
|
|
||||||
|
@keyframes loaderTitle { |
||||||
|
0% { opacity: 1; } |
||||||
|
50% { opacity: 0.8; } |
||||||
|
100% { opacity: 1; } |
||||||
|
} |
@ -0,0 +1,17 @@ |
|||||||
|
function requireAll (req) { req.keys().forEach(req); } |
||||||
|
|
||||||
|
console.time = () => {}; |
||||||
|
console.timeEnd = () => {}; |
||||||
|
|
||||||
|
require('aframe-environment-component'); |
||||||
|
require('aframe-event-set-component'); |
||||||
|
|
||||||
|
require('./components/aabb-collider'); |
||||||
|
require('./components/grab'); |
||||||
|
require('./components/watch'); |
||||||
|
|
||||||
|
require('./index.css') |
||||||
|
|
||||||
|
require('./home.html') |
||||||
|
|
||||||
|
if (module.hot) { module.hot.accept(); } |
@ -0,0 +1,66 @@ |
|||||||
|
const path = require('path') |
||||||
|
|
||||||
|
module.exports = { |
||||||
|
entry: { |
||||||
|
index: './src/index.js' |
||||||
|
}, |
||||||
|
output: { |
||||||
|
filename: 'bundle.js', |
||||||
|
path: path.resolve(__dirname, 'dist') |
||||||
|
}, |
||||||
|
devServer: { |
||||||
|
https: true, |
||||||
|
port: 3000, |
||||||
|
contentBase: './dist' |
||||||
|
}, |
||||||
|
devtool: 'source-map', |
||||||
|
plugins: [ |
||||||
|
], |
||||||
|
optimization: { |
||||||
|
}, |
||||||
|
module: { |
||||||
|
rules: [ |
||||||
|
{ |
||||||
|
test: /\.js/, |
||||||
|
exclude: /(node_modules)/, |
||||||
|
use: ['babel-loader', 'aframe-super-hot-loader'] |
||||||
|
}, |
||||||
|
{ |
||||||
|
test: /\.json/, |
||||||
|
exclude: /(node_modules)/, |
||||||
|
type: 'javascript/auto', |
||||||
|
loader: ['json-loader'] |
||||||
|
}, |
||||||
|
{ |
||||||
|
test: /\.html/, |
||||||
|
exclude: /(node_modules)/, |
||||||
|
use: [ |
||||||
|
'aframe-super-hot-html-loader', |
||||||
|
{ |
||||||
|
loader: 'html-require-loader', |
||||||
|
options: { |
||||||
|
root: path.resolve(__dirname, 'src') |
||||||
|
} |
||||||
|
} |
||||||
|
] |
||||||
|
}, |
||||||
|
{ |
||||||
|
test: /\.glsl/, |
||||||
|
exclude: /(node_modules)/, |
||||||
|
loader: 'webpack-glsl-loader' |
||||||
|
}, |
||||||
|
{ |
||||||
|
test: /\.css$/, |
||||||
|
exclude: /(node_modules)/, |
||||||
|
use: ['style-loader', 'css-loader'] |
||||||
|
}, |
||||||
|
{ |
||||||
|
test: /\.(png|jpg)/, |
||||||
|
loader: 'url-loader' |
||||||
|
} |
||||||
|
] |
||||||
|
}, |
||||||
|
resolve: { |
||||||
|
modules: [path.join(__dirname, 'node_modules')] |
||||||
|
} |
||||||
|
} |
Loading…
Reference in new issue