diff --git a/README.md b/README.md new file mode 100644 index 0000000..1730dce --- /dev/null +++ b/README.md @@ -0,0 +1,90 @@ +

Exokit Studio

+

An interface for the Exokit Engine.

+ +

+ + + + + + +

+ +
+ Site + — + Docs + — + Discord + — + Twitter + — + Email List +
+ +## Examples + +Hands Reality Tab +Live Reload Magic Leap +Tutorial Reality Tab + +Reality Projection with HTC Vive and Magic Leap +Emukit +Various Exokit Apps + +*Find more examples on [YouTube](https://www.youtube.com/channel/UC87Q7_5ooY8FSLwOec52ZPQ).* + + +## Overview + +Exokit Studio **enables developers to build XR experiences with ease** by having a seamless interface to the Exokit Engine. Studio prefers using GUIs instead of command lines with difficult to remember arguments to use the functionality you want. + +Studio uses the [Exokit Engine](https://github.com/exokitxr/exokit), which is written on top of Node and emulates a web browser, providing native hooks for WebGL, WebGL2, WebVR, WebXR, WebAudio, and other APIs used in immersive experiences. + +:eyeglasses: **Exokit Engine currently targets the following platforms**: +* OpenVR Desktop VR (Steam compatible) +* HTC Vive +* Valve Index * +* Oculus Desktop (Oculus Rift, Oculus Rift S) +* Oculus Mobile (Oculus Quest, Oculus Go, GearVR) +* Magic Leap +* iOS ARKit * +* Android ARCore * +* Google VR (Daydream / Cardboard / Mirage Solo) * +* Hololens / Hololens 2 * +* any XR device, start a [pull request](https://github.com/exokitxr/exokit/compare) to the Exokit Engine with a native binding if it isn't listed here! * + +\* not supported yet + +:electric_plug: **Exokit Engine powers experiences built with**: +* Three.js +* Unity +* Pixi.js +* Babylon.js +* A-Frame +* Custom WebGL frameworks +* WebAssembly, TypeScript, and any language that transpiles to JavaScript +* Unity WebVR export * +* SteamVR * +* any 3d web framework, start a [pull request](https://github.com/exokitxr/exokit/compare) to the Exokit Engine if a 3d web framework isn't currently supported! * + +\* not supported yet + +## Quickstart + +### Desktop +

Download and install Studio for current OS

+ +### Local Development + +```sh +git clone https://github.com/exokitxr/studio.git +cd studio +npm install +npm build +``` + +## Stay in Touch + +- [Join our Discord](https://discord.gg/Apk6cZN) for discussions. +- [Follow @exokitxr on Twitter](https://twitter.com/exokitxr) for updates. diff --git a/exobot.html b/exobot.html new file mode 100644 index 0000000..1930185 --- /dev/null +++ b/exobot.html @@ -0,0 +1,149 @@ + + + + + + + exobot + + + + + +

exobot

+ + + + + diff --git a/graffiti_ml.html b/graffiti_ml.html index f18989e..c59404f 100644 --- a/graffiti_ml.html +++ b/graffiti_ml.html @@ -331,7 +331,7 @@

graffiti_ml

scene.add(controllerMesh); }); - const cubeGeometry = new THREE.BoxBufferGeometry(0.02, 0.02, 0.001); + const cubeGeometry = new THREE.BoxBufferGeometry(0.05, 0.05, 0.05); const hitMeshMaterial = new THREE.MeshPhongMaterial({ color: 0x673ab7, }); diff --git a/index.html b/index.html index 9011030..ac72e7b 100644 --- a/index.html +++ b/index.html @@ -11,9 +11,14 @@ - - - - \ No newline at end of file diff --git a/ui/package.json b/ui/package.json index 3612464..ef77df8 100644 --- a/ui/package.json +++ b/ui/package.json @@ -7,7 +7,9 @@ "react": "^16.8.6", "react-dom": "^16.8.6", "react-markdown": "^4.0.8", - "react-scripts": "3.0.0" + "react-scripts": "3.0.0", + "yauzl": "^2.10.0", + "yazl": "^2.5.1" }, "scripts": { "start": "react-scripts start", diff --git a/ui/public/index.html b/ui/public/index.html index cbaaddb..0defbd8 100644 --- a/ui/public/index.html +++ b/ui/public/index.html @@ -12,7 +12,7 @@ - Exokit Launcher + Exokit Studio diff --git a/ui/public/manifest.json b/ui/public/manifest.json index fddfd1d..bc4df82 100644 --- a/ui/public/manifest.json +++ b/ui/public/manifest.json @@ -1,6 +1,6 @@ { - "short_name": "Exokit Launcher", - "name": "Exokit Launcher", + "short_name": "Exokit Studio", + "name": "Exokit Studio", "icons": [ { "src": "favicon.ico", diff --git a/ui/src/components/Engine.jsx b/ui/src/components/Engine.jsx index 57b3cf1..3e52ff1 100644 --- a/ui/src/components/Engine.jsx +++ b/ui/src/components/Engine.jsx @@ -34,16 +34,6 @@ class Engine extends React.Component { componentDidMount() { _postViewportMessage(); window.addEventListener('resize', _postViewportMessage); - - /* window.addEventListener('keydown', e => { - console.log('iframe keydown ' + e.keyCode); - }); - window.addEventListener('keyup', e => { - console.log('iframe keyup ' + e.keyCode); - }); - window.addEventListener('keypress', e => { - console.log('iframe keypress ' + e.keyCode); - }); */ } postMessage(action){ @@ -137,7 +127,7 @@ class Engine extends React.Component { e.stopPropagation(); } - onEngineRenderClick() { + onEngineRenderFocus() { this.blur(); } @@ -284,9 +274,9 @@ class Engine extends React.Component {
-
this.addTemplate('kitchenSink')}> - -
Kitchen sink
+
this.addTemplate('webXrSample')}> + +
WebXR Sample
this.addTemplate('exobot')}> @@ -338,11 +328,10 @@ class Engine extends React.Component { this.openSettings(null)}/>
-
this.onEngineRenderClick()} /> + this.onEngineRenderFocus()}/> { _postViewportMessage(); }}> @@ -365,6 +354,66 @@ class Engine extends React.Component { } } +class EngineRender extends React.Component { + /* onMouseDown(e) { + const engineRender = document.getElementById('engine-render'); + const bcr = engineRender.getBoundingClientRect(); + window.postMessage({ + method: 'viewportMouseDown', + x: e.clientX - bcr.x, + y: e.clientY - bcr.y, + button: e.button, + }); + } + onMouseUp(e) { + const engineRender = document.getElementById('engine-render'); + const bcr = engineRender.getBoundingClientRect(); + window.postMessage({ + method: 'viewportMouseUp', + x: e.clientX - bcr.x, + y: e.clientY - bcr.y, + button: e.button, + }); + } + onClick(e) { + const engineRender = document.getElementById('engine-render'); + const bcr = engineRender.getBoundingClientRect(); + window.postMessage({ + method: 'viewportClick', + x: e.clientX - bcr.x, + y: e.clientY - bcr.y, + button: e.button, + }); + } + onMouseMove(e) { + const engineRender = document.getElementById('engine-render'); + const bcr = engineRender.getBoundingClientRect(); + window.postMessage({ + method: 'viewportMouseMove', + x: e.clientX - bcr.x, + y: e.clientY - bcr.y, + }); + } + onMouseWheel(e) { + const engineRender = document.getElementById('engine-render'); + const bcr = engineRender.getBoundingClientRect(); + window.postMessage({ + method: 'viewportMouseWheel', + x: e.clientX - bcr.x, + y: e.clientY - bcr.y, + deltaX: e.deltaX, + deltaY: e.deltaY, + }); + } */ + + render() { + /*
this.onClick(e)} onMouseDown={e => this.onMouseDown(e)} onMouseUp={e => this.onMouseUp(e)} onMouseMove={e => this.onMouseMove(e)} onMouseWheel={e => this.onMouseWheel(e)} />*/ + return ( +
+ ); + } +} + class Settings extends React.Component { constructor(props) { super(props); diff --git a/ui/src/css/App.css b/ui/src/css/App.css index b6d5bd9..b85ae26 100644 --- a/ui/src/css/App.css +++ b/ui/src/css/App.css @@ -1,7 +1,8 @@ body { margin: 0; - background: #232526; /* fallback for old browsers */ + /* background: #232526;*/ /* fallback for old browsers */ + background: transparent; } .nav-pills .nav-link.active{ background-color: #B600DB; diff --git a/ui/src/css/console.css b/ui/src/css/console.css index 1b13cd4..ac1a362 100644 --- a/ui/src/css/console.css +++ b/ui/src/css/console.css @@ -1,14 +1,21 @@ .Console { position: relative; - height: 0px; + max-height: 0px; border-top: 2px solid #222; font-family: monospace; font-size: 11px; white-space: pre-wrap; - overflow-y: auto; + overflow-x: none; + overflow-y: scroll; + display: flex; + align-items: flex-end; + flex-direction:column; + justify-content:space-between; } .Console.open { - height: 100px; + height: 100%; + max-height: 100%; + min-height: 150px; } .Console .console-output { diff --git a/ui/src/css/dom.css b/ui/src/css/dom.css index 80284cd..2aff51c 100644 --- a/ui/src/css/dom.css +++ b/ui/src/css/dom.css @@ -89,6 +89,8 @@ padding-left: 10px; display: table-cell; vertical-align: middle; + position: absolute; + z-index: 1; } .Dom > .dom-detail > .dom-detail-name { font-family: monospace; diff --git a/ui/src/css/engine.css b/ui/src/css/engine.css index 2a065d1..693e57c 100644 --- a/ui/src/css/engine.css +++ b/ui/src/css/engine.css @@ -202,5 +202,6 @@ } #Engine .engine-render { flex: 1; - background-color: #000; + background-color: rgba(0.0, 0.0, 0.0, 0.0); + user-select: none; } diff --git a/ui/src/unzip.js b/ui/src/unzip.js new file mode 100644 index 0000000..c50a2d6 --- /dev/null +++ b/ui/src/unzip.js @@ -0,0 +1,109 @@ +var yauzl = require("yauzl"); +var path = require("path"); +var fs = require("fs"); +var util = require("util"); +var Transform = require("stream").Transform; + +var zipFilePath; +var args = process.argv.slice(2); +for (var i = 0; i < args.length; i++) { + var arg = args[i]; + if (zipFilePath != null) throw new Error("too many arguments"); + zipFilePath = arg; +} + + +function mkdirp(dir, cb) { + if (dir === ".") return cb(); + fs.stat(dir, function(err) { + if (err == null) return cb(); // already exists + + var parent = path.dirname(dir); + mkdirp(parent, function() { + process.stdout.write(dir.replace(/\/$/, "") + "/\n"); + fs.mkdir(dir, cb); + }); + }); +} + + +yauzl.open(zipFilePath, {lazyEntries: true}, handleZipFile); + +function handleZipFile(err, zipfile) { + if (err) throw err; + + // track when we've closed all our file handles + var handleCount = 0; + function incrementHandleCount() { + handleCount++; + } + function decrementHandleCount() { + handleCount--; + if (handleCount === 0) { + console.log("all input and output handles closed"); + } + } + + incrementHandleCount(); + zipfile.on("close", function() { + console.log("closed input file"); + decrementHandleCount(); + }); + + zipfile.readEntry(); + zipfile.on("entry", function(entry) { + if (/\/$/.test(entry.fileName)) { + // directory file names end with '/' + mkdirp(entry.fileName, function() { + if (err) throw err; + zipfile.readEntry(); + }); + } else { + // ensure parent directory exists + mkdirp(path.dirname(entry.fileName), function() { + zipfile.openReadStream(entry, function(err, readStream) { + if (err) throw err; + // report progress through large files + var byteCount = 0; + var totalBytes = entry.uncompressedSize; + var lastReportedString = byteCount + "/" + totalBytes + " 0%"; + process.stdout.write(entry.fileName + "..." + lastReportedString); + function reportString(msg) { + var clearString = ""; + for (var i = 0; i < lastReportedString.length; i++) { + clearString += "\b"; + if (i >= msg.length) { + clearString += " \b"; + } + } + process.stdout.write(clearString + msg); + lastReportedString = msg; + } + // report progress at 60Hz + var progressInterval = setInterval(function() { + reportString(byteCount + "/" + totalBytes + " " + ((byteCount / totalBytes * 100) | 0) + "%"); + }, 1000 / 60); + var filter = new Transform(); + filter._transform = function(chunk, encoding, cb) { + byteCount += chunk.length; + cb(null, chunk); + }; + filter._flush = function(cb) { + clearInterval(progressInterval); + reportString(""); + // delete the "..." + process.stdout.write("\b \b\b \b\b \b\n"); + cb(); + zipfile.readEntry(); + }; + + // pump file contents + var writeStream = fs.createWriteStream(entry.fileName); + incrementHandleCount(); + writeStream.on("close", decrementHandleCount); + readStream.pipe(filter).pipe(writeStream); + }); + }); + } + }); +} diff --git a/ui/src/zip.js b/ui/src/zip.js new file mode 100644 index 0000000..a3ffeab --- /dev/null +++ b/ui/src/zip.js @@ -0,0 +1,35 @@ +var yazl = require("yazl"); +var fs = require("fs"); + +var zipfile = new yazl.ZipFile(); +var options = {compress: true, forceZip64Format: false}; +var addStrategy = "addFile"; +var verbose = true; + +var args = process.argv.slice(2); + + +args.forEach(function(arg) { + // file thing + var stats = fs.statSync(arg); + if (stats.isFile()) { + if (verbose) console.log("addFile(" + + JSON.stringify(arg) + ", " + + JSON.stringify(arg) + ", " + + JSON.stringify(options) + ");"); + zipfile.addFile(arg, arg, options); + } else if (stats.isDirectory()) { + if (verbose) console.log("addEmptyDirectory(" + + JSON.stringify(arg) + ", "); + zipfile.addEmptyDirectory(arg); + } else { + throw new Error("what is this: " + arg); + } +}); + +var stream = fs.createWriteStream("outexokit.apk"); +zipfile.outputStream.pipe(stream); + +zipfile.end({forceZip64Format: options.forceZip64Format}, function(finalSize) { + console.log("finalSize prediction: " + finalSize); +});