wip: feat/remotestorage
This commit is contained in:
parent
52f79a44c5
commit
96e11f3594
10 changed files with 147 additions and 47 deletions
|
|
@ -20,7 +20,7 @@
|
||||||
renderer="colorManagement: false; stencil: true; antialias:true; highRefreshRate:true; foveationLevel: 0.5; toneMapping: ACESFilmic; exposure: 3.0"
|
renderer="colorManagement: false; stencil: true; antialias:true; highRefreshRate:true; foveationLevel: 0.5; toneMapping: ACESFilmic; exposure: 3.0"
|
||||||
device-orientation-permission-ui xrf-gaze-always joystick
|
device-orientation-permission-ui xrf-gaze-always joystick
|
||||||
light="defaultLightsEnabled: false">
|
light="defaultLightsEnabled: false">
|
||||||
<a-entity id="player" movement-controls touch-controls="axis:y" wasd-controls="fly:false" look-controls="magicWindowTrackingEnabled:true">
|
<a-entity id="player" movement-controls touch-controls="axis:y" wasd-controls="fly:true" look-controls="magicWindowTrackingEnabled:true">
|
||||||
<a-entity camera="fov:90" position="0 1.6 0" id="camera"></a-entity>
|
<a-entity camera="fov:90" position="0 1.6 0" id="camera"></a-entity>
|
||||||
<a-entity id="left-hand" hand-tracking-grab-controls="hand:left;modelColor:#cccccc" raycaster="objects:.ray" blink-controls="cameraRig:#player; teleportOrigin: #camera; collisionEntities: .floor">
|
<a-entity id="left-hand" hand-tracking-grab-controls="hand:left;modelColor:#cccccc" raycaster="objects:.ray" blink-controls="cameraRig:#player; teleportOrigin: #camera; collisionEntities: .floor">
|
||||||
<a-entity rotation="-35 0 0" position="0 0.1 0" id="navigator">
|
<a-entity rotation="-35 0 0" position="0 0.1 0" id="navigator">
|
||||||
|
|
|
||||||
2
make
2
make
|
|
@ -73,7 +73,7 @@ build(){
|
||||||
aframe(){
|
aframe(){
|
||||||
test -d src/3rd/js/aframe/build/aframe || git clone https://github.com/aframevr/aframe src/3rd/js/aframe/build/aframe --depth=1
|
test -d src/3rd/js/aframe/build/aframe || git clone https://github.com/aframevr/aframe src/3rd/js/aframe/build/aframe --depth=1
|
||||||
curdir=$(pwd)
|
curdir=$(pwd)
|
||||||
cd src/3rd/js/aframe/build && cp three.module.js aframe/src/lib/. # override to add extra loaders like fbx/collada e.g.
|
cd src/3rd/js/aframe/build && cp three*.js aframe/src/lib/. # override to add extra loaders like fbx/collada e.g.
|
||||||
#cd aframe && npm install && npm install troika-three-text && npm run dist
|
#cd aframe && npm install && npm install troika-three-text && npm run dist
|
||||||
cd aframe && npm install && npm run dist
|
cd aframe && npm install && npm run dist
|
||||||
cd "$curdir"
|
cd "$curdir"
|
||||||
|
|
|
||||||
36
src/3rd/js/aframe/build/three.js
Normal file
36
src/3rd/js/aframe/build/three.js
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
import * as SUPER_THREE from 'super-three';
|
||||||
|
import { DRACOLoader } from 'super-three/examples/jsm/loaders/DRACOLoader';
|
||||||
|
import { GLTFLoader } from 'super-three/examples/jsm/loaders/GLTFLoader';
|
||||||
|
//import { KTX2Loader } from 'super-three/examples/jsm/loaders/KTX2Loader';
|
||||||
|
import { OBB } from 'super-three/addons/math/OBB.js';
|
||||||
|
import { OBJLoader } from 'super-three/examples/jsm/loaders/OBJLoader';
|
||||||
|
import { FBXLoader } from 'super-three/examples/jsm/loaders/FBXLoader';
|
||||||
|
import { USDZLoader } from 'super-three/examples/jsm/loaders/USDZLoader';
|
||||||
|
import { ColladaLoader } from 'super-three/examples/jsm/loaders/ColladaLoader';
|
||||||
|
import { MTLLoader } from 'super-three/examples/jsm/loaders/MTLLoader';
|
||||||
|
import * as BufferGeometryUtils from 'super-three/examples/jsm/utils/BufferGeometryUtils';
|
||||||
|
import { LightProbeGenerator } from 'super-three/examples/jsm/lights/LightProbeGenerator';
|
||||||
|
import { TransformControls } from 'super-three/examples/jsm/controls/TransformControls.js';
|
||||||
|
import { GLTFExporter } from 'super-three/examples/jsm/exporters/GLTFExporter.js';
|
||||||
|
|
||||||
|
var THREE = globalThis.THREE = {...SUPER_THREE};
|
||||||
|
|
||||||
|
// TODO: Eventually include these only if they are needed by a component.
|
||||||
|
require('../../vendor/DeviceOrientationControls'); // THREE.DeviceOrientationControls
|
||||||
|
THREE.DRACOLoader = DRACOLoader;
|
||||||
|
THREE.GLTFLoader = GLTFLoader;
|
||||||
|
THREE.KTX2Loader = KTX2Loader;
|
||||||
|
THREE.OBJLoader = OBJLoader;
|
||||||
|
THREE.MTLLoader = MTLLoader;
|
||||||
|
THREE.FBXLoader = FBXLoader;
|
||||||
|
THREE.USDZLoader = USDZLoader;
|
||||||
|
THREE.ColladaLoader = ColladaLoader;
|
||||||
|
THREE.OBB = OBB;
|
||||||
|
THREE.BufferGeometryUtils = BufferGeometryUtils;
|
||||||
|
THREE.LightProbeGenerator = LightProbeGenerator;
|
||||||
|
THREE.TransformControls = TransformControls;
|
||||||
|
THREE.GLTFExporter = GLTFExporter;
|
||||||
|
|
||||||
|
THREE.Cache.enabled = true;
|
||||||
|
|
||||||
|
export default THREE;
|
||||||
|
|
@ -29,7 +29,7 @@ THREE.OBB = OBB;
|
||||||
THREE.BufferGeometryUtils = BufferGeometryUtils;
|
THREE.BufferGeometryUtils = BufferGeometryUtils;
|
||||||
THREE.LightProbeGenerator = LightProbeGenerator;
|
THREE.LightProbeGenerator = LightProbeGenerator;
|
||||||
THREE.TransformControls = TransformControls;
|
THREE.TransformControls = TransformControls;
|
||||||
THREE.GLTFExporter = GLTFExporter;
|
THREE.GLTFExporter = GLTFExporter || console.error("GLTFExporter not found");
|
||||||
//THREE.Text = Text
|
//THREE.Text = Text
|
||||||
|
|
||||||
export default THREE;
|
export default THREE;
|
||||||
|
|
|
||||||
|
|
@ -4,19 +4,23 @@ filesComponent = (el) => new Proxy({
|
||||||
|
|
||||||
html: (data) => `
|
html: (data) => `
|
||||||
<style type="text/css">
|
<style type="text/css">
|
||||||
#messages .msg.ui, #messages .msg.ui #files div {
|
#messages .msg.ui #files div {
|
||||||
border:none;
|
border:none;
|
||||||
padding:0;
|
padding:0;
|
||||||
|
border-radius:0;
|
||||||
margin:0;
|
margin:0;
|
||||||
box-shadow:none;
|
box-shadow:none;
|
||||||
}
|
}
|
||||||
|
.msg.ui #files{
|
||||||
|
min-width:415px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<div class="ui envelope">
|
<div class="ui envelope">
|
||||||
<div class="msg ui">
|
<div class="msg ui">
|
||||||
<div>
|
<div>
|
||||||
<div id="files">
|
<div id="files">
|
||||||
<i class="gg-close-o" id="close" onclick="$files.visible = false"></i>
|
<i class="gg-close-o" id="close" onclick="$files.remove()"></i>
|
||||||
<br>
|
<br>
|
||||||
<div class="tab-frame">
|
<div class="tab-frame">
|
||||||
${data.tabs.map( (t) =>
|
${data.tabs.map( (t) =>
|
||||||
|
|
@ -25,11 +29,12 @@ filesComponent = (el) => new Proxy({
|
||||||
`
|
`
|
||||||
).join('')
|
).join('')
|
||||||
}
|
}
|
||||||
<br><br>
|
<br><br><br>
|
||||||
<div class="tab">
|
<div class="tab">
|
||||||
<div id="localFilesTab">
|
<div id="localFilesTab">
|
||||||
<button id="uploadFile" ><i class="gg-software-upload"></i> upload</button>
|
<button id="localOpen" onclick="$files.fileLoaders()" ><i class="gg-software-upload"></i> open experience</button>
|
||||||
<button id="downloadfile"><i class="gg-software-download"></i> download</button>
|
<br>
|
||||||
|
<button id="localSave" onclick="frontend.download()"><i class="gg-software-download"></i> save current</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -40,7 +45,7 @@ filesComponent = (el) => new Proxy({
|
||||||
`,
|
`,
|
||||||
|
|
||||||
tabs: [
|
tabs: [
|
||||||
{name: "local files", id: "localFiles"}
|
{name: "offline", id: "localFiles"}
|
||||||
],
|
],
|
||||||
|
|
||||||
show: false,
|
show: false,
|
||||||
|
|
@ -50,6 +55,10 @@ filesComponent = (el) => new Proxy({
|
||||||
this.show = state !== undefined ? state : !this.show
|
this.show = state !== undefined ? state : !this.show
|
||||||
},
|
},
|
||||||
|
|
||||||
|
remove(){
|
||||||
|
el.parent.remove(el)
|
||||||
|
},
|
||||||
|
|
||||||
init(opts){
|
init(opts){
|
||||||
this.decorateFileButton()
|
this.decorateFileButton()
|
||||||
// create HTML element
|
// create HTML element
|
||||||
|
|
@ -72,7 +81,7 @@ filesComponent = (el) => new Proxy({
|
||||||
document.querySelector("#load").setAttribute("value","3D file")
|
document.querySelector("#load").setAttribute("value","3D file")
|
||||||
// decorate fileLoaders
|
// decorate fileLoaders
|
||||||
window.frontend.fileLoaders = ( (fileLoaders) => {
|
window.frontend.fileLoaders = ( (fileLoaders) => {
|
||||||
this.fileLoader = fileLoaders
|
this.fileLoaders = fileLoaders
|
||||||
return () => this.toggle()
|
return () => this.toggle()
|
||||||
})( window.frontend.fileLoaders )
|
})( window.frontend.fileLoaders )
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -889,6 +889,38 @@ document.head.innerHTML += `
|
||||||
bottom: 3px;
|
bottom: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.gg-globe-alt,
|
||||||
|
.gg-globe-alt::after,
|
||||||
|
.gg-globe-alt::before {
|
||||||
|
display: inline-block;
|
||||||
|
box-sizing: border-box;
|
||||||
|
height: 18px;
|
||||||
|
border: 2px solid;
|
||||||
|
}
|
||||||
|
.gg-globe-alt {
|
||||||
|
position: relative;
|
||||||
|
transform: scale(var(--ggs, 1));
|
||||||
|
width: 18px;
|
||||||
|
border-radius: 22px;
|
||||||
|
}
|
||||||
|
.gg-globe-alt::after,
|
||||||
|
.gg-globe-alt::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
width: 8px;
|
||||||
|
border-radius: 100%;
|
||||||
|
top: -2px;
|
||||||
|
left: 3px;
|
||||||
|
}
|
||||||
|
.gg-globe-alt::after {
|
||||||
|
width: 24px;
|
||||||
|
height: 20px;
|
||||||
|
border: 2px solid transparent;
|
||||||
|
border-bottom: 2px solid;
|
||||||
|
top: -11px;
|
||||||
|
left: -5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
`
|
`
|
||||||
|
|
|
||||||
|
|
@ -252,23 +252,26 @@ window.frontend = (opts) => new Proxy({
|
||||||
|
|
||||||
download(){
|
download(){
|
||||||
|
|
||||||
function download(dataurl, filename) {
|
|
||||||
var a = document.createElement("a");
|
|
||||||
a.href = URL.createObjectURL( new Blob([dataurl]) );
|
|
||||||
a.setAttribute("download", filename);
|
|
||||||
a.click();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function exportScene(model,ext,file){
|
function exportScene(model,ext,file){
|
||||||
|
|
||||||
document.dispatchEvent( new CustomEvent('frontend.export',{detail:{ scene: model.scene,ext}}) )
|
document.dispatchEvent( new CustomEvent('frontend.export',{detail:{ scene: model.scene,ext}}) )
|
||||||
xrf.emit('export', {scene: model.scene, ext})
|
xrf.emit('export', {scene: model.scene, ext})
|
||||||
.then( () => {
|
.then( () => {
|
||||||
|
|
||||||
|
function download(dataurl, filename) {
|
||||||
|
var a = document.createElement("a");
|
||||||
|
a.href = URL.createObjectURL( new Blob([dataurl]) );
|
||||||
|
a.setAttribute("download", filename);
|
||||||
|
a.click();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// setup exporters
|
// setup exporters
|
||||||
let defaultExporter = THREE.GLTFExporter
|
let defaultExporter = THREE.GLTFExporter
|
||||||
if( !xrf.loaders['gltf'].exporter ) xrf.loaders['gltf'].exporter = defaultExporter
|
if( !xrf.loaders['gltf'].exporter ) xrf.loaders['gltf'].exporter = defaultExporter
|
||||||
if( !xrf.loaders['glb'].exporter ) xrf.loaders['glb'].exporter = defaultExporter
|
if( !xrf.loaders['glb'].exporter ) xrf.loaders['glb'].exporter = defaultExporter
|
||||||
const exporter = new xrf.loaders[ext]()
|
const exporter = new xrf.loaders[ext].exporter()
|
||||||
|
debugger
|
||||||
exporter.parse(
|
exporter.parse(
|
||||||
model.scene,
|
model.scene,
|
||||||
function ( glb ) { download(glb, `${file}`) }, // ready
|
function ( glb ) { download(glb, `${file}`) }, // ready
|
||||||
|
|
@ -287,13 +290,12 @@ window.frontend = (opts) => new Proxy({
|
||||||
// load original scene and overwrite with updates
|
// load original scene and overwrite with updates
|
||||||
let url = document.location.search.replace(/\?/,'')
|
let url = document.location.search.replace(/\?/,'')
|
||||||
let {urlObj,dir,file,hash,fileExt} = xrf.navigator.origin = xrf.URI.parse(url)
|
let {urlObj,dir,file,hash,fileExt} = xrf.navigator.origin = xrf.URI.parse(url)
|
||||||
debugger
|
|
||||||
const Loader = xrf.loaders[fileExt]
|
const Loader = xrf.loaders[fileExt]
|
||||||
loader = new Loader().setPath( dir )
|
loader = new Loader().setPath( dir )
|
||||||
notify('exporting scene<br><br>please wait..')
|
notify('exporting scene<br><br>please wait..')
|
||||||
loader.load(url, (model) => {
|
loader.load(url, (model) => {
|
||||||
exportScene(model,fileExt,file)
|
exportScene(model,fileExt,file)
|
||||||
})
|
}, console.error )
|
||||||
},
|
},
|
||||||
|
|
||||||
updateHashPosition(randomize){
|
updateHashPosition(randomize){
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
const WebXRF = { name: 'webxr', builder: function(privateClient, publicClient) {
|
||||||
|
return {
|
||||||
|
exports: {
|
||||||
|
addScene: function() {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}};
|
||||||
|
|
@ -3,33 +3,39 @@
|
||||||
// reactive component for displaying the menu
|
// reactive component for displaying the menu
|
||||||
remoteStorageComponent = (el) => new Proxy({
|
remoteStorageComponent = (el) => new Proxy({
|
||||||
|
|
||||||
html: `
|
html: (data) => (`
|
||||||
<style type="text/css">
|
<style type="text/css">
|
||||||
body div#remotestorage-widget .rs-button{
|
body #files .rs-button-big{
|
||||||
background: #AAA;
|
background: #FFF;
|
||||||
|
box-shadow: none;
|
||||||
|
border: 1px solid #CCC;
|
||||||
|
padding: 10px 0px 49px 10px;
|
||||||
|
}
|
||||||
|
#files .rs-button{
|
||||||
|
background:#CCC;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<div id="remoteFilesTab">
|
<div id="remoteFilesTab">
|
||||||
<div id="rswidget"></div>
|
<div id="rswidget"></div>
|
||||||
<br><br>
|
<br>
|
||||||
<button id="uploadFile" ><i class="gg-software-upload"></i> upload</button>
|
<div id="buttons" style="display:none">
|
||||||
<button id="downloadfile"><i class="gg-software-download"></i> download</button>
|
<button id="remoteOpen" ><i class="gg-software-upload"></i> open experience</button>
|
||||||
|
<br>
|
||||||
|
<button id="remoteSave"><i class="gg-software-download"></i> save private experience</button>
|
||||||
|
<br>
|
||||||
|
<button id="remoteSave"><i class="gg-globe-alt"></i> save public experience</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`,
|
`),
|
||||||
|
|
||||||
show: false,
|
connected: false,
|
||||||
|
|
||||||
toggle(state){
|
|
||||||
this.show = state !== undefined ? state : !this.show
|
|
||||||
},
|
|
||||||
|
|
||||||
init(opts){
|
init(opts){
|
||||||
// create HTML element
|
// create HTML element
|
||||||
$files.tabs = $files.tabs.concat({id:"remoteFiles", name: "remote files"})
|
$files.tabs = $files.tabs.concat({id:"remoteFiles", name: "online"})
|
||||||
el.innerHTML = this.html
|
el.innerHTML = this.html(this)
|
||||||
el.className = "tab"
|
el.className = "tab"
|
||||||
this.toggle(this.show) // trigger visibility
|
|
||||||
document.querySelector("#files .tab-frame").appendChild(el);
|
document.querySelector("#files .tab-frame").appendChild(el);
|
||||||
|
|
||||||
// setup input listeners
|
// setup input listeners
|
||||||
|
|
@ -61,32 +67,36 @@ remoteStorageComponent = (el) => new Proxy({
|
||||||
let apis = {
|
let apis = {
|
||||||
dropbox: "4jc8nx1lbarp472"
|
dropbox: "4jc8nx1lbarp472"
|
||||||
}
|
}
|
||||||
window.remoteStorage = new RemoteStorage({logging: true })
|
const modules = []
|
||||||
|
if( typeof WebXRF != undefined ){
|
||||||
|
modules.push(WebXRF) // defined in remotestorage-module-webXRF.js
|
||||||
|
}
|
||||||
|
window.remoteStorage = new RemoteStorage({logging: true, modules })
|
||||||
if( Object.keys(apis).length ) remoteStorage.setApiKeys(apis)
|
if( Object.keys(apis).length ) remoteStorage.setApiKeys(apis)
|
||||||
|
|
||||||
remoteStorage.on('connected', (e) => { console.log("connected") } )
|
remoteStorage.on('connected', (e) => { this.connected = true })
|
||||||
//remoteStorage.on('network-offline', (e) => this.el.sceneEl.emit('remoteStorage.network-offline',e) )
|
//remoteStorage.on('network-offline', (e) => this.el.sceneEl.emit('remoteStorage.network-offline',e) )
|
||||||
//remoteStorage.on('network-online', (e) => this.el.sceneEl.emit('remoteStorage.network-online',e) )
|
//remoteStorage.on('network-online', (e) => this.el.sceneEl.emit('remoteStorage.network-online',e) )
|
||||||
//remoteStorage.on('error', (e) => this.el.sceneEl.emit('remoteStorage.error',e) )
|
//remoteStorage.on('error', (e) => this.el.sceneEl.emit('remoteStorage.error',e) )
|
||||||
//remoteStorage.on('ready', (e) => { } )
|
remoteStorage.on('ready', (e) => { } )
|
||||||
|
|
||||||
remoteStorage.access.claim( `webxr`, 'rw'); // our data dir
|
remoteStorage.access.claim( `webxr`, 'rw'); // our data dir
|
||||||
remoteStorage.caching.enable( `/webxr/` ) // local-first, remotestorage-second
|
remoteStorage.caching.enable( `/webxr/` ) // local-first, remotestorage-second
|
||||||
|
remoteStorage.caching.enable( `/public/webxr/` ) // local-first, remotestorage-second
|
||||||
|
|
||||||
// create widget
|
// create widget
|
||||||
let opts = {}
|
let opts = {}
|
||||||
opts.modalBackdrop = false
|
opts.modalBackdrop = false
|
||||||
|
opts.leaveOpen = true
|
||||||
widget = new window.Widget(window.remoteStorage, opts)
|
widget = new window.Widget(window.remoteStorage, opts)
|
||||||
widget.attach( "rswidget" );
|
widget.attach( "rswidget" );
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
click(id,e){
|
click(id,e){
|
||||||
switch(id){
|
//switch(id){
|
||||||
case "icon":
|
// case "more": return this.toggle(); break;
|
||||||
case "more": return this.toggle(); break;
|
//}
|
||||||
}
|
|
||||||
this.toggle(false)
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -95,6 +105,9 @@ remoteStorageComponent = (el) => new Proxy({
|
||||||
|
|
||||||
set(me,k,v){
|
set(me,k,v){
|
||||||
me[k] = v
|
me[k] = v
|
||||||
|
switch( k ){
|
||||||
|
case 'connected': el.querySelector("#buttons").style.display = v ? 'block' : 'none'; break;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -63,7 +63,6 @@ xrf.navigator.to = (url,flags,loader,data) => {
|
||||||
evalFragment()
|
evalFragment()
|
||||||
return resolve(xrf.model) // eval non-positional fragments (no loader needed)
|
return resolve(xrf.model) // eval non-positional fragments (no loader needed)
|
||||||
}
|
}
|
||||||
|
|
||||||
xrf
|
xrf
|
||||||
.emit('navigateLoading', {url,loader,data})
|
.emit('navigateLoading', {url,loader,data})
|
||||||
.then( () => {
|
.then( () => {
|
||||||
|
|
@ -109,7 +108,7 @@ xrf.navigator.init = () => {
|
||||||
xrf.navigator.URI = xrfragment.URI.parse(document.location.href)
|
xrf.navigator.URI = xrfragment.URI.parse(document.location.href)
|
||||||
|
|
||||||
window.addEventListener('popstate', function (event){
|
window.addEventListener('popstate', function (event){
|
||||||
if( xrf.navigator.updateHash.active ){ // ignore programmatic hash updates (causes infinite recursion)
|
if( xrf.navigator.updateHash.active && document.location.hash.length > 1 ){ // ignore programmatic hash updates (causes infinite recursion)
|
||||||
xrf.navigator.to( xrf.navigator.URI.last )
|
xrf.navigator.to( xrf.navigator.URI.last )
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
@ -119,7 +118,9 @@ xrf.navigator.init = () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
// allow other libraries to trigger popstate event without triggering the navigate-fallbacks during pageload
|
// allow other libraries to trigger popstate event without triggering the navigate-fallbacks during pageload
|
||||||
setTimeout( xrf.navigator.setupNavigateFallbacks(), 1500 )
|
setTimeout( () => {
|
||||||
|
xrf.navigator.setupNavigateFallbacks()
|
||||||
|
}, 2500 )
|
||||||
|
|
||||||
// this allows selectionlines to be updated according to the camera (renderloop)
|
// this allows selectionlines to be updated according to the camera (renderloop)
|
||||||
xrf.focusLine = new xrf.THREE.Group()
|
xrf.focusLine = new xrf.THREE.Group()
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue