matrix/trystero plugin updates + added simple example

This commit is contained in:
Leon van Kammen 2024-01-29 20:17:57 +01:00
parent b07e38db6e
commit ead6a93171
26 changed files with 135 additions and 37890 deletions

Binary file not shown.

View file

@ -1,262 +0,0 @@
{
"accessors" : [
{
"bufferView" : 0,
"byteOffset" : 0,
"componentType" : 5126,
"count" : 3,
"max" : [
2.000000
],
"min" : [
0.000000
],
"type" : "SCALAR"
},
{
"bufferView" : 1,
"byteOffset" : 0,
"componentType" : 5126,
"count" : 3,
"max" : [
0.000000,
1.000000,
0.000000,
1.000000
],
"min" : [
0.000000,
-8.742278e-008,
0.000000,
-1.000000
],
"type" : "VEC4"
},
{
"bufferView" : 2,
"byteOffset" : 0,
"componentType" : 5123,
"count" : 36,
"max" : [
35
],
"min" : [
0
],
"type" : "SCALAR"
},
{
"bufferView" : 3,
"byteOffset" : 0,
"componentType" : 5126,
"count" : 36,
"max" : [
1.000000,
1.000000,
1.000001
],
"min" : [
-1.000000,
-1.000000,
-1.000000
],
"type" : "VEC3"
},
{
"bufferView" : 4,
"byteOffset" : 0,
"componentType" : 5126,
"count" : 36,
"max" : [
1.000000,
1.000000,
1.000000
],
"min" : [
-1.000000,
-1.000000,
-1.000000
],
"type" : "VEC3"
},
{
"bufferView" : 5,
"byteOffset" : 0,
"componentType" : 5126,
"count" : 36,
"max" : [
1.000000,
-0.000000,
-0.000000,
1.000000
],
"min" : [
0.000000,
-0.000000,
-1.000000,
-1.000000
],
"type" : "VEC4"
},
{
"bufferView" : 6,
"byteOffset" : 0,
"componentType" : 5126,
"count" : 36,
"max" : [
1.000000,
1.000000
],
"min" : [
-1.000000,
-1.000000
],
"type" : "VEC2"
}
],
"animations" : [
{
"channels" : [
{
"sampler" : 0,
"target" : {
"node" : 0,
"path" : "rotation"
}
}
],
"name" : "animation_AnimatedCube",
"samplers" : [
{
"input" : 0,
"interpolation" : "LINEAR",
"output" : 1
}
]
}
],
"asset" : {
"generator" : "VKTS glTF 2.0 exporter",
"version" : "2.0"
},
"bufferViews" : [
{
"buffer" : 0,
"byteLength" : 12,
"byteOffset" : 0
},
{
"buffer" : 0,
"byteLength" : 48,
"byteOffset" : 12
},
{
"buffer" : 0,
"byteLength" : 72,
"byteOffset" : 60,
"target" : 34963
},
{
"buffer" : 0,
"byteLength" : 432,
"byteOffset" : 132,
"target" : 34962
},
{
"buffer" : 0,
"byteLength" : 432,
"byteOffset" : 564,
"target" : 34962
},
{
"buffer" : 0,
"byteLength" : 576,
"byteOffset" : 996,
"target" : 34962
},
{
"buffer" : 0,
"byteLength" : 288,
"byteOffset" : 1572,
"target" : 34962
}
],
"buffers" : [
{
"byteLength" : 1860,
"uri" : "AnimatedCube.bin"
}
],
"images" : [
{
"uri" : "AnimatedCube_BaseColor.png"
},
{
"uri" : "AnimatedCube_MetallicRoughness.png"
}
],
"materials" : [
{
"name" : "AnimatedCube",
"pbrMetallicRoughness" : {
"baseColorTexture" : {
"index" : 0
},
"metallicRoughnessTexture" : {
"index" : 1
}
}
}
],
"meshes" : [
{
"name" : "AnimatedCube",
"primitives" : [
{
"attributes" : {
"NORMAL" : 4,
"POSITION" : 3,
"TANGENT" : 5,
"TEXCOORD_0" : 6
},
"indices" : 2,
"material" : 0,
"mode" : 4
}
]
}
],
"nodes" : [
{
"mesh" : 0,
"name" : "AnimatedCube",
"rotation" : [
0.000000,
-1.000000,
0.000000,
0.000000
]
}
],
"samplers" : [
{}
],
"scene" : 0,
"scenes" : [
{
"nodes" : [
0
]
}
],
"textures" : [
{
"sampler" : 0,
"source" : 0
},
{
"sampler" : 0,
"source" : 1
}
]
}

View file

@ -1,160 +0,0 @@
{
"asset":{
"generator":"Khronos glTF Blender I/O v3.5.30",
"version":"2.0"
},
"scene":0,
"scenes":[
{
"name":"Scene",
"nodes":[
0
]
}
],
"nodes":[
{
"mesh":0,
"name":"AnimatedCube"
}
],
"animations":[
{
"channels":[
{
"sampler":0,
"target":{
"node":0,
"path":"rotation"
}
}
],
"name":"walk",
"samplers":[
{
"input":4,
"interpolation":"LINEAR",
"output":5
}
]
}
],
"materials":[
{
"name":"AnimatedCube",
"pbrMetallicRoughness":{}
}
],
"meshes":[
{
"name":"AnimatedCube",
"primitives":[
{
"attributes":{
"POSITION":0,
"TEXCOORD_0":1,
"NORMAL":2
},
"indices":3,
"material":0
}
]
}
],
"accessors":[
{
"bufferView":0,
"componentType":5126,
"count":36,
"max":[
1,
1,
1.0000009536743164
],
"min":[
-1,
-1,
-1
],
"type":"VEC3"
},
{
"bufferView":1,
"componentType":5126,
"count":36,
"type":"VEC2"
},
{
"bufferView":2,
"componentType":5126,
"count":36,
"type":"VEC3"
},
{
"bufferView":3,
"componentType":5123,
"count":36,
"type":"SCALAR"
},
{
"bufferView":4,
"componentType":5126,
"count":3,
"max":[
2
],
"min":[
0
],
"type":"SCALAR"
},
{
"bufferView":5,
"componentType":5126,
"count":3,
"type":"VEC4"
}
],
"bufferViews":[
{
"buffer":0,
"byteLength":432,
"byteOffset":0,
"target":34962
},
{
"buffer":0,
"byteLength":288,
"byteOffset":432,
"target":34962
},
{
"buffer":0,
"byteLength":432,
"byteOffset":720,
"target":34962
},
{
"buffer":0,
"byteLength":72,
"byteOffset":1152,
"target":34963
},
{
"buffer":0,
"byteLength":12,
"byteOffset":1224
},
{
"buffer":0,
"byteLength":48,
"byteOffset":1236
}
],
"buffers":[
{
"byteLength":1284,
"uri":"data:application/octet-stream;base64,AACAPwAAgL8AAIA/AACAvwAAgL8AAIC/AACAPwAAgL8AAIC/AACAvwAAgD8AAIC/7/9/PwAAgD8IAIA/AACAPwAAgD/v/3+/AACAPwAAgD/v/3+/AACAPwAAgL8AAIA/AACAPwAAgL8AAIC/7/9/PwAAgD8IAIA/AACAvwAAgL8AAIA/AACAPwAAgL8AAIA/AACAvwAAgL8AAIA/AACAvwAAgD8AAIC/AACAvwAAgL8AAIC/AACAPwAAgL8AAIC/AACAvwAAgD8AAIC/AACAPwAAgD/v/3+/AACAPwAAgL8AAIA/AACAvwAAgL8AAIA/AACAvwAAgL8AAIC/AACAvwAAgD8AAIC/AACAvwAAgD8AAIA/7/9/PwAAgD8IAIA/AACAPwAAgD/v/3+/7/9/PwAAgD8IAIA/AACAPwAAgL8AAIA/7/9/PwAAgD8IAIA/AACAvwAAgD8AAIA/AACAvwAAgL8AAIA/AACAvwAAgL8AAIA/AACAvwAAgD8AAIA/AACAvwAAgD8AAIC/AACAPwAAgL8AAIC/AACAvwAAgL8AAIC/AACAvwAAgD8AAIC/AAAAAAAAAAAAAIC/AACAPwAAAAAAAIA/AAAAAAAAAAAAAIA/AACAvwAAgD8AAAAAAACAPwAAAAAAAAAAAACAvwAAgD8AAIC/AACAPwAAAAAAAACAAACAvwAAgD8AAIC/AAAAAAAAAAAAAIA/AACAPwAAgD8AAAAAAAAAAAAAAAAAAIC/AACAPwAAAAAAAIA/AAAAAAAAAAAAAIC/AAAAAAAAgL8AAIA/AAAAAAAAAAAAAACAAACAvwAAgD8AAIC/AACAPwAAAAAAAACAAAAAAAAAAAAAAIC/AACAPwAAAAAAAACAAAAAAAAAAIAAAIC/AAAAAAAAAAAAAAAAAACAPwAAgD8AAIA/AAAAAAAAAAAAAIC/AAAAAAAAgL8AAIA/AAAAAAAAgL8AAACAAAAAAAAAgL8AAACAAAAAAAAAgL8AAACAAAAAAAAAgD8AAACAAAAAAAAAgD8AAACAAAAAAAAAgD8AAACAAACAPwAAAAAAAACAAACAPwAAAAAAAACAAACAPwAAAAAAAACAAAAAAAAAAIAAAIA/AAAAAAAAAIAAAIA/AAAAAAAAAIAAAIA/AACAvwAAAAAAAACAAACAvwAAAAAAAACAAACAvwAAAAAAAACAAAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAgL8AAACAAAAAAAAAgL8AAACAAAAAAAAAgL8AAACAAAAAAAAAgD8AAACAAAAAAAAAgD8AAACAAAAAAAAAgD8AAACAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAAAAgAAAAAAAAIA/AAAAgAAAAAAAAIA/AAAAgAAAAAAAAIA/AACAvwAAAAAAAACAAACAvwAAAAAAAACAAACAvwAAAAAAAACAAAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAABAAIAAwAEAAUABgAHAAgACQAKAAsADAANAA4ADwAQABEAEgATABQAFQAWABcAGAAZABoAGwAcAB0AHgAfACAAIQAiACMAAAAAAAAAgD8AAABAAAAAAAAAAAAAAACAAACAPwAAAIAAAIC/AAAAAC69OzMAAAAALr27MwAAAIAAAIA/"
}
]
}

Binary file not shown.

Binary file not shown.

BIN
example/assets/example.glb Normal file

Binary file not shown.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1,11 +1,12 @@
chatComponent = { chatComponent = {
html: ` html: `
<div id="chat">
<div id="videos" style="pointer-events:none"></div> <div id="videos" style="pointer-events:none"></div>
<div id="messages" aria-live="assertive" aria-relevant></div> <div id="messages" aria-live="assertive" aria-relevant></div>
<div id="chatfooter"> <div id="chatfooter">
<div id="chatbar"> <div id="chatbar">
<input id="chatline" type="text" placeholder="type here"></input> <input id="chatline" type="text" placeholder="chat here"></input>
</div> </div>
<button id="showchat" class="btn">show chat</button> <button id="showchat" class="btn">show chat</button>
</div> </div>
@ -14,10 +15,10 @@ chatComponent = {
init: (el) => new Proxy({ init: (el) => new Proxy({
scene: null, scene: null,
visible: true, visible: true,
visibleChatbar: false, messages: [],
messages: [], oneMessagePerUser: false,
username: '', // configured by 'network.connected' event username: '', // configured by 'network.connected' event
@ -29,10 +30,10 @@ chatComponent = {
install(opts){ install(opts){
this.opts = opts this.opts = opts
this.scene = opts.scene this.scene = opts.scene
this.$chatbar.style.display = 'none'
el.className = "xrf" el.className = "xrf"
el.style.display = 'none' // start hidden el.style.display = 'none' // start hidden
document.body.appendChild( el ) document.body.appendChild( el )
this.visibleChatbar = false
document.dispatchEvent( new CustomEvent("$chat:ready", {detail: opts}) ) document.dispatchEvent( new CustomEvent("$chat:ready", {detail: opts}) )
this.send({message:`Welcome to <b>${document.location.search.substr(1)}</b>, a 3D scene(file) which simply links to other ones.<br>You can start a solo offline exploration in XR right away.<br>Type /help below, or use the arrow- or WASD-keys on your keyboard, and mouse-drag to rotate.<br>`, class: ["info","guide","multiline"] }) this.send({message:`Welcome to <b>${document.location.search.substr(1)}</b>, a 3D scene(file) which simply links to other ones.<br>You can start a solo offline exploration in XR right away.<br>Type /help below, or use the arrow- or WASD-keys on your keyboard, and mouse-drag to rotate.<br>`, class: ["info","guide","multiline"] })
}, },
@ -41,6 +42,7 @@ chatComponent = {
let {$chatline} = this let {$chatline} = this
$chatline.addEventListener('click', (e) => this.inform() ) $chatline.addEventListener('click', (e) => this.inform() )
$chatline.addEventListener('keydown', (e) => { $chatline.addEventListener('keydown', (e) => {
if (e.key == 'Enter' ){ if (e.key == 'Enter' ){
if( $chatline.value[0] != '/' ){ if( $chatline.value[0] != '/' ){
@ -52,16 +54,20 @@ chatComponent = {
} }
}) })
document.addEventListener('network.connect', (e) => {
this.visible = true
this.$chatbar.style.display = '' // show
})
document.addEventListener('network.connected', (e) => { document.addEventListener('network.connected', (e) => {
if( e.detail.username ) this.username = e.detail.username if( e.detail.username ) this.username = e.detail.username
}) })
console.dir(this.scene)
}, },
inform(){ inform(){
if( !this.inform.informed && (this.inform.informed = true) ){ if( !this.inform.informed && (this.inform.informed = true) ){
window.notify("Here you can update your statusline.<br>Protocols like [Matrix] allow you to view the full transcript<br>in a dedicated client like <a href='https://element.io' target='_blank'>element.io</a>") window.notify("Connected via P2P. You can now type message which will be visible to others.")
} }
}, },
@ -83,6 +89,7 @@ chatComponent = {
let {$messages} = this let {$messages} = this
opts = { linebreak:true, message:"", class:[], ...opts } opts = { linebreak:true, message:"", class:[], ...opts }
if( window.frontend && window.frontend.emit ) window.frontend.emit('$chat.send', opts ) if( window.frontend && window.frontend.emit ) window.frontend.emit('$chat.send', opts )
opts.pos = opts.pos || network.posName || network.pos
let div = document.createElement('div') let div = document.createElement('div')
let msg = document.createElement('div') let msg = document.createElement('div')
let br = document.createElement('br') let br = document.createElement('br')
@ -98,9 +105,9 @@ chatComponent = {
br.classList.add.apply(br.classList, opts.class) br.classList.add.apply(br.classList, opts.class)
div.classList.add.apply(div.classList, opts.class.concat(["envelope"])) div.classList.add.apply(div.classList, opts.class.concat(["envelope"]))
} }
if( !opts.from && !msg.className.match(/(info|guide)/) ){ if( !msg.className.match(/(info|guide|ui)/) ){
let frag = xrf.URI.parse(document.location.hash) let frag = xrf.URI.parse(document.location.hash)
opts.from = this.username opts.from = 'you'
if( frag.pos ) opts.pos = frag.pos.string if( frag.pos ) opts.pos = frag.pos.string
msg.classList.add('self') msg.classList.add('self')
} }
@ -116,7 +123,7 @@ chatComponent = {
} }
div.appendChild(msg) div.appendChild(msg)
// force one message per user // force one message per user
if( opts.from ){ if( this.oneMessagePerUser && opts.from ){
div.id = this.hyphenate(opts.from) div.id = this.hyphenate(opts.from)
let oldMsg = $messages.querySelector(`#${div.id}`) let oldMsg = $messages.querySelector(`#${div.id}`)
if( oldMsg ) oldMsg.remove() if( oldMsg ) oldMsg.remove()
@ -148,9 +155,6 @@ chatComponent = {
if( !el.inited && (el.inited = true) ) me.initListeners() if( !el.inited && (el.inited = true) ) me.initListeners()
break; break;
} }
case "visibleChatbar": {
me.$chatbar.style.display = v ? 'block' : 'none'
}
} }
} }
@ -234,25 +238,25 @@ chatComponent.css = `
max-width:unset; max-width:unset;
} }
#messages{ #messages{
/*
display: flex; display: flex;
flex-direction: column-reverse; flex-direction: column;
width: 91%;
max-width: 500px;
*/
width:100%;
align-items: flex-start; align-items: flex-start;
position: absolute; position: absolute;
transition:1s; transition:1s;
top: 0px; top: 77px;
left: 0; left: 0;
bottom: 49px; bottom: 49px;
padding: 20px; padding: 20px;
overflow:hidden; overflow:hidden;
overflow-y: scroll;
pointer-events:none; pointer-events:none;
transition:1s; transition:1s;
width: 91%;
max-width: 500px;
z-index: 100; z-index: 100;
-webkit-user-select:none;
-moz-user-select:-moz-none;
-ms-user-select:none;
user-select:none;
} }
body.menu #messages{ body.menu #messages{
top:50px; top:50px;
@ -261,7 +265,11 @@ chatComponent.css = `
pointer-events:all; pointer-events:all;
} }
#messages *{ #messages *{
pointer-events:all; pointer-events:none;
-webkit-user-select:none;
-moz-user-select:-moz-none;
-ms-user-select:none;
user-select:none;
} }
#messages .msg{ #messages .msg{
transition:all 1s ease; transition:all 1s ease;
@ -272,11 +280,18 @@ chatComponent.css = `
color: #000c; color: #000c;
margin-bottom: 10px; margin-bottom: 10px;
line-height:23px; line-height:23px;
pointer-events:visible;
line-height:33px; line-height:33px;
cursor:grabbing; cursor:grabbing;
border: 1px solid #0002; border: 1px solid #0002;
} }
#messages .msg *{
pointer-events:all;
-webkit-user-select:text;
-moz-user-select:-moz-text;
-ms-user-select:text;
user-select:text;
}
#messages .msg.self{ #messages .msg.self{
border-radius: 20px; border-radius: 20px;
background:var(--xrf-box-shadow); background:var(--xrf-box-shadow);
@ -303,7 +318,7 @@ chatComponent.css = `
transition:0.3s; transition:0.3s;
} }
#messages .msg.info a, #messages .msg.info a,
#messages .ruler a{ #messages a.ruler{
color:#FFF; color:#FFF;
} }
#messages .msg a:hover{ #messages .msg a:hover{
@ -375,10 +390,19 @@ chatComponent.css = `
margin:0; margin:0;
} }
.envelope{ .envelope,
height:unset; .envelope * {
overflow:hidden; overflow:hidden;
transition:1s; transition:1s;
pointer-events:none;
}
.envelope a,
.envelope button,
.envelope input,
.envelope textarea,
.envelope msg,
.envelope msg * {
pointer-events:all;
} }
.user{ .user{

View file

@ -2,7 +2,7 @@ connectionsComponent = {
html: ` html: `
<div id="connections"> <div id="connections">
<i class="gg-close-o" id="close" onclick="$connections.toggle()"></i> <i class="gg-close-o" id="close" onclick="$connections.visible = false"></i>
<br> <br>
<div class="tab-frame"> <div class="tab-frame">
<input type="radio" name="tab" id="login" checked> <input type="radio" name="tab" id="login" checked>
@ -83,6 +83,8 @@ connectionsComponent = {
init: (el) => new Proxy({ init: (el) => new Proxy({
visible: true,
webcam: [{profile:{name:"No thanks"},config: () => document.createElement('div')}], webcam: [{profile:{name:"No thanks"},config: () => document.createElement('div')}],
chatnetwork: [{profile:{name:"No thanks"},config: () => document.createElement('div')}], chatnetwork: [{profile:{name:"No thanks"},config: () => document.createElement('div')}],
scene: [{profile:{name:"No thanks"},config: () => document.createElement('div')}], scene: [{profile:{name:"No thanks"},config: () => document.createElement('div')}],
@ -117,8 +119,7 @@ connectionsComponent = {
}, },
toggle(){ toggle(){
let parent = el.closest('.envelope') $chat.visible = !$chat.visible
parent.style.display = parent.style.display == 'none' ? parent.style.display = '' : 'none'
}, },
change(id,e){ change(id,e){
@ -130,13 +131,14 @@ connectionsComponent = {
show(opts){ show(opts){
opts = opts || {} opts = opts || {}
if( opts.hide ){ if( opts.hide ){
el.parentElement.parentElement.style.display = 'none' if( el.parentElement ) el.parentElement.parentElement.style.display = 'none' // hide along with wrapper elements
if( !opts.showChat ) $chat.visible = false
}else{ }else{
$chat.visible = true $chat.visible = true
this.visible = true
// hide networking settings if entering thru meetinglink // hide networking settings if entering thru meetinglink
$networking.style.display = document.location.href.match(/meet=/) ? 'none' : 'block' $networking.style.display = document.location.href.match(/meet=/) ? 'none' : 'block'
if( !network.connected ){ if( !network.connected ){
if( el.parentElement ) el.parentElement.parentElement.remove()
document.querySelector('body > .xrf').appendChild(el) document.querySelector('body > .xrf').appendChild(el)
$chat.send({message:"", el, class:['ui']}) $chat.send({message:"", el, class:['ui']})
if( !network.meetinglink ){ // set default if( !network.meetinglink ){ // set default
@ -233,7 +235,7 @@ connectionsComponent = {
reactToNetwork(){ // *TODO* move to network? reactToNetwork(){ // *TODO* move to network?
document.addEventListener('network.connect', () => { document.addEventListener('network.connect', () => {
this.show({hide:true}) this.show({hide:true, showChat: true})
}) })
document.addEventListener('network.disconnect', () => { document.addEventListener('network.disconnect', () => {
this.connected = false this.connected = false
@ -247,6 +249,7 @@ connectionsComponent = {
set(data,k,v){ set(data,k,v){
data[k] = v data[k] = v
switch( k ){ switch( k ){
case "visible": el.style.display = v ? '' : 'none'; break;
case "webcam": $webcam.innerHTML = `<option>${data[k].map((p)=>p.profile.name).join('</option><option>')}</option>`; break; case "webcam": $webcam.innerHTML = `<option>${data[k].map((p)=>p.profile.name).join('</option><option>')}</option>`; break;
case "chatnetwork": $chatnetwork.innerHTML = `<option>${data[k].map((p)=>p.profile.name).join('</option><option>')}</option>`; break; case "chatnetwork": $chatnetwork.innerHTML = `<option>${data[k].map((p)=>p.profile.name).join('</option><option>')}</option>`; break;
case "scene": $scene.innerHTML = `<option>${data[k].map((p)=>p.profile.name).join('</option><option>')}</option>`; break; case "scene": $scene.innerHTML = `<option>${data[k].map((p)=>p.profile.name).join('</option><option>')}</option>`; break;

View file

@ -16,8 +16,8 @@ menuComponent = (el) => new Proxy({
$buttons: $buttons = el.querySelector('#buttons'), $buttons: $buttons = el.querySelector('#buttons'),
$btnMore: $btnMore = el.querySelector('#more'), $btnMore: $btnMore = el.querySelector('#more'),
toggle(){ toggle(state){
this.collapsed = !this.collapsed this.collapsed = state !== undefined ? state : !this.collapsed
el.querySelector("i#icon").className = this.collapsed ? 'gg-close' : 'gg-menu' el.querySelector("i#icon").className = this.collapsed ? 'gg-close' : 'gg-menu'
document.body.classList[ this.collapsed ? 'add' : 'remove' ](['menu']) document.body.classList[ this.collapsed ? 'add' : 'remove' ](['menu'])
}, },

View file

@ -43,6 +43,7 @@ window.frontend = (opts) => new Proxy({
.setupUserHints() .setupUserHints()
.setupNetworkListeners() .setupNetworkListeners()
.hidetopbarWhenMenuCollapse() .hidetopbarWhenMenuCollapse()
.hideUIWhenNavigating()
window.notify = this.notify window.notify = this.notify
setTimeout( () => { setTimeout( () => {
@ -130,6 +131,29 @@ window.frontend = (opts) => new Proxy({
return this return this
}, },
hideUIWhenNavigating(){
// hide ui when user is navigating the scene using mouse/touch
let showUI = (show) => (e) => {
let isChatMsg = e.target.closest('.msg')
let isChatLine = e.target.id == 'chatline'
let isChatEmptySpace = e.target.id == 'messages'
let isUI = e.target.closest('.ui')
//console.dir({class: e.target.className, id: e.target.id, isChatMsg,isChatLine,isChatEmptySpace,isUI, tagName: e.target.tagName})
if( isUI || e.target.tagName.match(/^(BUTTON|TEXTAREA|INPUT|A)/) || e.target.className.match(/(btn)/) ) return
if( show ){
$chat.visible = true
}else{
$chat.visible = false
$menu.toggle(false)
}
return true
}
document.addEventListener('mousedown', showUI(false) )
document.addEventListener('mouseup', showUI(true) )
document.addEventListener('touchstart', showUI(false) )
document.addEventListener('touchend', showUI(true) )
},
loadFile(contentLoaders, multiple){ loadFile(contentLoaders, multiple){
return () => { return () => {
window.notify("if you're on Meta browser, file-uploads might be disabled") window.notify("if you're on Meta browser, file-uploads might be disabled")
@ -218,10 +242,11 @@ window.frontend = (opts) => new Proxy({
share(opts){ share(opts){
opts = opts || {notify:true,qr:true,share:true,linkonly:false} opts = opts || {notify:true,qr:true,share:true,linkonly:false}
if( network.connected && !document.location.hash.match(/meet=/) ){ if( network.meetingLink && !document.location.hash.match(/meet=/) ){
let p = $connections.chatnetwork.find( (p) => p.profile.name == $connections.selectedChatnetwork ) document.location.hash += `&meet=${network.meetingLink}`
console.dir(p) }
if( p.link ) document.location.hash = `${ network.pos ? `pos=${network.pos}` : ''}&meet=${p.link}` if( !document.location.hash.match(/pos=/) ){
document.location.hash += `&pos=${ network.posName || network.pos }`
} }
let url = window.location.href let url = window.location.href
if( opts.linkonly ) return url if( opts.linkonly ) return url

View file

@ -60,8 +60,7 @@ window.network = (opts) => new Proxy({
for ( var i in String.prototype ) add(i) for ( var i in String.prototype ) add(i)
var a = names[Math.floor(Math.random() * names.length)]; var a = names[Math.floor(Math.random() * names.length)];
var b = names[Math.floor(Math.random() * names.length)]; var b = names[Math.floor(Math.random() * names.length)];
var c = names[Math.floor(Math.random() * names.length)]; return String(`${a}-${b}-${String(Math.random()).substr(13)}`).toLowerCase()
return String(`${a}-${b}-${c}`).toLowerCase()
} }
}, },

View file

@ -49,7 +49,7 @@ window.matrix = (opts) => new Proxy({
<tr> <tr>
<td>user</td> <td>user</td>
<td> <td>
<input type="text" id="username" placeholder="@you:matrix.org"/> <input type="text" id="username" placeholder="@you:matrix.org" value="${opts.plugin.username}"/>
</td> </td>
</tr> </tr>
<tr> <tr>
@ -76,7 +76,8 @@ window.matrix = (opts) => new Proxy({
init(){ init(){
frontend.plugin['matrix'] = this frontend.plugin['matrix'] = this
$connections.chatnetwork = $connections.chatnetwork.concat([this]) $connections.chatnetwork = $connections.chatnetwork.concat([this])
$connections.scene = $connections.chatnetwork.concat([this]) $connections.scene = $connections.scene.concat([this])
if( window.localStorage.getItem("username") ) this.username = window.localStorage.getItem("username")
this.reactToConnectionHrefs() this.reactToConnectionHrefs()
document.addEventListener('network.connect', (e) => this.connect(e.detail) ) document.addEventListener('network.connect', (e) => this.connect(e.detail) )
@ -93,8 +94,8 @@ window.matrix = (opts) => new Proxy({
if( opts.selectedChatnetwork == this.profile.name ) this.useChat = true if( opts.selectedChatnetwork == this.profile.name ) this.useChat = true
if( opts.selectedScene == this.profile.name ) this.useScene = true if( opts.selectedScene == this.profile.name ) this.useScene = true
if( this.useWebcam || this.useScene || this.useChat ){ if( this.useWebcam || this.useScene || this.useChat ){
this.createLink() // ensure link
this.link = `matrix://r/${this.channel.replace(/^#/,'')}` this.link = `matrix://r/${this.channel.replace(/^#/,'')}`
this.createLink() // ensure link
this.channel = document.querySelector("#matrix input#channel").value this.channel = document.querySelector("#matrix input#channel").value
this.server = document.querySelector("#matrix input#server").value this.server = document.querySelector("#matrix input#server").value
this.username = document.querySelector("#matrix input#username").value this.username = document.querySelector("#matrix input#username").value
@ -156,7 +157,10 @@ window.matrix = (opts) => new Proxy({
.then( (o) => { .then( (o) => {
console.log(`${this.channel} has id ${o.room_id}`) console.log(`${this.channel} has id ${o.room_id}`)
this.roomId = o.room_id this.roomId = o.room_id
this.setupListeners() // join room if we haven't already
this.client.joinRoom(this.roomId)
.then( () => this.setupListeners() )
.catch( () => this.setupListeners() )
}) })
.catch((e) => { .catch((e) => {
console.error(e) console.error(e)
@ -242,7 +246,7 @@ window.matrix = (opts) => new Proxy({
formatted_body: message, formatted_body: message,
msgtype:"m.text" msgtype:"m.text"
} }
this.client.sendEvent( this.roomId, "m.room.message", content, "", (err,res) => console.error(err) ) this.client.sendEvent( this.roomId, "m.room.message", content, "", (err,res) => console.error({err,res}) )
}) })
}, },
@ -292,7 +296,7 @@ window.matrix = (opts) => new Proxy({
let hash = document.location.hash let hash = document.location.hash
if( !this.link ){ if( !this.link ){
const meeting = network.getMeetingFromUrl(document.location.href) const meeting = network.getMeetingFromUrl(document.location.href)
this.link = meeting.match("matrix://") ? meeting : '' this.link = network.meetingLink = meeting.match("matrix://") ? meeting : ''
} }
if( !hash.match('meet=') ) document.location.hash += `${hash.length > 1 ? '&' : '#'}meet=${this.link}` if( !hash.match('meet=') ) document.location.hash += `${hash.length > 1 ? '&' : '#'}meet=${this.link}`
}, },

View file

@ -88,8 +88,8 @@ window.trystero = (opts) => new Proxy({
console.log("trystero link: "+this.link) console.log("trystero link: "+this.link)
this.room = joinRoom( {appId: 'xrfragment'}, this.link ) this.room = joinRoom( {appId: 'xrfragment'}, this.link )
$chat.send({message:`Share the meeting link <a onclick="frontend.share()">by clicking here</a>`,class:['info'],timeout:10000}) $chat.send({message:`Share the meeting link <a onclick="frontend.share()">by clicking here</a>`,class:['info']})
$chat.send({message:"waiting for other humans..",class:['info'], timeout:5000}) $chat.send({message:"waiting for other humans..",class:['info']})
// setup trystero events // setup trystero events
const [sendPing, getPing] = this.room.makeAction('ping') const [sendPing, getPing] = this.room.makeAction('ping')
@ -132,6 +132,7 @@ window.trystero = (opts) => new Proxy({
this.chat.get = getChat this.chat.get = getChat
document.addEventListener('network.send', (e) => { document.addEventListener('network.send', (e) => {
console.log("trystero")
this.chat.send({...e.detail, from: this.nickname, pos: network.pos }) // send to P2P network this.chat.send({...e.detail, from: this.nickname, pos: network.pos }) // send to P2P network
}) })
// prime chatlog of other people joining // prime chatlog of other people joining
@ -159,7 +160,7 @@ window.trystero = (opts) => new Proxy({
video: $connections.$videoInput.value video: $connections.$videoInput.value
}) })
this.room.addStream(this.selfStream) this.room.addStream(this.selfStream)
this.videos[ this.selfId ] = this.getVideo(this.selfId,{stream: this.selfStream}) this.getVideo(this.selfId,{create:true,stream: this.selfStream})
// send stream + chatlog to peers who join later // send stream + chatlog to peers who join later
this.room.onPeerJoin( (peerId) => this.room.addStream( this.selfStream, peerId)) this.room.onPeerJoin( (peerId) => this.room.addStream( this.selfStream, peerId))
@ -198,7 +199,8 @@ window.trystero = (opts) => new Proxy({
// add video element to the DOM // add video element to the DOM
if( opts.stream ) video.srcObject = opts.stream if( opts.stream ) video.srcObject = opts.stream
console.log("creating video for peerId") console.log("creating video for peerId "+this.selfId)
this.videos[ this.selfId ] = video
$chat.$videos.appendChild(video) $chat.$videos.appendChild(video)
} }
}, },
@ -209,7 +211,7 @@ window.trystero = (opts) => new Proxy({
let hash = document.location.hash let hash = document.location.hash
if( !this.link ){ if( !this.link ){
const meeting = network.getMeetingFromUrl(document.location.href) const meeting = network.getMeetingFromUrl(document.location.href)
this.link = meeting.match("trystero://") ? meeting : `trystero://r/${network.randomRoom()}:bittorrent` this.link = network.meetingLink = meeting.match("trystero://") ? meeting : `trystero://r/${network.randomRoom()}:bittorrent`
} }
if( !hash.match('meet=') ) document.location.hash += `${hash.length > 1 ? '&' : '#'}meet=${this.link}` if( !hash.match('meet=') ) document.location.hash += `${hash.length > 1 ? '&' : '#'}meet=${this.link}`
}, },

View file

@ -53,15 +53,6 @@ xrf.getFile = (url) => url.split("/").pop().replace(/#.*/,'')
xrf.parseModel = function(model,url){ xrf.parseModel = function(model,url){
let file = xrf.getFile(url) let file = xrf.getFile(url)
model.file = file model.file = file
model.animations.map( (a) => console.log("anim: "+a.name) )
// spec: 2. init metadata inside model for non-SRC data
if( !model.isSRC ){
model.scene.traverse( (mesh) => xrf.hashbus.pub.mesh(mesh,model) )
}
// spec: 1. execute the default predefined view '#' (if exist) (https://xrfragment.org/#predefined_view)
xrf.frag.defaultPredefinedViews({model,scene:model.scene})
// spec: predefined view(s) & objects-of-interest-in-XRWG from URL (https://xrfragment.org/#predefined_view)
let frag = xrf.hashbus.pub( url, model) // and eval URI XR fragments
xrf.emit('parseModel',{model,url,file}) xrf.emit('parseModel',{model,url,file})
} }
@ -93,6 +84,8 @@ xrf.reset = () => {
// remove mixers // remove mixers
xrf.mixers.map( (m) => m.stop()) xrf.mixers.map( (m) => m.stop())
xrf.mixers = [] xrf.mixers = []
// set the player to position 0,0,0
xrf.camera.position.set(0,0,0)
} }
xrf.parseUrl = (url) => { xrf.parseUrl = (url) => {

View file

@ -38,29 +38,42 @@ xrf.navigator.to = (url,flags,loader,data) => {
return resolve(xrf.model) return resolve(xrf.model)
} }
// clear xrf objects from scene
if( xrf.model && xrf.model.scene ) xrf.model.scene.visible = false if( xrf.model && xrf.model.scene ) xrf.model.scene.visible = false
xrf.reset()
// force relative path for files which dont include protocol or relative path // force relative path for files which dont include protocol or relative path
if( dir ) dir = dir[0] == '.' || dir.match("://") ? dir : `.${dir}` if( dir ) dir = dir[0] == '.' || dir.match("://") ? dir : `.${dir}`
url = url.replace(dir,"") url = url.replace(dir,"")
loader = loader || new Loader().setPath( dir ) loader = loader || new Loader().setPath( dir )
const onLoad = (model) => { const onLoad = (model) => {
xrf.reset() // clear xrf objects from scene
model.file = file model.file = file
// only change url when loading *another* file // only change url when loading *another* file
if( xrf.model ) xrf.navigator.pushState( `${dir}${file}`, hash ) if( xrf.model ) xrf.navigator.pushState( `${dir}${file}`, hash )
xrf.model = model xrf.model = model
if(xrf.debug ) model.animations.map( (a) => console.log("anim: "+a.name) )
// spec: 2. init metadata inside model for non-SRC data
if( !model.isSRC ){
model.scene.traverse( (mesh) => xrf.hashbus.pub.mesh(mesh,model) )
}
// spec: 1. generate the XRWG // spec: 1. generate the XRWG
xrf.XRWG.generate({model,scene:model.scene}) xrf.XRWG.generate({model,scene:model.scene})
// spec: 1. execute the default predefined view '#' (if exist) (https://xrfragment.org/#predefined_view)
xrf.frag.defaultPredefinedViews({model,scene:model.scene})
// spec: predefined view(s) & objects-of-interest-in-XRWG from URL (https://xrfragment.org/#predefined_view)
let frag = xrf.hashbus.pub( url, model) // and eval URI XR fragments
xrf.add( model.scene ) xrf.add( model.scene )
if( hash ) xrf.navigator.updateHash(hash) if( hash ) xrf.navigator.updateHash(hash)
xrf.emit('navigateLoaded',{url,model}) xrf.emit('navigateLoaded',{url,model})
resolve(model) resolve(model)
} }
if( data ) loader.parse(data, "", onLoad ) if( data ){ // file upload
else loader.load(url, onLoad ) loader.parse(data, "", onLoad )
}else loader.load(url, onLoad )
}) })
}) })
}) })

View file

@ -4,9 +4,11 @@ xrf.frag.defaultPredefinedViews = (opts) => {
if( n.userData && n.userData['#'] ){ if( n.userData && n.userData['#'] ){
let frag = xrf.URI.parse( n.userData['#'] ) let frag = xrf.URI.parse( n.userData['#'] )
if( n.parent && n.parent.parent.isScene && document.location.hash.length < 2 ){ if( n.parent && n.parent.parent.isScene && document.location.hash.length < 2 ){
xrf.navigator.to( n.userData['#'] ) // evaluate main scene XR fragments and update URL xrf.navigator.to( n.userData['#'] ) // evaluate static XR fragments
console.log("to")
}else{ }else{
xrf.hashbus.pub( n.userData['#'] ) // evaluate static XR fragments xrf.hashbus.pub( n.userData['#'] ) // evaluate static XR fragments
console.log("pub")
} }
} }
}) })

View file

@ -47,12 +47,12 @@ xrf.frag.href = function(v, opts){
let toFrag = xrf.URI.parse( v.string, xrf.XRF.NAVIGATOR | xrf.XRF.PV_OVERRIDE | xrf.XRF.METADATA ) let toFrag = xrf.URI.parse( v.string, xrf.XRF.NAVIGATOR | xrf.XRF.PV_OVERRIDE | xrf.XRF.METADATA )
// always commit current location in case of teleport (keep a trail of last positions before we navigate) // always commit current location in case of teleport (keep a trail of last positions before we navigate)
if( isLocal && !hasPos ){ //if( isLocal && !hasPos ){
xrf.hashbus.pub( v.string, xrf.model ) // publish to hashbus // xrf.hashbus.pub( v.string, xrf.model ) // publish to hashbus
}else{ //}else{
//if( !e.nocommit && !document.location.hash.match(lastPos) ) xrf.navigator.updateHash(`#${lastPos}`) //if( !e.nocommit && !document.location.hash.match(lastPos) ) xrf.navigator.updateHash(`#${lastPos}`)
xrf.navigator.to(v.string) // let's surf xrf.navigator.to(v.string) // let's surf
} //}
}) })
.catch( console.error ) .catch( console.error )
} }

View file

@ -9,7 +9,7 @@ xrf.addEventListener('env', (opts) => {
//scene.texture = env.material.map //scene.texture = env.material.map
// renderer.toneMapping = THREE.ACESFilmicToneMapping; // renderer.toneMapping = THREE.ACESFilmicToneMapping;
// renderer.toneMappingExposure = 2; // renderer.toneMappingExposure = 2;
console.log(` └ applied image '${frag.env.string}' as environment map`) // console.log(` └ applied image '${frag.env.string}' as environment map`)
} }
}) })