2023-05-09 17:42:29 +02:00
var $hx _exports = typeof exports != "undefined" ? exports : typeof window != "undefined" ? window : typeof self != "undefined" ? self : this ;
( function ( $global ) { "use strict" ;
$hx _exports [ "xrfragment" ] = $hx _exports [ "xrfragment" ] || { } ;
var EReg = function ( r , opt ) {
this . r = new RegExp ( r , opt . split ( "u" ) . join ( "" ) ) ;
} ;
EReg . _ _name _ _ = true ;
EReg . prototype = {
match : function ( s ) {
if ( this . r . global ) {
this . r . lastIndex = 0 ;
}
this . r . m = this . r . exec ( s ) ;
this . r . s = s ;
return this . r . m != null ;
}
, split : function ( s ) {
var d = "#__delim__#" ;
return s . replace ( this . r , d ) . split ( d ) ;
}
} ;
var HxOverrides = function ( ) { } ;
HxOverrides . _ _name _ _ = true ;
HxOverrides . cca = function ( s , index ) {
var x = s . charCodeAt ( index ) ;
if ( x != x ) {
return undefined ;
}
return x ;
} ;
HxOverrides . substr = function ( s , pos , len ) {
if ( len == null ) {
len = s . length ;
} else if ( len < 0 ) {
if ( pos == 0 ) {
len = s . length + len ;
} else {
return "" ;
}
}
return s . substr ( pos , len ) ;
} ;
HxOverrides . now = function ( ) {
return Date . now ( ) ;
} ;
Math . _ _name _ _ = true ;
var Reflect = function ( ) { } ;
Reflect . _ _name _ _ = true ;
Reflect . field = function ( o , field ) {
try {
return o [ field ] ;
} catch ( _g ) {
return null ;
}
} ;
Reflect . fields = function ( o ) {
var a = [ ] ;
if ( o != null ) {
var hasOwnProperty = Object . prototype . hasOwnProperty ;
for ( var f in o ) {
if ( f != "__id__" && f != "hx__closures__" && hasOwnProperty . call ( o , f ) ) {
a . push ( f ) ;
}
}
}
return a ;
} ;
Reflect . deleteField = function ( o , field ) {
if ( ! Object . prototype . hasOwnProperty . call ( o , field ) ) {
return false ;
}
delete ( o [ field ] ) ;
return true ;
} ;
var Std = function ( ) { } ;
Std . _ _name _ _ = true ;
Std . string = function ( s ) {
return js _Boot . _ _string _rec ( s , "" ) ;
} ;
Std . parseInt = function ( x ) {
if ( x != null ) {
var _g = 0 ;
var _g1 = x . length ;
while ( _g < _g1 ) {
var i = _g ++ ;
var c = x . charCodeAt ( i ) ;
if ( c <= 8 || c >= 14 && c != 32 && c != 45 ) {
var nc = x . charCodeAt ( i + 1 ) ;
var v = parseInt ( x , nc == 120 || nc == 88 ? 16 : 10 ) ;
if ( isNaN ( v ) ) {
return null ;
} else {
return v ;
}
}
}
}
return null ;
} ;
var StringTools = function ( ) { } ;
StringTools . _ _name _ _ = true ;
StringTools . isSpace = function ( s , pos ) {
var c = HxOverrides . cca ( s , pos ) ;
if ( ! ( c > 8 && c < 14 ) ) {
return c == 32 ;
} else {
return true ;
}
} ;
StringTools . ltrim = function ( s ) {
var l = s . length ;
var r = 0 ;
while ( r < l && StringTools . isSpace ( s , r ) ) ++ r ;
if ( r > 0 ) {
return HxOverrides . substr ( s , r , l - r ) ;
} else {
return s ;
}
} ;
StringTools . rtrim = function ( s ) {
var l = s . length ;
var r = 0 ;
while ( r < l && StringTools . isSpace ( s , l - r - 1 ) ) ++ r ;
if ( r > 0 ) {
return HxOverrides . substr ( s , 0 , l - r ) ;
} else {
return s ;
}
} ;
StringTools . trim = function ( s ) {
return StringTools . ltrim ( StringTools . rtrim ( s ) ) ;
} ;
StringTools . replace = function ( s , sub , by ) {
return s . split ( sub ) . join ( by ) ;
} ;
var haxe _iterators _ArrayIterator = function ( array ) {
this . current = 0 ;
this . array = array ;
} ;
haxe _iterators _ArrayIterator . _ _name _ _ = true ;
haxe _iterators _ArrayIterator . prototype = {
hasNext : function ( ) {
return this . current < this . array . length ;
}
, next : function ( ) {
return this . array [ this . current ++ ] ;
}
} ;
var js _Boot = function ( ) { } ;
js _Boot . _ _name _ _ = true ;
js _Boot . _ _string _rec = function ( o , s ) {
if ( o == null ) {
return "null" ;
}
if ( s . length >= 5 ) {
return "<...>" ;
}
var t = typeof ( o ) ;
if ( t == "function" && ( o . _ _name _ _ || o . _ _ename _ _ ) ) {
t = "object" ;
}
switch ( t ) {
case "function" :
return "<function>" ;
case "object" :
if ( ( ( o ) instanceof Array ) ) {
var str = "[" ;
s += "\t" ;
var _g = 0 ;
var _g1 = o . length ;
while ( _g < _g1 ) {
var i = _g ++ ;
str += ( i > 0 ? "," : "" ) + js _Boot . _ _string _rec ( o [ i ] , s ) ;
}
str += "]" ;
return str ;
}
var tostr ;
try {
tostr = o . toString ;
} catch ( _g ) {
return "???" ;
}
if ( tostr != null && tostr != Object . toString && typeof ( tostr ) == "function" ) {
var s2 = o . toString ( ) ;
if ( s2 != "[object Object]" ) {
return s2 ;
}
}
var str = "{\n" ;
s += "\t" ;
var hasp = o . hasOwnProperty != null ;
var k = null ;
for ( k in o ) {
if ( hasp && ! o . hasOwnProperty ( k ) ) {
continue ;
}
if ( k == "prototype" || k == "__class__" || k == "__super__" || k == "__interfaces__" || k == "__properties__" ) {
continue ;
}
if ( str . length != 2 ) {
str += ", \n" ;
}
str += s + k + " : " + js _Boot . _ _string _rec ( o [ k ] , s ) ;
}
s = s . substring ( 1 ) ;
str += "\n" + s + "}" ;
return str ;
case "string" :
return o ;
default :
return String ( o ) ;
}
} ;
var xrfragment _Parser = $hx _exports [ "xrfragment" ] [ "Parser" ] = function ( ) { } ;
xrfragment _Parser . _ _name _ _ = true ;
2023-06-27 09:43:10 +02:00
xrfragment _Parser . parse = function ( key , value , store ) {
2023-05-09 17:42:29 +02:00
var Frag _h = Object . create ( null ) ;
2023-08-15 18:27:26 +02:00
Frag _h [ "#" ] = xrfragment _XRF . ASSET | xrfragment _XRF . T _PREDEFINED _VIEW | xrfragment _XRF . PV _EXECUTE ;
2023-05-09 17:42:29 +02:00
Frag _h [ "prio" ] = xrfragment _XRF . ASSET | xrfragment _XRF . T _INT ;
Frag _h [ "src" ] = xrfragment _XRF . ASSET | xrfragment _XRF . T _URL ;
Frag _h [ "href" ] = xrfragment _XRF . ASSET | xrfragment _XRF . T _URL | xrfragment _XRF . T _PREDEFINED _VIEW ;
2023-08-15 18:27:26 +02:00
Frag _h [ "class" ] = xrfragment _XRF . ASSET | xrfragment _XRF . T _STRING ;
Frag _h [ "pos" ] = xrfragment _XRF . PV _OVERRIDE | xrfragment _XRF . ROUNDROBIN | xrfragment _XRF . T _VECTOR3 | xrfragment _XRF . T _STRING _OBJ | xrfragment _XRF . METADATA | xrfragment _XRF . NAVIGATOR ;
Frag _h [ "q" ] = xrfragment _XRF . PV _OVERRIDE | xrfragment _XRF . T _STRING | xrfragment _XRF . METADATA ;
Frag _h [ "scale" ] = xrfragment _XRF . QUERY _OPERATOR | xrfragment _XRF . PV _OVERRIDE | xrfragment _XRF . ROUNDROBIN | xrfragment _XRF . T _VECTOR3 | xrfragment _XRF . METADATA ;
Frag _h [ "rot" ] = xrfragment _XRF . QUERY _OPERATOR | xrfragment _XRF . PV _OVERRIDE | xrfragment _XRF . ROUNDROBIN | xrfragment _XRF . T _VECTOR3 | xrfragment _XRF . METADATA | xrfragment _XRF . NAVIGATOR ;
Frag _h [ "mov" ] = xrfragment _XRF . QUERY _OPERATOR | xrfragment _XRF . PV _OVERRIDE | xrfragment _XRF . ROUNDROBIN | xrfragment _XRF . T _VECTOR3 | xrfragment _XRF . METADATA ;
Frag _h [ "show" ] = xrfragment _XRF . QUERY _OPERATOR | xrfragment _XRF . PV _OVERRIDE | xrfragment _XRF . ROUNDROBIN | xrfragment _XRF . T _INT | xrfragment _XRF . METADATA ;
Frag _h [ "env" ] = xrfragment _XRF . ASSET | xrfragment _XRF . PV _OVERRIDE | xrfragment _XRF . T _STRING | xrfragment _XRF . METADATA ;
Frag _h [ "t" ] = xrfragment _XRF . ASSET | xrfragment _XRF . PV _OVERRIDE | xrfragment _XRF . ROUNDROBIN | xrfragment _XRF . T _VECTOR2 | xrfragment _XRF . NAVIGATOR | xrfragment _XRF . METADATA ;
Frag _h [ "gravity" ] = xrfragment _XRF . ASSET | xrfragment _XRF . PV _OVERRIDE | xrfragment _XRF . T _VECTOR3 | xrfragment _XRF . METADATA ;
Frag _h [ "physics" ] = xrfragment _XRF . ASSET | xrfragment _XRF . PV _OVERRIDE | xrfragment _XRF . T _VECTOR3 | xrfragment _XRF . METADATA ;
Frag _h [ "fov" ] = xrfragment _XRF . ASSET | xrfragment _XRF . PV _OVERRIDE | xrfragment _XRF . T _INT | xrfragment _XRF . NAVIGATOR | xrfragment _XRF . METADATA ;
Frag _h [ "clip" ] = xrfragment _XRF . ASSET | xrfragment _XRF . PV _OVERRIDE | xrfragment _XRF . T _VECTOR2 | xrfragment _XRF . NAVIGATOR | xrfragment _XRF . METADATA ;
2023-08-24 13:36:41 +02:00
Frag _h [ "fog" ] = xrfragment _XRF . ASSET | xrfragment _XRF . PV _OVERRIDE | xrfragment _XRF . T _VECTOR2 | xrfragment _XRF . NAVIGATOR | xrfragment _XRF . METADATA ;
Frag _h [ "bg" ] = xrfragment _XRF . ASSET | xrfragment _XRF . PV _OVERRIDE | xrfragment _XRF . T _VECTOR3 | xrfragment _XRF . NAVIGATOR | xrfragment _XRF . METADATA ;
2023-05-09 17:42:29 +02:00
Frag _h [ "namespace" ] = xrfragment _XRF . ASSET | xrfragment _XRF . T _STRING ;
Frag _h [ "SPDX" ] = xrfragment _XRF . ASSET | xrfragment _XRF . T _STRING ;
Frag _h [ "unit" ] = xrfragment _XRF . ASSET | xrfragment _XRF . T _STRING ;
Frag _h [ "description" ] = xrfragment _XRF . ASSET | xrfragment _XRF . T _STRING ;
2023-08-15 18:27:26 +02:00
Frag _h [ "session" ] = xrfragment _XRF . ASSET | xrfragment _XRF . T _URL | xrfragment _XRF . PV _OVERRIDE | xrfragment _XRF . NAVIGATOR | xrfragment _XRF . METADATA | xrfragment _XRF . PROMPT ;
var isPVDynamic = value . length == 0 && key . length > 0 && ! Object . prototype . hasOwnProperty . call ( Frag _h , key ) ;
var isPVDefault = value . length == 0 && key . length > 0 && key == "#" ;
if ( isPVDynamic ) {
2023-06-09 16:40:08 +02:00
var v = new xrfragment _XRF ( key , xrfragment _XRF . PV _EXECUTE | xrfragment _XRF . NAVIGATOR ) ;
v . validate ( key ) ;
2023-06-27 09:43:10 +02:00
store [ key ] = v ;
2023-05-09 17:42:29 +02:00
return true ;
}
if ( key . split ( "." ) . length > 1 && value . split ( "." ) . length > 1 ) {
2023-06-27 09:43:10 +02:00
store [ key ] = new xrfragment _XRF ( key , xrfragment _XRF . ASSET | xrfragment _XRF . PV _OVERRIDE | xrfragment _XRF . T _STRING | xrfragment _XRF . PROP _BIND ) ;
2023-05-09 17:42:29 +02:00
return true ;
}
2023-06-27 09:43:10 +02:00
var v = new xrfragment _XRF ( key , Frag _h [ key ] ) ;
2023-05-09 17:42:29 +02:00
if ( Object . prototype . hasOwnProperty . call ( Frag _h , key ) ) {
if ( ! v . validate ( value ) ) {
2023-08-24 13:36:41 +02:00
console . log ( "src/xrfragment/Parser.hx:83:" , "⚠ fragment '" + key + "' has incompatible value (" + value + ")" ) ;
2023-05-09 17:42:29 +02:00
return false ;
}
2023-06-27 09:43:10 +02:00
store [ key ] = v ;
2023-05-09 17:42:29 +02:00
if ( xrfragment _Parser . debug ) {
2023-08-24 13:36:41 +02:00
console . log ( "src/xrfragment/Parser.hx:87:" , "✔ " + key + ": " + v . string ) ;
2023-05-09 17:42:29 +02:00
}
2023-06-27 09:43:10 +02:00
} else {
2023-06-27 12:15:04 +02:00
if ( typeof ( value ) == "string" ) {
v . guessType ( v , value ) ;
}
2023-06-27 09:43:10 +02:00
store [ "_" + key ] = v ;
2023-05-09 17:42:29 +02:00
}
return true ;
} ;
var xrfragment _Query = $hx _exports [ "xrfragment" ] [ "Query" ] = function ( str ) {
this . isNumber = new EReg ( "^[0-9\\.]+$" , "" ) ;
this . isClass = new EReg ( "^[-]?class$" , "" ) ;
2023-06-22 08:48:52 +02:00
this . isRoot = new EReg ( "^[-]?/" , "" ) ;
2023-05-09 17:42:29 +02:00
this . isExclude = new EReg ( "^-" , "" ) ;
this . isProp = new EReg ( "^.*:[><=!]?" , "" ) ;
this . q = { } ;
this . str = "" ;
if ( str != null ) {
this . parse ( str ) ;
}
} ;
xrfragment _Query . _ _name _ _ = true ;
xrfragment _Query . prototype = {
toObject : function ( ) {
return this . q ;
}
, expandAliases : function ( token ) {
var classAlias = new EReg ( "^(-)?\\." , "" ) ;
if ( classAlias . match ( token ) ) {
return StringTools . replace ( token , "." , "class:" ) ;
} else {
return token ;
}
}
, get : function ( ) {
return this . q ;
}
2023-06-27 09:43:10 +02:00
, parse : function ( str ) {
2023-05-09 17:42:29 +02:00
var _gthis = this ;
var token = str . split ( " " ) ;
var q = { } ;
var process = function ( str , prefix ) {
if ( prefix == null ) {
prefix = "" ;
}
str = StringTools . trim ( str ) ;
var k = str . split ( ":" ) [ 0 ] ;
var v = str . split ( ":" ) [ 1 ] ;
var filter = { } ;
if ( q [ prefix + k ] ) {
filter = q [ prefix + k ] ;
}
filter [ "rules" ] = filter [ "rules" ] != null ? filter [ "rules" ] : [ ] ;
if ( _gthis . isProp . match ( str ) ) {
var oper = "" ;
if ( str . indexOf ( "*" ) != - 1 ) {
oper = "*" ;
}
if ( str . indexOf ( ">" ) != - 1 ) {
oper = ">" ;
}
if ( str . indexOf ( "<" ) != - 1 ) {
oper = "<" ;
}
if ( str . indexOf ( ">=" ) != - 1 ) {
oper = ">=" ;
}
if ( str . indexOf ( "<=" ) != - 1 ) {
oper = "<=" ;
}
if ( _gthis . isExclude . match ( k ) ) {
oper = "!=" ;
k = HxOverrides . substr ( k , 1 , null ) ;
} else {
v = HxOverrides . substr ( v , oper . length , null ) ;
}
if ( oper . length == 0 ) {
oper = "=" ;
}
if ( _gthis . isClass . match ( k ) ) {
filter [ prefix + k ] = oper != "!=" ;
q [ v ] = filter ;
} else {
var rule = { } ;
if ( _gthis . isNumber . match ( v ) ) {
rule [ oper ] = parseFloat ( v ) ;
} else {
rule [ oper ] = v ;
}
filter [ "rules" ] . push ( rule ) ;
q [ k ] = filter ;
}
return ;
} else {
filter [ "id" ] = _gthis . isExclude . match ( str ) ? false : true ;
2023-06-22 08:48:52 +02:00
filter [ "root" ] = _gthis . isRoot . match ( str ) ;
if ( _gthis . isExclude . match ( str ) ) {
str = HxOverrides . substr ( str , 1 , null ) ;
}
if ( _gthis . isRoot . match ( str ) ) {
str = HxOverrides . substr ( str , 1 , null ) ;
}
q [ str ] = filter ;
2023-05-09 17:42:29 +02:00
}
} ;
var _g = 0 ;
var _g1 = token . length ;
while ( _g < _g1 ) {
var i = _g ++ ;
process ( this . expandAliases ( token [ i ] ) ) ;
}
return this . q = q ;
}
, test : function ( obj ) {
var qualify = false ;
var _g = 0 ;
var _g1 = Reflect . fields ( obj ) ;
while ( _g < _g1 . length ) {
var k = _g1 [ _g ] ;
++ _g ;
var v = Std . string ( Reflect . field ( obj , k ) ) ;
if ( this . testProperty ( k , v ) ) {
qualify = true ;
}
}
var _g = 0 ;
var _g1 = Reflect . fields ( obj ) ;
while ( _g < _g1 . length ) {
var k = _g1 [ _g ] ;
++ _g ;
var v = Std . string ( Reflect . field ( obj , k ) ) ;
if ( this . testProperty ( k , v , true ) ) {
qualify = false ;
}
}
return qualify ;
}
, testProperty : function ( property , value , exclude ) {
var conds = 0 ;
var fails = 0 ;
var qualify = 0 ;
var testprop = function ( expr ) {
conds += 1 ;
fails += expr ? 0 : 1 ;
return expr ;
} ;
if ( this . q [ value ] != null ) {
var v = this . q [ value ] ;
if ( v [ property ] != null ) {
return v [ property ] ;
}
}
var _g = 0 ;
var _g1 = Reflect . fields ( this . q ) ;
while ( _g < _g1 . length ) {
var k = _g1 [ _g ] ;
++ _g ;
var filter = Reflect . field ( this . q , k ) ;
if ( filter . rules == null ) {
continue ;
}
var rules = filter . rules ;
var _g2 = 0 ;
while ( _g2 < rules . length ) {
var rule = rules [ _g2 ] ;
++ _g2 ;
if ( exclude ) {
if ( Reflect . field ( rule , "!=" ) != null && testprop ( ( value == null ? "null" : "" + value ) == Std . string ( Reflect . field ( rule , "!=" ) ) ) && exclude ) {
++ qualify ;
}
} else {
if ( Reflect . field ( rule , "*" ) != null && testprop ( parseFloat ( value ) != null ) ) {
++ qualify ;
}
if ( Reflect . field ( rule , ">" ) != null && testprop ( parseFloat ( value ) > parseFloat ( Reflect . field ( rule , ">" ) ) ) ) {
++ qualify ;
}
if ( Reflect . field ( rule , "<" ) != null && testprop ( parseFloat ( value ) < parseFloat ( Reflect . field ( rule , "<" ) ) ) ) {
++ qualify ;
}
if ( Reflect . field ( rule , ">=" ) != null && testprop ( parseFloat ( value ) >= parseFloat ( Reflect . field ( rule , ">=" ) ) ) ) {
++ qualify ;
}
if ( Reflect . field ( rule , "<=" ) != null && testprop ( parseFloat ( value ) <= parseFloat ( Reflect . field ( rule , "<=" ) ) ) ) {
++ qualify ;
}
if ( Reflect . field ( rule , "=" ) != null && ( testprop ( value == Reflect . field ( rule , "=" ) ) || testprop ( parseFloat ( value ) == parseFloat ( Reflect . field ( rule , "=" ) ) ) ) ) {
++ qualify ;
}
}
}
}
return qualify > 0 ;
}
} ;
var xrfragment _URI = $hx _exports [ "xrfragment" ] [ "URI" ] = function ( ) { } ;
xrfragment _URI . _ _name _ _ = true ;
xrfragment _URI . parse = function ( url , filter ) {
var store = { } ;
2023-06-10 14:43:07 +02:00
if ( url == null || url . indexOf ( "#" ) == - 1 ) {
2023-05-09 17:42:29 +02:00
return store ;
}
var fragment = url . split ( "#" ) ;
var splitArray = fragment [ 1 ] . split ( "&" ) ;
var _g = 0 ;
var _g1 = splitArray . length ;
while ( _g < _g1 ) {
var i = _g ++ ;
var splitByEqual = splitArray [ i ] . split ( "=" ) ;
var regexPlus = new EReg ( "\\+" , "g" ) ;
var key = splitByEqual [ 0 ] ;
var value = "" ;
if ( splitByEqual . length > 1 ) {
var s = regexPlus . split ( splitByEqual [ 1 ] ) . join ( " " ) ;
value = decodeURIComponent ( s . split ( "+" ) . join ( " " ) ) ;
}
var ok = xrfragment _Parser . parse ( key , value , store ) ;
}
if ( filter != null && filter != 0 ) {
var _g = 0 ;
var _g1 = Reflect . fields ( store ) ;
while ( _g < _g1 . length ) {
var key = _g1 [ _g ] ;
++ _g ;
var xrf = store [ key ] ;
if ( ! xrf . is ( filter ) ) {
Reflect . deleteField ( store , key ) ;
}
}
}
return store ;
} ;
var xrfragment _XRF = $hx _exports [ "xrfragment" ] [ "XRF" ] = function ( _fragment , _flags ) {
this . fragment = _fragment ;
this . flags = _flags ;
} ;
xrfragment _XRF . _ _name _ _ = true ;
xrfragment _XRF . set = function ( flag , flags ) {
return flags | flag ;
} ;
xrfragment _XRF . unset = function ( flag , flags ) {
return flags & ~ flag ;
} ;
xrfragment _XRF . prototype = {
is : function ( flag ) {
return ( this . flags & flag ) != 0 ;
}
, validate : function ( value ) {
this . guessType ( this , value ) ;
if ( value . split ( "|" ) . length > 1 ) {
this . args = [ ] ;
var args = value . split ( "|" ) ;
var _g = 0 ;
var _g1 = args . length ;
while ( _g < _g1 ) {
var i = _g ++ ;
var x = new xrfragment _XRF ( this . fragment , this . flags ) ;
this . guessType ( x , args [ i ] ) ;
this . args . push ( x ) ;
}
}
if ( this . fragment == "q" ) {
this . query = new xrfragment _Query ( value ) . get ( ) ;
}
var ok = true ;
if ( ! ( ( this . args ) instanceof Array ) ) {
if ( this . is ( xrfragment _XRF . T _VECTOR3 ) && ! ( typeof ( this . x ) == "number" && typeof ( this . y ) == "number" && typeof ( this . z ) == "number" ) ) {
ok = false ;
}
if ( this . is ( xrfragment _XRF . T _VECTOR2 ) && ! ( typeof ( this . x ) == "number" && typeof ( this . y ) == "number" ) ) {
ok = false ;
}
var tmp ;
if ( this . is ( xrfragment _XRF . T _INT ) ) {
var v = this . int ;
tmp = ! ( typeof ( v ) == "number" && ( ( v | 0 ) === v ) ) ;
} else {
tmp = false ;
}
if ( tmp ) {
ok = false ;
}
}
return ok ;
}
, guessType : function ( v , str ) {
v . string = str ;
if ( str . split ( "," ) . length > 1 ) {
var xyz = str . split ( "," ) ;
if ( xyz . length > 0 ) {
v . x = parseFloat ( xyz [ 0 ] ) ;
}
if ( xyz . length > 1 ) {
v . y = parseFloat ( xyz [ 1 ] ) ;
}
if ( xyz . length > 2 ) {
v . z = parseFloat ( xyz [ 2 ] ) ;
}
}
if ( xrfragment _XRF . isColor . match ( str ) ) {
v . color = str ;
}
if ( xrfragment _XRF . isFloat . match ( str ) ) {
v . float = parseFloat ( str ) ;
}
if ( xrfragment _XRF . isInt . match ( str ) ) {
v . int = Std . parseInt ( str ) ;
}
}
} ;
if ( typeof ( performance ) != "undefined" ? typeof ( performance . now ) == "function" : false ) {
HxOverrides . now = performance . now . bind ( performance ) ;
}
String . _ _name _ _ = true ;
Array . _ _name _ _ = true ;
js _Boot . _ _toStr = ( { } ) . toString ;
xrfragment _Parser . error = "" ;
xrfragment _Parser . debug = false ;
xrfragment _XRF . ASSET = 1 ;
xrfragment _XRF . PROP _BIND = 2 ;
xrfragment _XRF . QUERY _OPERATOR = 4 ;
xrfragment _XRF . PROMPT = 8 ;
xrfragment _XRF . ROUNDROBIN = 16 ;
xrfragment _XRF . NAVIGATOR = 32 ;
2023-08-15 18:27:26 +02:00
xrfragment _XRF . METADATA = 64 ;
2023-05-09 17:42:29 +02:00
xrfragment _XRF . PV _OVERRIDE = 128 ;
xrfragment _XRF . PV _EXECUTE = 256 ;
xrfragment _XRF . T _COLOR = 8192 ;
xrfragment _XRF . T _INT = 16384 ;
xrfragment _XRF . T _FLOAT = 32768 ;
xrfragment _XRF . T _VECTOR2 = 65536 ;
xrfragment _XRF . T _VECTOR3 = 131072 ;
xrfragment _XRF . T _URL = 262144 ;
xrfragment _XRF . T _PREDEFINED _VIEW = 524288 ;
xrfragment _XRF . T _STRING = 1048576 ;
xrfragment _XRF . T _STRING _OBJ = 2097152 ;
xrfragment _XRF . T _STRING _OBJ _PROP = 4194304 ;
xrfragment _XRF . isColor = new EReg ( "^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$" , "" ) ;
xrfragment _XRF . isInt = new EReg ( "^[0-9]+$" , "" ) ;
xrfragment _XRF . isFloat = new EReg ( "^[0-9]+\\.[0-9]+$" , "" ) ;
xrfragment _XRF . isVector = new EReg ( "([,]+|\\w)" , "" ) ;
xrfragment _XRF . isUrl = new EReg ( "(://)?\\..*" , "" ) ;
xrfragment _XRF . isUrlOrPretypedView = new EReg ( "(^#|://)?\\..*" , "" ) ;
xrfragment _XRF . isString = new EReg ( ".*" , "" ) ;
} ) ( { } ) ;
var xrfragment = $hx _exports [ "xrfragment" ] ;
2023-06-07 17:42:21 +02:00
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) 2023 Leon van Kammen/NLNET
var xrf = { }
xrf . init = function ( opts ) {
opts = opts || { }
xrf . Parser . debug = xrf . debug
for ( let i in opts ) xrf [ i ] = opts [ i ]
xrf . emit ( 'init' , opts )
return xrf . query
}
xrf . query = function ( ) {
// framework implementations can override this function, see src/3rd/js/three/index.sj
alert ( "queries are not implemented (yet) for this particular framework" )
}
2023-06-09 16:40:08 +02:00
xrf . roundrobin = ( frag , store ) => {
if ( ! frag . args || frag . args . length == 0 ) return 0
if ( ! store . rr ) store . rr = { }
let label = frag . fragment
if ( store . rr [ label ] ) return store . rr [ label ] . next ( )
store . rr [ label ] = frag . args
store . rr [ label ] . next = ( ) => {
store . rr [ label ] . index = ( store . rr [ label ] . index + 1 ) % store . rr [ label ] . length
return store . rr [ label ] . index
}
return store . rr [ label ] . index = 0
}
2023-06-07 17:42:21 +02:00
// map library functions to xrf
for ( let i in xrfragment ) xrf [ i ] = xrfragment [ i ]
/ *
* ( promise - able ) EVENTS
*
* example :
*
* xrf . addEventListener ( 'foo' , ( e ) => {
* // let promise = e.promise()
* console . log ( "navigating to: " + e . detail . destination . url )
* // promise.resolve()
* // promise.reject("not going to happen")
* } )
*
* xrf . emit ( 'foo' , 123 )
* xrf . emit ( 'foo' , 123 ) . then ( ... ) . catch ( ... ) . finally ( ... )
* /
xrf . addEventListener = function ( eventName , callback ) {
if ( ! this . _listeners ) this . _listeners = [ ]
if ( ! this . _listeners [ eventName ] ) {
// create a new array for this event name if it doesn't exist yet
this . _listeners [ eventName ] = [ ] ;
}
// add the callback to the listeners array for this event name
this . _listeners [ eventName ] . push ( callback ) ;
} ;
xrf . emit = function ( eventName , data ) {
2023-06-22 08:48:52 +02:00
if ( typeof data != 'object' ) throw 'emit() requires passing objects'
2023-06-07 17:42:21 +02:00
return xrf . emit . promise ( eventName , data )
}
xrf . emit . normal = function ( eventName , data ) {
if ( ! xrf . _listeners ) xrf . _listeners = [ ]
var callbacks = xrf . _listeners [ eventName ]
if ( callbacks ) {
for ( var i = 0 ; i < callbacks . length ; i ++ ) {
callbacks [ i ] ( data ) ;
}
}
} ;
xrf . emit . promise = function ( e , opts ) {
opts . XRF = xrf // always pass root XRF obj
return new Promise ( ( resolve , reject ) => {
opts . promise = ( ) => {
opts . promise . halted = true
return { resolve , reject }
}
xrf . emit . normal ( e , opts )
if ( ! opts . promise . halted ) resolve ( )
} )
}
2023-07-06 15:27:27 +02:00
/ * ! r a s t e r i z e H T M L . j s - v 1 . 3 . 1 - 2 0 2 3 - 0 7 - 0 6
* http : //www.github.com/cburgmer/rasterizeHTML.js
* Copyright ( c ) 2023 Christoph Burgmer ; Licensed MIT * /
2023-08-04 09:11:26 +02:00
! function ( o , i ) { void 0 === o && void 0 !== window && ( o = window ) , "function" == typeof define && define . amd ? define ( [ "url" , "xmlserializer" , "sane-domparser-error" , "inlineresources" ] , function ( e , t , n , r ) { return o . rasterizeHTML = i ( e , t , n , r ) } ) : "object" == typeof module && module . exports ? module . exports = i ( require ( "url" ) , require ( "xmlserializer" ) , require ( "sane-domparser-error" ) , require ( "inlineresources" ) ) : o . rasterizeHTML = i ( o . url , o . xmlserializer , o . sanedomparsererror , o . inlineresources ) } ( this , function ( e , t , n , r ) { var o = function ( n ) { "use strict" ; var o = { } , t = [ ] ; o . joinUrl = function ( e , t ) { return e ? n . resolve ( e , t ) : t } , o . getConstantUniqueIdFor = function ( e ) { return t . indexOf ( e ) < 0 && t . push ( e ) , t . indexOf ( e ) } , o . clone = function ( e ) { var t , n = { } ; for ( t in e ) e . hasOwnProperty ( t ) && ( n [ t ] = e [ t ] ) ; return n } ; return o . parseOptionalParameters = function ( e ) { var t , n , r = { canvas : null , options : { } } ; return null == e [ 0 ] || ( t = e [ 0 ] , "object" == typeof ( n = t ) && null !== n && Object . prototype . toString . apply ( t ) . match ( /\[object (Canvas|HTMLCanvasElement)\]/i ) ) ? ( r . canvas = e [ 0 ] || null , r . options = o . clone ( e [ 1 ] ) ) : r . options = o . clone ( e [ 0 ] ) , r } , o } ( e ) , i = function ( i ) { "use strict" ; function u ( e , t , n ) { var r = e [ t ] ; return e [ t ] = function ( ) { var e = Array . prototype . slice . call ( arguments ) ; return n . apply ( this , [ e , r ] ) } , r } var e = { } ; return e . baseUrlRespectingXhr = function ( t , o ) { return function ( ) { var e = new t ; return u ( e , "open" , function ( e , t ) { var n = e . shift ( ) , r = e . shift ( ) , r = i . joinUrl ( o , r ) ; return t . apply ( this , [ n , r ] . concat ( e ) ) } ) , e } } , e . finishNotifyingXhr = function ( t ) { function e ( ) { var e = new t ; return u ( e , "send" , function ( e , t ) { return r += 1 , t . apply ( this , arguments ) } ) , e . addEventListener ( "load" , function ( ) { o += 1 , n ( ) } ) , e } var n , r = 0 , o = 0 , i = ! 1 , c = new Promise ( function ( e ) { n = function ( ) { r - o <= 0 && i && e ( { totalCount : r } ) } } ) ; return e . waitForRequestsToFinish = function ( ) { return i = ! 0 , n ( ) , c } , e } , e } ( o ) , e = function ( i ) { "use strict" ; function r ( e ) { return Array . prototype . slice . call ( e ) } var e = { } , c = { active : ! 0 , hover : ! 0 , focus : ! 1 , target : ! 1 } ; return e . fakeUserAction = function ( e , t , n ) { var r = e . querySelector ( t ) , o = ":" + n , t = "rasterizehtml" + n ; r && ( c [ n ] ? i . addClassNameRecursively ( r , t ) : i . addClassName ( r , t ) , i . rewriteCssSelectorWith ( e , o , "." + t ) ) } , e . persistInputValues = function ( e ) { function t ( e ) { return "checkbox" === e . type || "radio" === e . type } var n = e . querySelectorAll ( "input" ) , e = e . querySelectorAll ( "textarea" ) ; r ( n ) . filter ( t ) . forEach ( function ( e ) { e . checked ? e . setAttribute ( "checked" , "" ) : e . removeAttribute ( "checked" ) } ) , r ( n ) . filter ( function ( e ) { return ! t ( e ) } ) . forEach ( function ( e ) { e . setAttribute ( "value" , e . value ) } ) , r ( e ) . forEach ( function ( e ) { e . textContent = e . value } ) } , e . rewriteTagNameSelectorsToLowerCase = function ( e ) { i . lowercaseCssTypeSelectors ( e , i . findHtmlOnlyNodeNames ( e ) ) } , e } ( function ( ) { "use strict" ; function c ( e ) { return Array . prototype . slice . call ( e ) } var n = { } ; n . addClassName = function ( e , t ) { e . className += " " + t } , n . addClassNameRecursively = function ( e , t ) { n . addClassName ( e , t ) , e . parentNode !== e . ownerDocument && n . addClassNameRecursively ( e . parentNode , t ) } ; function r ( e , t , o ) { var i = "((?:^|[^.#:\\w])|(?=\\W))(" + t . join ( "|" ) + ")(?=\\W|$)" ; c ( e . querySelectorAll ( "style" ) ) . forEach ( function ( e ) { var t , n ; void 0 === e . sheet && ( t = e , n = document . implementation . createHTMLDocument ( "" ) , ( r = document . createElement ( "style" ) ) . textContent = t . textContent , n . body . appendChild ( r ) , t . sheet = r . sheet ) ; var r = c ( e . sheet . cssRules ) . filter ( function ( e ) { return e . selectorText && new RegExp ( i , "i" ) . test ( e . selectorText ) } ) ; r . length && ( r . forEach ( function ( e ) { var t , n = e . selectorText . replace ( new RegExp ( i , "gi" ) , function ( e , t , n ) { return t + o ( n ) } ) ; n !== e . selectorText && ( t = n , e = ( n = e ) . cssText . replace ( /^[^\{]+/ , "" ) , u ( n , t + " " + e ) ) } ) , e . textContent = a ( e . sheet . cssRules ) ) } ) } var u = function ( e , t ) { var n = e . parentStyleSheet , e = c ( n . cssRules ) . indexOf ( e ) ; n . insertRule ( t , e + 1 ) , n . deleteRule ( e ) } , a = function ( e ) { return c ( e ) . reduce ( function ( e , t ) { return e + t . cssText } , "" ) } ; return n . rewriteCssSelectorWith = function ( e , t , n ) { r ( e , [ t ] , function ( ) { return n } ) } , n . lowercaseCssTypeSelectors = function ( e , t ) { r ( e , t , function ( e ) { return e . toLowerCase ( ) } ) } , n . findHtmlOnlyNodeNames = function ( e ) { for ( var t , n = e . ownerDocument . createTreeWalker ( e , NodeFilter . SHOW _ELEMENT ) , r = { } , o = { } ; t = n . currentNode . tagName . toLowerCase ( ) , "http://www.w3.org/1999/xhtml" === n . currentNode . namespaceURI ? r [ t ] = ! 0 : o [ t ] = ! 0 , n . nextNode ( ) ; ) ; retu
xrf . model = { }
xrf . init = ( ( init ) => function ( opts ) {
init ( opts )
if ( opts . loaders ) Object . values ( opts . loaders ) . map ( xrf . patchLoader )
xrf . patchRenderer ( opts . renderer )
xrf . navigator . init ( )
// return xrfragment lib as 'xrf' query functor (like jquery)
for ( let i in xrf ) xrf . query [ i ] = xrf [ i ]
return xrf . query
} ) ( xrf . init )
xrf . patchRenderer = function ( renderer ) {
renderer . xr . addEventListener ( 'sessionstart' , ( ) => xrf . baseReferenceSpace = renderer . xr . getReferenceSpace ( ) ) ;
renderer . xr . enabled = true ;
renderer . render = ( ( render ) => function ( scene , camera ) {
if ( xrf . model && xrf . model . render )
xrf . model . render ( scene , camera )
render ( scene , camera )
} ) ( renderer . render . bind ( renderer ) )
}
xrf . patchLoader = function ( loader ) {
if ( loader . prototype . load . xrf _patched ) return // prevent patching aliased loaders twice
loader . prototype . load = ( ( load ) => function ( url , onLoad , onProgress , onError ) {
load . call ( this ,
url ,
( model ) => {
onLoad ( model ) ;
xrf . parseModel ( model , url )
} ,
onProgress ,
onError )
} ) ( loader . prototype . load )
loader . prototype . load . xrf _patched = true
}
xrf . getFile = ( url ) => url . split ( "/" ) . pop ( ) . replace ( /#.*/ , '' )
xrf . parseModel = function ( model , url ) {
let file = xrf . getFile ( url )
model . file = file
// eval embedded XR fragments
model . scene . traverse ( ( mesh ) => xrf . eval . mesh ( mesh , model ) )
// add animations
model . clock = new xrf . THREE . Clock ( ) ;
model . mixer = new xrf . THREE . AnimationMixer ( model . scene )
model . animations . map ( ( anim ) => model . mixer . clipAction ( anim ) . play ( ) )
model . render = function ( ) {
model . mixer . update ( model . clock . getDelta ( ) )
xrf . navigator . material . selection . color . r = ( 1.0 + Math . sin ( model . clock . getElapsedTime ( ) * 10 ) ) / 2
}
}
xrf . getLastModel = ( ) => xrf . model . last
xrf . eval = function ( url , model , flags ) { // evaluate fragments in url
model = model || xrf . model
let { THREE , camera } = xrf
let frag = xrf . URI . parse ( url , flags || xrf . XRF . NAVIGATOR )
let opts = { frag , mesh : xrf . camera , model , camera : xrf . camera , scene : xrf . scene , renderer : xrf . renderer , THREE : xrf . THREE }
xrf . emit ( 'eval' , opts )
. then ( ( ) => {
for ( let k in frag ) {
xrf . eval . fragment ( k , opts )
}
} )
}
xrf . eval . mesh = ( mesh , model ) => { // evaluate embedded fragments (metadata) inside mesh of model
if ( mesh . userData ) {
let frag = { }
for ( let k in mesh . userData ) xrf . Parser . parse ( k , mesh . userData [ k ] , frag )
for ( let k in frag ) {
let opts = { frag , mesh , model , camera : xrf . camera , scene : xrf . scene , renderer : xrf . renderer , THREE : xrf . THREE }
mesh . userData . XRF = frag // allow fragment impl to access XRF obj already
xrf . emit ( 'eval' , opts )
. then ( ( ) => xrf . eval . fragment ( k , opts ) )
}
}
}
xrf . eval . fragment = ( k , opts ) => { // evaluate one fragment
2023-08-15 18:27:26 +02:00
let frag = opts . frag [ k ] ;
2023-08-04 09:11:26 +02:00
// call native function (xrf/env.js e.g.), or pass it to user decorator
let func = xrf . frag [ k ] || function ( ) { }
2023-08-15 18:27:26 +02:00
if ( xrf [ k ] ) xrf [ k ] ( func , frag , opts )
else func ( frag , opts )
2023-08-04 09:11:26 +02:00
}
xrf . reset = ( ) => {
const disposeObject = ( obj ) => {
if ( obj . children . length > 0 ) obj . children . forEach ( ( child ) => disposeObject ( child ) ) ;
if ( obj . geometry ) obj . geometry . dispose ( ) ;
if ( obj . material ) {
if ( obj . material . map ) obj . material . map . dispose ( ) ;
obj . material . dispose ( ) ;
}
obj . clear ( )
obj . removeFromParent ( )
return true
} ;
let nodes = [ ]
xrf . scene . traverse ( ( child ) => child . isXRF ? nodes . push ( child ) : false )
nodes . map ( disposeObject ) // leave non-XRF objects intact
xrf . interactive = xrf . InteractiveGroup ( xrf . THREE , xrf . renderer , xrf . camera )
xrf . add ( xrf . interactive )
}
xrf . parseUrl = ( url ) => {
const urlObj = new URL ( url . match ( /:\/\// ) ? url : String ( ` https://fake.com/ ${ url } ` ) . replace ( /\/\// , '/' ) )
let dir = url . substring ( 0 , url . lastIndexOf ( '/' ) + 1 )
const file = urlObj . pathname . substring ( urlObj . pathname . lastIndexOf ( '/' ) + 1 ) ;
const hash = url . match ( /#/ ) ? url . replace ( /.*#/ , '' ) : ''
const ext = file . split ( '.' ) . pop ( )
return { urlObj , dir , file , hash , ext }
}
xrf . add = ( object ) => {
object . isXRF = true // mark for easy deletion when replacing scene
xrf . scene . add ( object )
}
// wrapper to survive in/outside modules
2023-05-09 17:42:29 +02:00
2023-06-07 17:42:21 +02:00
xrf . InteractiveGroup = function ( THREE , renderer , camera ) {
2023-05-12 22:06:21 +02:00
let {
Group ,
Matrix4 ,
Raycaster ,
Vector2
} = THREE
const _pointer = new Vector2 ( ) ;
const _event = { type : '' , data : _pointer } ;
class InteractiveGroup extends Group {
constructor ( renderer , camera ) {
super ( ) ;
if ( ! renderer || ! camera ) return
2023-05-22 17:18:15 +02:00
// extract camera when camera-rig is passed
camera . traverse ( ( n ) => String ( n . type ) . match ( /Camera/ ) ? camera = n : null )
2023-05-12 22:06:21 +02:00
const scope = this ;
const raycaster = new Raycaster ( ) ;
const tempMatrix = new Matrix4 ( ) ;
2023-05-17 21:31:28 +02:00
function nocollide ( ) {
if ( nocollide . tid ) return // ratelimit
_event . type = "nocollide"
scope . children . map ( ( c ) => c . dispatchEvent ( _event ) )
nocollide . tid = setTimeout ( ( ) => nocollide . tid = null , 100 )
}
2023-05-12 22:06:21 +02:00
// Pointer Events
const element = renderer . domElement ;
function onPointerEvent ( event ) {
//event.stopPropagation();
const rect = renderer . domElement . getBoundingClientRect ( ) ;
_pointer . x = ( event . clientX - rect . left ) / rect . width * 2 - 1 ;
_pointer . y = - ( event . clientY - rect . top ) / rect . height * 2 + 1 ;
raycaster . setFromCamera ( _pointer , camera ) ;
const intersects = raycaster . intersectObjects ( scope . children , false ) ;
if ( intersects . length > 0 ) {
const intersection = intersects [ 0 ] ;
const object = intersection . object ;
const uv = intersection . uv ;
_event . type = event . type ;
_event . data . set ( uv . x , 1 - uv . y ) ;
object . dispatchEvent ( _event ) ;
2023-05-17 21:31:28 +02:00
} else nocollide ( )
2023-05-12 22:06:21 +02:00
}
element . addEventListener ( 'pointerdown' , onPointerEvent ) ;
element . addEventListener ( 'pointerup' , onPointerEvent ) ;
element . addEventListener ( 'pointermove' , onPointerEvent ) ;
element . addEventListener ( 'mousedown' , onPointerEvent ) ;
element . addEventListener ( 'mouseup' , onPointerEvent ) ;
element . addEventListener ( 'mousemove' , onPointerEvent ) ;
element . addEventListener ( 'click' , onPointerEvent ) ;
// WebXR Controller Events
// TODO: Dispatch pointerevents too
const events = {
'move' : 'mousemove' ,
'select' : 'click' ,
'selectstart' : 'mousedown' ,
'selectend' : 'mouseup'
} ;
function onXRControllerEvent ( event ) {
const controller = event . target ;
tempMatrix . identity ( ) . extractRotation ( controller . matrixWorld ) ;
raycaster . ray . origin . setFromMatrixPosition ( controller . matrixWorld ) ;
raycaster . ray . direction . set ( 0 , 0 , - 1 ) . applyMatrix4 ( tempMatrix ) ;
const intersections = raycaster . intersectObjects ( scope . children , false ) ;
if ( intersections . length > 0 ) {
const intersection = intersections [ 0 ] ;
const object = intersection . object ;
const uv = intersection . uv ;
_event . type = events [ event . type ] ;
_event . data . set ( uv . x , 1 - uv . y ) ;
if ( _event . type != "mousemove" ) {
console . log ( event . type + " => " + _event . type )
}
object . dispatchEvent ( _event ) ;
2023-05-17 21:31:28 +02:00
} else nocollide ( )
2023-05-12 22:06:21 +02:00
}
const controller1 = renderer . xr . getController ( 0 ) ;
controller1 . addEventListener ( 'move' , onXRControllerEvent ) ;
controller1 . addEventListener ( 'select' , onXRControllerEvent ) ;
controller1 . addEventListener ( 'selectstart' , onXRControllerEvent ) ;
controller1 . addEventListener ( 'selectend' , onXRControllerEvent ) ;
const controller2 = renderer . xr . getController ( 1 ) ;
controller2 . addEventListener ( 'move' , onXRControllerEvent ) ;
controller2 . addEventListener ( 'select' , onXRControllerEvent ) ;
controller2 . addEventListener ( 'selectstart' , onXRControllerEvent ) ;
controller2 . addEventListener ( 'selectend' , onXRControllerEvent ) ;
}
}
return new InteractiveGroup ( renderer , camera )
}
2023-05-17 21:31:28 +02:00
xrf . navigator = { }
2023-05-12 22:06:21 +02:00
2023-07-04 17:15:23 +02:00
xrf . navigator . to = ( url , flags , loader , data ) => {
2023-05-18 17:11:11 +02:00
if ( ! url ) throw 'xrf.navigator.to(..) no url given'
2023-06-22 08:48:52 +02:00
2023-05-12 22:06:21 +02:00
return new Promise ( ( resolve , reject ) => {
2023-05-17 21:31:28 +02:00
let { urlObj , dir , file , hash , ext } = xrf . parseUrl ( url )
2023-05-18 17:11:11 +02:00
2023-05-22 17:18:15 +02:00
if ( ! file || xrf . model . file == file ) { // we're already loaded
2023-06-22 08:48:52 +02:00
xrf . eval ( url , xrf . model , flags ) // and eval local URI XR fragments
2023-05-18 17:11:11 +02:00
return resolve ( xrf . model )
}
2023-05-12 22:06:21 +02:00
if ( xrf . model && xrf . model . scene ) xrf . model . scene . visible = false
2023-07-04 17:15:23 +02:00
if ( ! loader ) {
const Loader = xrf . loaders [ ext ]
if ( ! Loader ) throw 'xrfragment: no loader passed to xrfragment for extension .' + ext
loader = loader || new Loader ( ) . setPath ( dir )
}
2023-05-12 22:06:21 +02:00
// force relative path
if ( dir ) dir = dir [ 0 ] == '.' ? dir : ` . ${ dir } `
2023-07-05 16:43:07 +02:00
url = url . replace ( dir , "" )
2023-07-04 17:15:23 +02:00
loader = loader || new Loader ( ) . setPath ( dir )
const onLoad = ( model ) => {
xrf . reset ( ) // clear xrf objects from scene
2023-05-18 12:39:47 +02:00
model . file = file
2023-06-22 08:48:52 +02:00
// only change url when loading *another* file
if ( xrf . model ) xrf . navigator . pushState ( ` ${ dir } ${ file } ` , hash )
2023-05-12 22:06:21 +02:00
xrf . model = model
2023-08-15 18:27:26 +02:00
// spec: 1. execute the default predefined view '#' (if exist) (https://xrfragment.org/#predefined_view)
xrf . frag . defaultPredefinedView ( { model , scene : model . scene } )
// spec: 2. execute predefined view(s) from URL (https://xrfragment.org/#predefined_view)
xrf . eval ( url , model ) // and eval URI XR fragments
xrf . add ( model . scene )
2023-06-22 08:48:52 +02:00
if ( ! hash . match ( /pos=/ ) )
xrf . eval ( '#pos=0,0,0' ) // set default position if not specified
2023-05-12 22:06:21 +02:00
resolve ( model )
2023-07-04 17:15:23 +02:00
}
if ( data ) loader . parse ( data , "" , onLoad )
else loader . load ( url , onLoad )
2023-05-12 22:06:21 +02:00
} )
}
2023-05-17 21:31:28 +02:00
xrf . navigator . init = ( ) => {
if ( xrf . navigator . init . inited ) return
2023-05-12 22:06:21 +02:00
window . addEventListener ( 'popstate' , function ( event ) {
2023-06-22 08:48:52 +02:00
xrf . navigator . to ( document . location . search . substr ( 1 ) + document . location . hash )
2023-05-12 22:06:21 +02:00
} )
2023-06-08 17:45:21 +02:00
xrf . navigator . material = {
selection : new xrf . THREE . LineBasicMaterial ( { color : 0xFF00FF , linewidth : 2 } )
}
2023-05-17 21:31:28 +02:00
xrf . navigator . init . inited = true
2023-05-12 22:06:21 +02:00
}
2023-06-22 08:48:52 +02:00
xrf . navigator . updateHash = ( hash ) => {
if ( hash == document . location . hash || hash . match ( /\|/ ) ) return // skip unnecesary pushState triggers
2023-06-22 13:59:17 +02:00
console . log ( ` URL: ${ document . location . search . substr ( 1 ) } # ${ hash } ` )
2023-06-22 08:48:52 +02:00
document . location . hash = hash
xrf . emit ( 'updateHash' , { hash } )
}
2023-05-18 17:11:11 +02:00
xrf . navigator . pushState = ( file , hash ) => {
2023-05-18 12:32:57 +02:00
if ( file == document . location . search . substr ( 1 ) ) return // page is in its default state
window . history . pushState ( { } , ` ${ file } # ${ hash } ` , document . location . pathname + ` ? ${ file } # ${ hash } ` )
2023-05-12 22:06:21 +02:00
}
2023-08-24 13:36:41 +02:00
xrf . frag . bg = function ( v , opts ) {
let { frag , mesh , model , camera , scene , renderer , THREE } = opts
console . log ( "└ bg " + v . x + "," + v . y + "," + v . z ) ;
if ( scene . background ) delete scene . background
scene . background = new THREE . Color ( v . x , v . y , v . z )
}
xrf . frag . clip = function ( v , opts ) {
let { frag , mesh , model , camera , scene , renderer , THREE } = opts
if ( v . x == 0 ) v . x = 1 ; // THREE.js .near restriction
console . log ( "└ clip " + v . x + "," + v . y ) ;
camera . near = v . x
camera . far = v . y
camera . updateProjectionMatrix ( ) ;
}
2023-05-12 22:06:21 +02:00
xrf . frag . env = function ( v , opts ) {
2023-05-09 17:42:29 +02:00
let { mesh , model , camera , scene , renderer , THREE } = opts
let env = mesh . getObjectByName ( v . string )
2023-06-22 08:48:52 +02:00
if ( ! env ) return console . warn ( "xrf.env " + v . string + " not found" )
2023-05-09 17:42:29 +02:00
env . material . map . mapping = THREE . EquirectangularReflectionMapping ;
scene . environment = env . material . map
2023-05-12 22:06:21 +02:00
//scene.texture = env.material.map
2023-05-09 17:42:29 +02:00
renderer . toneMapping = THREE . ACESFilmicToneMapping ;
2023-05-12 22:06:21 +02:00
renderer . toneMappingExposure = 2 ;
2023-05-09 17:42:29 +02:00
console . log ( ` └ applied image ' ${ v . string } ' as environment map ` )
}
2023-08-24 13:36:41 +02:00
xrf . frag . fog = function ( v , opts ) {
let { frag , mesh , model , camera , scene , renderer , THREE } = opts
console . log ( "└ fog " + v . x + "," + v . y ) ;
if ( v . x == 0 && v . y == 0 ) {
if ( scene . fog ) delete scene . fog
scene . fog = null ;
} else scene . fog = new THREE . Fog ( scene . background , v . x , v . y ) ;
}
xrf . frag . fov = function ( v , opts ) {
let { frag , mesh , model , camera , scene , renderer , THREE } = opts
console . log ( "└ fov " + v . int ) ;
camera . fov = v . int ;
camera . updateProjectionMatrix ( ) ;
}
2023-05-22 15:03:23 +02:00
/ * *
2023-05-23 14:41:24 +02:00
*
2023-05-22 15:03:23 +02:00
* navigation , portals & mutations
2023-05-23 14:41:24 +02:00
*
2023-05-22 15:03:23 +02:00
* | fragment | type | scope | example value |
2023-05-23 14:41:24 +02:00
* | ` href ` | string ( uri or predefined view ) | 🔒 | ` #pos=1,1,0 ` < br > ` #pos=1,1,0&rot=90,0,0 ` < br > ` #pos=pyramid ` < br > ` #pos=lastvisit|pyramid ` < br > ` ://somefile.gltf#pos=1,1,0 ` < br > |
*
* [ [ » example implementation | https : //github.com/coderofsalvation/xrfragment/blob/main/src/3rd/three/xrf/href.js]]<br>
* [ [ » example 3 D asset | https : //github.com/coderofsalvation/xrfragment/blob/main/example/assets/href.gltf#L192]]<br>
* [ [ » discussion | https : //github.com/coderofsalvation/xrfragment/issues/1]]<br>
2023-05-22 15:03:23 +02:00
*
* [ img [ xrfragment . jpg ] ]
2023-05-23 14:41:24 +02:00
*
*
2023-05-22 15:03:23 +02:00
* ! ! ! spec 1.0
2023-05-23 14:41:24 +02:00
*
* 1. an '' external '' - or '' file URI '' fully replaces the current scene and assumes ` pos=0,0,0&rot=0,0,0 ` by default ( unless specified )
*
2023-05-22 15:03:23 +02:00
* 2. navigation should not happen when queries ( ` q= ` ) are present in local url : queries will apply ( ` pos= ` , ` rot= ` e . g . ) to the targeted object ( s ) instead .
2023-05-23 14:41:24 +02:00
*
2023-05-22 15:03:23 +02:00
* 3. navigation should not happen '' immediately '' when user is more than 2 meter away from the portal / object containing the href ( to prevent accidental navigation e . g . )
2023-05-23 14:41:24 +02:00
*
* 4. URL navigation should always be reflected in the client ( in case of javascript : see [ [ here | https : //github.com/coderofsalvation/xrfragment/blob/dev/src/3rd/three/navigator.js]] for an example navigator).
*
* 5. In XR mode , the navigator back / forward - buttons should be always visible ( using a wearable e . g . , see [ [ here | https : //github.com/coderofsalvation/xrfragment/blob/dev/example/aframe/sandbox/index.html#L26-L29]] for an example wearable)
*
* [ img [ navigation . png ] ]
*
2023-05-22 15:03:23 +02:00
* /
2023-05-12 22:06:21 +02:00
xrf . frag . href = function ( v , opts ) {
2023-06-08 17:45:21 +02:00
opts . embedded = v // indicate embedded XR fragment
2023-05-09 17:42:29 +02:00
let { mesh , model , camera , scene , renderer , THREE } = opts
2023-06-09 16:40:08 +02:00
if ( mesh . userData . XRF . href . exec ) return // mesh already initialized
2023-05-22 14:10:44 +02:00
const world = {
pos : new THREE . Vector3 ( ) ,
scale : new THREE . Vector3 ( ) ,
quat : new THREE . Quaternion ( )
}
// detect equirectangular image
2023-06-20 14:59:25 +02:00
let texture = mesh . material && mesh . material . map ? mesh . material . map : null
2023-05-17 21:31:28 +02:00
if ( texture && texture . source . data . height == texture . source . data . width / 2 ) {
texture . mapping = THREE . ClampToEdgeWrapping
texture . needsUpdate = true
2023-05-09 17:42:29 +02:00
2023-05-22 14:10:44 +02:00
// poor man's equi-portal
mesh . material = new THREE . ShaderMaterial ( {
side : THREE . DoubleSide ,
uniforms : {
pano : { value : texture } ,
selected : { value : false } ,
} ,
vertexShader : `
vec3 portalPosition ;
varying vec3 vWorldPosition ;
varying float vDistanceToCenter ;
varying float vDistance ;
void main ( ) {
vDistanceToCenter = clamp ( length ( position - vec3 ( 0.0 , 0.0 , 0.0 ) ) , 0.0 , 1.0 ) ;
portalPosition = ( modelMatrix * vec4 ( 0.0 , 0.0 , 0.0 , 1.0 ) ) . xyz ;
vDistance = length ( portalPosition - cameraPosition ) ;
vWorldPosition = ( modelMatrix * vec4 ( position , 1.0 ) ) . xyz ;
gl _Position = projectionMatrix * modelViewMatrix * vec4 ( position , 1.0 ) ;
}
` ,
fragmentShader : `
# define RECIPROCAL _PI2 0.15915494
uniform sampler2D pano ;
uniform bool selected ;
varying float vDistanceToCenter ;
varying float vDistance ;
varying vec3 vWorldPosition ;
void main ( ) {
vec3 direction = normalize ( vWorldPosition - cameraPosition ) ;
vec2 sampleUV ;
sampleUV . y = - clamp ( direction . y * 0.5 + 0.5 , 0.0 , 1.0 ) ;
sampleUV . x = atan ( direction . z , - direction . x ) * - RECIPROCAL _PI2 ;
2023-05-22 15:03:23 +02:00
sampleUV . x += 0.33 ; // adjust focus to AFRAME's a-scene.components.screenshot.capture()
2023-05-22 14:10:44 +02:00
vec4 color = texture2D ( pano , sampleUV ) ;
// Convert color to grayscale (lazy lite approach to not having to match tonemapping/shaderstacking of THREE.js)
float luminance = 0.2126 * color . r + 0.7152 * color . g + 0.0722 * color . b ;
vec4 grayscale _color = selected ? color : vec4 ( vec3 ( luminance ) + vec3 ( 0.33 ) , color . a ) ;
gl _FragColor = grayscale _color ;
}
` ,
} ) ;
mesh . material . needsUpdate = true
2023-06-20 14:59:25 +02:00
} else if ( mesh . material ) { mesh . material = mesh . material . clone ( ) }
2023-06-07 17:42:21 +02:00
let click = mesh . userData . XRF . href . exec = ( e ) => {
2023-06-22 13:59:17 +02:00
let isLocal = v . string [ 0 ] == '#'
2023-06-22 12:05:36 +02:00
let lastPos = ` #pos= ${ camera . position . x } , ${ camera . position . y } , ${ camera . position . z } `
2023-06-08 17:45:21 +02:00
xrf
2023-06-22 12:05:36 +02:00
. emit ( 'href' , { click : true , mesh , xrf : v } ) // let all listeners agree
2023-06-08 17:45:21 +02:00
. then ( ( ) => {
2023-06-22 08:48:52 +02:00
const flags = v . string [ 0 ] == '#' && v . string . match ( /(\||#q)/ ) ? xrf . XRF . PV _OVERRIDE : undefined
2023-06-22 13:59:17 +02:00
if ( ! isLocal || v . string . match ( /pos=/ ) ) xrf . navigator . to ( lastPos ) // commit last position
2023-06-22 12:05:36 +02:00
xrf . navigator . to ( v . string , flags ) // let's surf to HREF!
2023-06-08 17:45:21 +02:00
} )
2023-05-22 14:10:44 +02:00
}
let selected = ( state ) => ( ) => {
if ( mesh . selected == state ) return // nothing changed
2023-06-20 14:59:25 +02:00
if ( mesh . material ) {
if ( mesh . material . uniforms ) mesh . material . uniforms . selected . value = state
else mesh . material . color . r = mesh . material . color . g = mesh . material . color . b = state ? 2.0 : 1.0
}
2023-05-22 14:10:44 +02:00
// update mouse cursor
if ( ! renderer . domElement . lastCursor )
renderer . domElement . lastCursor = renderer . domElement . style . cursor
renderer . domElement . style . cursor = state ? 'pointer' : renderer . domElement . lastCursor
2023-06-02 12:00:21 +02:00
xrf
. emit ( 'href' , { selected : state , mesh , xrf : v } ) // let all listeners agree
. then ( ( ) => mesh . selected = state )
2023-05-12 22:06:21 +02:00
}
2023-06-07 17:42:21 +02:00
mesh . addEventListener ( 'click' , click )
mesh . addEventListener ( 'mousemove' , selected ( true ) )
mesh . addEventListener ( 'nocollide' , selected ( false ) )
2023-05-17 21:31:28 +02:00
2023-05-22 15:03:23 +02:00
// lazy add mesh (because we're inside a recursive traverse)
setTimeout ( ( mesh ) => {
2023-06-07 17:42:21 +02:00
mesh . getWorldPosition ( world . pos )
mesh . getWorldScale ( world . scale )
mesh . getWorldQuaternion ( world . quat ) ;
mesh . position . copy ( world . pos )
mesh . scale . copy ( world . scale )
mesh . setRotationFromQuaternion ( world . quat ) ;
2023-05-22 15:03:23 +02:00
xrf . interactive . add ( mesh )
2023-08-15 18:27:26 +02:00
} , 10 , mesh )
2023-05-09 17:42:29 +02:00
}
2023-05-22 15:03:23 +02:00
/ * *
2023-05-23 14:41:24 +02:00
* > above solutions were abducted from [ [ this | https : //i.imgur.com/E3En0gJ.png]] and [[this|https://i.imgur.com/lpnTz3A.png]] survey result
2023-05-22 15:03:23 +02:00
*
2023-05-23 14:41:24 +02:00
* ! ! ! Demo
*
* < $videojs controls = "controls" aspectratio = "16:9" preload = "auto" poster = "" fluid = "fluid" class = "vjs-big-play-centered" >
* < source src = "https://coderofsalvation.github.io/xrfragment.media/href.mp4" type = "video/mp4" / >
* < / $ v i d e o j s >
*
* > capture of < a href = "./example/aframe/sandbox" target = "_blank" > aframe / sandbox < / a >
2023-05-22 15:03:23 +02:00
* /
2023-08-15 18:27:26 +02:00
xrf . frag . mov = function ( v , opts ) {
2023-05-09 17:42:29 +02:00
let { frag , mesh , model , camera , scene , renderer , THREE } = opts
2023-05-22 17:18:15 +02:00
2023-08-15 18:27:26 +02:00
if ( frag . q ) { // only operate on queried object(s)
frag . q . getObjects ( ) . map ( ( o ) => {
o . position . add ( new THREE . Vector3 ( v . x , v . y , v . z ) )
} )
}
}
xrf . frag . pos = function ( v , opts ) {
let { frag , mesh , model , camera , scene , renderer , THREE } = opts
if ( frag . q ) { // only operate on queried object(s)
// apply roundrobin (if any)
if ( v . args ) v = v . args [ xrf . roundrobin ( v , model ) ]
frag . q . getObjects ( ) . map ( ( o ) => {
// if object has no parent (name == 'Scene') use absolute positioning, otherwise relative to parent
o . position . x = o . parent . name == 'Scene' ? v . x : o . positionOriginal . x + v . x
o . position . y = o . parent . name == 'Scene' ? v . z : o . positionOriginal . y + v . z
o . position . z = o . parent . name == 'Scene' ? v . y : o . positionOriginal . z + v . y
} )
} else {
2023-06-08 17:45:21 +02:00
camera . position . x = v . x
camera . position . y = v . y
camera . position . z = v . z
}
2023-08-15 18:27:26 +02:00
}
xrf . frag . defaultPredefinedView = ( opts ) => {
let { scene , model } = opts ;
let frag = { }
xrf . Parser . parse ( "#" , "" , frag )
xrf . frag . updatePredefinedView ( { frag , model , scene } )
2023-06-08 17:45:21 +02:00
}
2023-08-15 18:27:26 +02:00
xrf . frag . updatePredefinedView = ( opts ) => {
2023-08-08 15:41:16 +02:00
let { frag , scene , model } = opts
2023-06-08 17:45:21 +02:00
2023-06-27 09:43:10 +02:00
// spec: https://xrfragment.org/#Selection%20of%20interest
2023-06-09 17:40:52 +02:00
const selectionOfInterest = ( frag , scene , mesh ) => {
let id = frag . string
2023-06-22 11:35:30 +02:00
let oldSelection
2023-06-20 14:59:25 +02:00
if ( ! id ) return id // important: ignore empty strings
2023-06-22 11:35:30 +02:00
if ( mesh . selection ) oldSelection = mesh . selection
2023-06-20 14:59:25 +02:00
// Selection of Interest if predefined_view matches object name
2023-06-22 08:48:52 +02:00
if ( mesh . visible && ( id == mesh . name || id . substr ( 1 ) == mesh . userData . class ) ) {
2023-06-09 17:40:52 +02:00
xrf . emit ( 'selection' , { ... opts , frag } )
2023-06-08 17:45:21 +02:00
. then ( ( ) => {
const margin = 1.2
mesh . scale . multiplyScalar ( margin )
mesh . selection = new xrf . THREE . BoxHelper ( mesh , 0xff00ff )
mesh . scale . divideScalar ( margin )
mesh . selection . material . dispose ( )
mesh . selection . material = xrf . navigator . material . selection
mesh . selection . isXRF = true
scene . add ( mesh . selection )
} )
}
2023-06-22 11:35:30 +02:00
return oldSelection
2023-06-08 17:45:21 +02:00
}
2023-05-22 17:18:15 +02:00
2023-06-27 09:43:10 +02:00
// spec: https://xrfragment.org/#predefined_view
2023-06-09 16:40:08 +02:00
const predefinedView = ( frag , scene , mesh ) => {
2023-08-15 18:27:26 +02:00
let id = frag . string || frag . fragment
id = ` # ${ id } `
if ( id == '##' ) id = '#' ; // default predefined view
if ( ! id ) return // prevent empty matches
if ( mesh . userData [ id ] ) { // get alias
frag = xrf . URI . parse ( mesh . userData [ id ] , xrf . XRF . NAVIGATOR | xrf . XRF . PV _OVERRIDE | xrf . XRF . METADATA )
2023-06-22 08:48:52 +02:00
xrf . emit ( 'predefinedView' , { ... opts , frag } )
. then ( ( ) => {
for ( let k in frag ) {
let opts = { frag , model , camera : xrf . camera , scene : xrf . scene , renderer : xrf . renderer , THREE : xrf . THREE }
2023-08-15 18:27:26 +02:00
if ( frag [ k ] . is ( xrf . XRF . PV _EXECUTE ) && scene . XRF _PV _ORIGIN != k ) { // cyclic detection
traverseScene ( frag [ k ] , scene ) // recurse predefined views
} else xrf . eval . fragment ( k , opts )
2023-06-22 08:48:52 +02:00
}
} )
2023-05-22 17:18:15 +02:00
}
2023-06-08 17:45:21 +02:00
}
2023-06-22 08:48:52 +02:00
const traverseScene = ( v , scene ) => {
let remove = [ ]
if ( ! scene ) return
scene . traverse ( ( mesh ) => {
remove . push ( selectionOfInterest ( v , scene , mesh ) )
predefinedView ( v , scene , mesh )
} )
2023-06-22 11:35:30 +02:00
remove . filter ( ( e ) => e ) . map ( ( selection ) => {
scene . remove ( selection )
2023-06-22 08:48:52 +02:00
} )
}
2023-05-22 17:18:15 +02:00
2023-06-22 08:48:52 +02:00
let pviews = [ ]
2023-06-08 17:45:21 +02:00
for ( let i in frag ) {
let v = frag [ i ]
if ( v . is ( xrf . XRF . PV _EXECUTE ) ) {
2023-08-15 18:27:26 +02:00
scene . XRF _PV _ORIGIN = v . string
2023-06-09 16:40:08 +02:00
if ( v . args ) v = v . args [ xrf . roundrobin ( v , xrf . model ) ]
2023-08-15 18:27:26 +02:00
// wait for nested instances to arrive at the scene ?
traverseScene ( v , scene )
2023-06-22 08:48:52 +02:00
if ( v . string ) pviews . push ( v . string )
} else if ( v . is ( xrf . XRF . NAVIGATOR ) ) pviews . push ( ` ${ i } = ${ v . string } ` )
2023-05-22 17:18:15 +02:00
}
2023-06-22 08:48:52 +02:00
if ( pviews . length ) xrf . navigator . updateHash ( pviews . join ( "&" ) )
2023-05-09 17:42:29 +02:00
}
2023-06-08 17:45:21 +02:00
// when predefined view occurs in url changes
2023-08-15 18:27:26 +02:00
//xrf.addEventListener('updateHash', (opts) => {
// let frag = xrf.URI.parse( opts.xrf.string, xrf.XRF.NAVIGATOR | xrf.XRF.PV_OVERRIDE | xrf.XRF.METADATA )
// xrf.frag.updatePredefinedView({frag,scene:xrf.scene,href:opts.xrf})
//})
2023-06-08 17:45:21 +02:00
// clicking href url with predefined view
xrf . addEventListener ( 'href' , ( opts ) => {
if ( ! opts . click || opts . xrf . string [ 0 ] != '#' ) return
2023-08-15 18:27:26 +02:00
let frag = xrf . URI . parse ( opts . xrf . string , xrf . XRF . NAVIGATOR | xrf . XRF . PV _OVERRIDE | xrf . XRF . METADATA )
xrf . frag . updatePredefinedView ( { frag , scene : xrf . scene , href : opts . xrf } )
2023-06-08 17:45:21 +02:00
} )
2023-06-09 17:40:52 +02:00
//let updateUrl = (opts) => {
// console.dir(opts)
//}
//
//xrf.addEventListener('predefinedView', updateUrl )
//xrf.addEventListener('selection', updateUrl )
2023-06-27 09:43:10 +02:00
// spec: https://xrfragment.org/#queries
2023-05-12 22:06:21 +02:00
xrf . frag . q = function ( v , opts ) {
let { frag , mesh , model , camera , scene , renderer , THREE } = opts
console . log ( " └ running query " )
2023-06-07 17:42:21 +02:00
let qobjs = Object . keys ( v . query )
2023-08-15 18:27:26 +02:00
// convience function for other fragments (which apply to the query)
frag . q . getObjects = ( ) => {
let objs = [ ]
scene . traverse ( ( o ) => {
for ( let name in v . query ) {
let qobj = v . query [ name ] ;
if ( qobj . class && o . userData . class && o . userData . class == name ) objs . push ( o )
else if ( qobj . id && o . name == name ) objs . push ( o )
}
} )
return objs . filter ( ( o ) => o ) // return and filter out empty
. map ( ( o ) => {
if ( ! o . positionOriginal ) o . positionOriginal = o . position . clone ( )
return o
} )
}
2023-06-27 09:43:10 +02:00
// spec: https://xrfragment.org/#src
2023-06-08 17:45:21 +02:00
const instanceScene = ( ) => {
v . scene = new THREE . Group ( )
for ( let i in v . query ) {
let target = v . query [ i ]
if ( ! scene . getObjectByName ( i ) && i != '*' ) return console . log ( ` └ mesh not found: ${ i } ` )
if ( i == '*' ) {
let cloneScene = scene . clone ( )
2023-07-05 16:43:07 +02:00
// add interactive elements (href's e.g.) *TODO* this is called by both internal/external src's
2023-07-04 17:15:23 +02:00
v . scene . add ( xrf . interactive . clone ( ) )
2023-06-08 17:45:21 +02:00
cloneScene . children . forEach ( ( child ) => v . scene . getObjectByName ( child . name ) ? null : v . scene . add ( child ) )
target . mesh = v . scene
} else {
if ( ! v . scene . getObjectByName ( i ) && target . id === true ) {
2023-07-05 16:43:07 +02:00
console . log ( ` └ query-ing mesh: ${ i } ` )
2023-06-08 17:45:21 +02:00
v . scene . add ( target . mesh = scene . getObjectByName ( i ) . clone ( ) )
}
}
if ( target . id != undefined && target . mesh ) {
target . mesh . position . set ( 0 , 0 , 0 )
target . mesh . rotation . set ( 0 , 0 , 0 )
2023-06-07 17:42:21 +02:00
}
}
2023-06-20 14:59:25 +02:00
// hide negative selectors
let negative = [ ]
2023-06-08 17:45:21 +02:00
v . scene . traverse ( ( mesh ) => {
for ( let i in v . query ) {
2023-06-20 14:59:25 +02:00
if ( mesh . name == i && v . query [ i ] . id === false ) negative . push ( mesh )
2023-06-08 17:45:21 +02:00
}
} )
2023-06-20 14:59:25 +02:00
negative . map ( ( mesh ) => mesh . visible = false )
2023-05-12 22:06:21 +02:00
}
2023-06-08 17:45:21 +02:00
2023-06-27 09:43:10 +02:00
// spec: https://xrfragment.org/#queries
2023-06-08 17:45:21 +02:00
const showHide = ( ) => {
let q = frag . q . query
scene . traverse ( ( mesh ) => {
for ( let i in q ) {
2023-06-09 16:40:08 +02:00
let isMeshId = q [ i ] . id != undefined
let isMeshClass = q [ i ] . class != undefined
let isMeshProperty = q [ i ] . rules != undefined && q [ i ] . rules . length && ! isMeshId && ! isMeshClass
2023-06-22 08:48:52 +02:00
if ( q [ i ] . root && mesh . isSRC ) continue ; // ignore nested object for root-items (queryseletor '/foo' e.g.)
2023-06-09 16:40:08 +02:00
if ( isMeshId && i == mesh . name ) mesh . visible = q [ i ] . id
if ( isMeshClass && i == mesh . userData . class ) mesh . visible = q [ i ] . class
if ( isMeshProperty && mesh . userData [ i ] ) mesh . visible = ( new xrf . Query ( frag . q . string ) ) . testProperty ( i , mesh . userData [ i ] )
2023-06-08 17:45:21 +02:00
}
} )
}
2023-07-05 16:43:07 +02:00
const doLocalInstancing = opts . embedded && opts . embedded . fragment == 'src' && opts . embedded . string [ 0 ] == '#'
if ( doLocalInstancing ) instanceScene ( ) // spec : https://xrfragment.org/#src
else showHide ( ) // spec : https://xrfragment.org/#queries
2023-05-12 22:06:21 +02:00
}
xrf . frag . rot = function ( v , opts ) {
2023-08-15 18:27:26 +02:00
let { frag , mesh , model , camera , scene , renderer , THREE } = opts
// apply roundrobin (if any)
if ( v . args ) v = v . args [ xrf . roundrobin ( v , model ) ]
if ( frag . q ) { // only operate on queried object(s)
frag . q . getObjects ( ) . map ( ( o ) => {
o . rotation . set (
v . x * Math . PI / 180 ,
v . y * Math . PI / 180 ,
v . z * Math . PI / 180
)
} )
} else {
console . log ( " └ setting camera rotation to " + v . string )
camera . rotation . set (
v . x * Math . PI / 180 ,
v . y * Math . PI / 180 ,
v . z * Math . PI / 180
)
camera . updateMatrixWorld ( )
}
}
xrf . frag . scale = function ( v , opts ) {
let { frag , mesh , model , camera , scene , renderer , THREE } = opts
// apply roundrobin (if any)
if ( v . args ) v = v . args [ xrf . roundrobin ( v , model ) ]
if ( frag . q ) { // only operate on queried object(s)
frag . q . getObjects ( ) . map ( ( o ) => {
o . scale . x = v . x
o . scale . y = v . y
o . scale . z = v . z
} )
}
}
xrf . frag . show = function ( v , opts ) {
let { frag , mesh , model , camera , scene , renderer , THREE } = opts
// apply roundrobin (if any)
if ( v . args ) v = v . args [ xrf . roundrobin ( v , model ) ]
if ( frag . q ) { // only operate on queried object(s)
frag . q . getObjects ( ) . map ( ( o ) => {
o . visible = v . int == 1 ;
} )
}
2023-05-09 17:42:29 +02:00
}
2023-05-12 22:06:21 +02:00
// *TODO* use webgl instancing
xrf . frag . src = function ( v , opts ) {
2023-08-04 09:11:26 +02:00
2023-06-08 17:45:21 +02:00
opts . embedded = v // indicate embedded XR fragment
2023-05-09 17:42:29 +02:00
let { mesh , model , camera , scene , renderer , THREE } = opts
2023-07-04 18:15:08 +02:00
console . log ( " └ instancing src" )
2023-06-07 17:42:21 +02:00
let src = new THREE . Group ( )
2023-07-04 18:15:08 +02:00
let frag = xrfragment . URI . parse ( v . string )
2023-05-17 21:31:28 +02:00
2023-07-04 18:15:08 +02:00
const localSRC = ( ) => {
2023-05-17 21:31:28 +02:00
setTimeout ( ( ) => {
2023-07-06 15:27:27 +02:00
// scale URI XR Fragments inside src-value
2023-05-17 21:31:28 +02:00
for ( var i in frag ) {
2023-06-07 17:42:21 +02:00
xrf . eval . fragment ( i , Object . assign ( opts , { frag , model , scene } ) )
2023-05-17 21:31:28 +02:00
}
2023-06-07 17:42:21 +02:00
if ( frag . q . query ) {
2023-06-20 14:59:25 +02:00
let srcScene = frag . q . scene // three/xrf/q.js initializes .scene
2023-06-07 17:42:21 +02:00
if ( ! srcScene || ! srcScene . visible ) return
console . log ( " └ inserting " + i + " (srcScene)" )
srcScene . position . set ( 0 , 0 , 0 )
srcScene . rotation . set ( 0 , 0 , 0 )
srcScene . traverse ( ( m ) => {
2023-07-04 17:15:23 +02:00
m . isSRC = true
2023-06-07 17:42:21 +02:00
if ( m . userData && ( m . userData . src || m . userData . href ) ) return ; //delete m.userData.src // prevent infinite recursion
xrf . eval . mesh ( m , { scene , recursive : true } )
} )
if ( srcScene . visible ) src . add ( srcScene )
}
2023-07-06 15:27:27 +02:00
xrf . frag . src . scale ( src , opts )
2023-05-18 12:32:57 +02:00
} , 10 )
2023-05-09 17:42:29 +02:00
}
2023-07-04 18:15:08 +02:00
const externalSRC = ( ) => {
2023-07-05 16:43:07 +02:00
fetch ( v . string , { method : 'HEAD' } )
. then ( ( res ) => {
console . log ( ` loading src ${ v . string } ` )
let mimetype = res . headers . get ( 'Content-type' )
console . log ( "src mimetype: " + mimetype )
return xrf . frag . src . type [ mimetype ] ? xrf . frag . src . type [ mimetype ] ( v . string , opts ) : xrf . frag . src . type . unknown ( v . string , opts )
} )
. finally ( ( ) => {
} )
. catch ( console . error )
2023-07-04 18:15:08 +02:00
}
2023-07-05 16:43:07 +02:00
if ( v . string [ 0 ] == "#" ) localSRC ( ) // current file
else externalSRC ( ) // external file
}
2023-07-06 16:54:12 +02:00
// scale embedded XR fragments https://xrfragment.org/#scaling%20of%20instanced%20objects
2023-07-06 15:27:27 +02:00
xrf . frag . src . scale = function ( scene , opts , url ) {
2023-07-05 16:43:07 +02:00
let { mesh , model , camera , renderer , THREE } = opts
2023-07-06 15:27:27 +02:00
let restrictToBoundingBox = mesh . geometry
2023-07-05 16:43:07 +02:00
if ( url ) {
let frag = xrfragment . URI . parse ( url )
console . log ( "parse url:" + url )
console . log ( "children:" + scene . children . length )
2023-07-06 15:27:27 +02:00
// scale URI XR Fragments (queries) inside src-value
2023-07-05 16:43:07 +02:00
for ( var i in frag ) {
xrf . eval . fragment ( i , Object . assign ( opts , { frag , model : { scene } , scene } ) )
}
2023-08-04 09:11:26 +02:00
//if( frag.q ) scene = frag.q.scene
2023-07-05 16:43:07 +02:00
//xrf.add( model.scene )
xrf . eval ( '#' , { scene } ) // execute the default projection '#' (if exist)
xrf . eval ( url , { scene } ) // and eval URI XR fragments
2023-08-04 09:11:26 +02:00
//if( !hash.match(/pos=/) ) // xrf.eval( '#pos=0,0,0' ) // set default position if not specified
2023-07-06 15:27:27 +02:00
}
2023-08-04 09:11:26 +02:00
if ( restrictToBoundingBox ) {
// spec 3 of https://xrfragment.org/#src
// spec 1 of https://xrfragment.org/#scaling%20of%20instanced%20objects
// normalize instanced objectsize to boundingbox
2023-07-05 16:43:07 +02:00
let bboxMesh = new THREE . Box3 ( ) . setFromObject ( mesh ) ;
let bboxScene = new THREE . Box3 ( ) . setFromObject ( scene ) ;
let maxScene = bboxScene . max . y > bboxScene . max . x ? bboxScene . max . y : bboxScene . max . x
2023-08-08 12:40:38 +02:00
let maxMesh = bboxMesh . max . y > bboxMesh . max . x ? bboxMesh . max . y : bboxMesh . max . x
2023-07-05 16:43:07 +02:00
let factor = maxMesh > maxScene ? maxScene / maxMesh : maxMesh / maxScene
scene . scale . multiplyScalar ( factor )
2023-08-04 09:11:26 +02:00
} else {
// spec 4 of https://xrfragment.org/#src
2023-08-08 12:40:38 +02:00
// spec 2 of https://xrfragment.org/#scaling%20of%20instanced%20objects
2023-08-04 09:11:26 +02:00
scene . scale . multiply ( mesh . scale )
2023-07-05 16:43:07 +02:00
}
2023-08-04 09:11:26 +02:00
scene . isXRF = model . scene . isSRC = true
2023-07-05 16:43:07 +02:00
mesh . add ( scene )
if ( ! opts . recursive && mesh . material ) mesh . material . visible = false // lets hide the preview object because deleting disables animations+nested objs
}
2023-07-06 16:54:12 +02:00
/ *
* replace the src - mesh with the contents of the src
* /
2023-07-05 16:43:07 +02:00
xrf . frag . src . type = { }
/ *
* mimetype : unknown
* /
xrf . frag . src . type [ 'unknown' ] = function ( url , opts ) {
return new Promise ( ( resolve , reject ) => {
reject ( ` ${ url } mimetype not supported (yet) ` )
} )
}
/ *
* mimetype : model / gltf + json
* /
xrf . frag . src . type [ 'model/gltf+json' ] = function ( url , opts ) {
return new Promise ( ( resolve , reject ) => {
let { urlObj , dir , file , hash , ext } = xrf . parseUrl ( url )
let loader
const Loader = xrf . loaders [ ext ]
if ( ! Loader ) throw 'xrfragment: no loader passed to xrfragment for extension .' + ext
if ( ! dir . match ( "://" ) ) { // force relative path
dir = dir [ 0 ] == '.' ? dir : ` . ${ dir } `
loader = new Loader ( ) . setPath ( dir )
} else loader = new Loader ( )
const onLoad = ( model ) => {
2023-07-06 15:27:27 +02:00
xrf . frag . src . scale ( model . scene , { ... opts , model , scene : model . scene } , url )
2023-07-05 16:43:07 +02:00
resolve ( model )
}
loader . load ( url , onLoad )
} )
2023-05-09 17:42:29 +02:00
}
2023-07-06 16:54:12 +02:00
/ *
* mimetype : image / png
* mimetype : image / jpg
* mimetype : image / gif
* /
xrf . frag . src . type [ 'image/png' ] = function ( url , opts ) {
let { mesh } = opts
let restrictToBoundingBox = mesh . geometry
const texture = new THREE . TextureLoader ( ) . load ( url ) ;
texture . colorSpace = THREE . SRGBColorSpace ;
//const geometry = new THREE.BoxGeometry();
const material = new THREE . MeshBasicMaterial ( {
map : texture ,
transparent : url . match ( /(png|gif)/ ) ? true : false ,
side : THREE . DoubleSide ,
color : 0xFFFFFF ,
opacity : 1
} ) ;
2023-08-04 09:11:26 +02:00
// stretch image by pinning uv-coordinates to corners
2023-07-06 16:54:12 +02:00
if ( mesh . geometry ) {
if ( mesh . geometry . attributes . uv ) { // buffergeometries
let uv = mesh . geometry . attributes . uv ;
// i u v
uv . setXY ( 0 , 0 , 0 )
uv . setXY ( 1 , 1 , 0 )
uv . setXY ( 2 , 0 , 1 )
uv . setXY ( 3 , 1 , 1 )
} else {
console . warn ( "xrfragment: uv's of ${url} might be off for non-buffer-geometries *TODO*" )
//if( geometry.faceVertexUvs ){
// *TODO* force uv's of dynamically created geometries (in threejs)
//}
}
}
mesh . material = material
}
xrf . frag . src . type [ 'image/gif' ] = xrf . frag . src . type [ 'image/png' ]
xrf . frag . src . type [ 'image/jpg' ] = xrf . frag . src . type [ 'image/png' ]
2023-05-09 17:42:29 +02:00
window . AFRAME . registerComponent ( 'xrf' , {
schema : {
} ,
init : function ( ) {
if ( ! AFRAME . XRF ) this . initXRFragments ( )
2023-06-27 09:43:10 +02:00
if ( typeof this . data == "string" ) {
2023-06-22 08:48:52 +02:00
if ( document . location . search || document . location . hash . length > 1 ) { // override url
this . data = ` ${ document . location . search . substr ( 1 ) } ${ document . location . hash } `
}
2023-05-17 21:31:28 +02:00
AFRAME . XRF . navigator . to ( this . data )
2023-05-12 22:06:21 +02:00
. then ( ( model ) => {
let gets = [ ... document . querySelectorAll ( '[xrf-get]' ) ]
2023-05-12 22:40:09 +02:00
gets . map ( ( g ) => g . emit ( 'update' ) )
2023-05-12 22:06:21 +02:00
} )
}
2023-05-09 17:42:29 +02:00
} ,
2023-05-12 22:06:21 +02:00
2023-05-09 17:42:29 +02:00
initXRFragments : function ( ) {
2023-05-17 21:31:28 +02:00
2023-05-18 12:32:57 +02:00
//window.addEventListener('popstate', clear )
//window.addEventListener('pushstate', clear )
2023-05-17 21:31:28 +02:00
2023-05-09 17:42:29 +02:00
// enable XR fragments
2023-05-17 21:31:28 +02:00
let aScene = document . querySelector ( 'a-scene' )
2023-06-27 09:43:10 +02:00
let XRF = AFRAME . XRF = xrf . init ( {
2023-05-09 17:42:29 +02:00
THREE ,
2023-06-27 09:43:10 +02:00
camera : aScene . camera ,
2023-05-09 17:42:29 +02:00
scene : aScene . object3D ,
renderer : aScene . renderer ,
debug : true ,
2023-07-04 17:15:23 +02:00
loaders : {
gltf : THREE . GLTFLoader , // which 3D assets (exts) to check for XR fragments?
glb : THREE . GLTFLoader
}
2023-05-09 17:42:29 +02:00
} )
2023-05-12 22:06:21 +02:00
if ( ! XRF . camera ) throw 'xrfragment: no camera detected, please declare <a-entity camera..> ABOVE entities with xrf-attributes'
2023-06-27 09:43:10 +02:00
// override the camera-related XR Fragments so the camera-rig is affected
2023-05-12 22:06:21 +02:00
let camOverride = ( xrf , v , opts ) => {
2023-06-27 09:43:10 +02:00
opts . camera = document . querySelector ( '[camera]' ) . object3D . parent
2023-05-12 22:06:21 +02:00
xrf ( v , opts )
2023-05-09 17:42:29 +02:00
}
2023-06-27 09:43:10 +02:00
2023-06-07 17:42:21 +02:00
xrf . pos = camOverride
2023-05-12 22:40:09 +02:00
2023-05-23 14:41:24 +02:00
// in order to set the rotation programmatically
// we need to disable look-controls
2023-06-07 17:42:21 +02:00
xrf . rot = ( xrf , v , opts ) => {
2023-08-15 18:27:26 +02:00
let { frag , renderer } = opts ;
if ( frag . q ) return // camera was not targeted for rotation
2023-05-23 14:41:24 +02:00
let look = document . querySelector ( '[look-controls]' )
if ( look ) look . removeAttribute ( "look-controls" )
camOverride ( xrf , v , opts )
2023-06-27 09:43:10 +02:00
// *TODO* make look-controls compatible, because simply
2023-06-07 17:42:21 +02:00
// adding the look-controls will revert to the old rotation (cached somehow?)
//setTimeout( () => look.setAttribute("look-controls",""), 100 )
2023-05-23 14:41:24 +02:00
}
// convert portal to a-entity so AFRAME
// raycaster can find & execute it
2023-06-27 09:43:10 +02:00
xrf . href = ( xrf , v , opts ) => {
camOverride ( xrf , v , opts )
2023-05-12 22:40:09 +02:00
let { mesh , camera } = opts ;
let el = document . createElement ( "a-entity" )
el . setAttribute ( "xrf-get" , mesh . name )
2023-05-22 18:58:05 +02:00
el . setAttribute ( "class" , "ray" )
2023-05-17 21:31:28 +02:00
el . addEventListener ( "click" , mesh . userData . XRF . href . exec )
2023-05-12 22:40:09 +02:00
$ ( 'a-scene' ) . appendChild ( el )
}
2023-05-18 12:32:57 +02:00
// cleanup xrf-get objects when resetting scene
2023-06-07 17:42:21 +02:00
xrf . reset = ( ( reset ) => ( ) => {
2023-05-22 15:03:23 +02:00
reset ( )
2023-05-18 12:32:57 +02:00
console . log ( "aframe reset" )
let els = [ ... document . querySelectorAll ( '[xrf-get]' ) ]
els . map ( ( el ) => document . querySelector ( 'a-scene' ) . removeChild ( el ) )
} ) ( XRF . reset )
2023-06-27 09:43:10 +02:00
2023-05-22 18:58:05 +02:00
// undo lookup-control shenanigans (which blocks updating camerarig position in VR)
aScene . addEventListener ( 'enter-vr' , ( ) => document . querySelector ( '[camera]' ) . object3D . parent . matrixAutoUpdate = true )
2023-05-12 22:06:21 +02:00
} ,
} )
2023-05-22 18:58:05 +02:00
window . AFRAME . registerComponent ( 'xrf-button' , {
schema : {
label : {
default : 'label'
} ,
width : {
default : 0.11
} ,
toggable : {
default : false
} ,
textSize : {
default : 0.66
} ,
color : {
default : '#111'
} ,
textColor : {
default : '#fff'
} ,
hicolor : {
default : '#555555'
} ,
action : {
default : ''
}
} ,
init : function ( ) {
var el = this . el ;
var labelEl = this . labelEl = document . createElement ( 'a-entity' ) ;
this . color = this . data . color
el . setAttribute ( 'geometry' , {
primitive : 'box' ,
width : this . data . width ,
height : 0.05 ,
depth : 0.005
} ) ;
el . setAttribute ( 'material' , {
color : this . color ,
transparent : true ,
opacity : 0.3
} ) ;
el . setAttribute ( 'pressable' , '' ) ;
labelEl . setAttribute ( 'position' , '0 0 0.01' ) ;
labelEl . setAttribute ( 'text' , {
value : this . data . label ,
color : this . data . textColor ,
align : 'center'
} ) ;
labelEl . setAttribute ( 'scale' , ` ${ this . data . textSize } ${ this . data . textSize } ${ this . data . textSize } ` ) ;
this . el . appendChild ( labelEl ) ;
this . bindMethods ( ) ;
this . el . addEventListener ( 'stateadded' , this . stateChanged ) ;
this . el . addEventListener ( 'stateremoved' , this . stateChanged ) ;
this . el . addEventListener ( 'pressedstarted' , this . onPressedStarted ) ;
this . el . addEventListener ( 'pressedended' , this . onPressedEnded ) ;
this . el . addEventListener ( 'mouseenter' , ( e ) => this . onMouseEnter ( e ) ) ;
this . el . addEventListener ( 'mouseleave' , ( e ) => this . onMouseLeave ( e ) ) ;
if ( this . data . action ) {
this . el . addEventListener ( 'click' , new Function ( this . data . action ) )
}
} ,
bindMethods : function ( ) {
this . stateChanged = this . stateChanged . bind ( this ) ;
this . onPressedStarted = this . onPressedStarted . bind ( this ) ;
this . onPressedEnded = this . onPressedEnded . bind ( this ) ;
} ,
update : function ( oldData ) {
if ( oldData . label !== this . data . label ) {
this . labelEl . setAttribute ( 'text' , 'value' , this . data . label ) ;
}
} ,
stateChanged : function ( ) {
var color = this . el . is ( 'pressed' ) ? this . data . hicolor : this . color ;
this . el . setAttribute ( 'material' , {
color : color
} ) ;
} ,
onMouseEnter : function ( ) {
this . el . setAttribute ( 'material' , { color : this . data . hicolor } ) ;
} ,
onMouseLeave : function ( ) {
this . el . setAttribute ( 'material' , { color : this . color } ) ;
} ,
onPressedStarted : function ( ) {
var el = this . el ;
el . setAttribute ( 'material' , {
color : this . data . hicolor
} ) ;
el . emit ( 'click' ) ;
if ( this . data . togabble ) {
if ( el . is ( 'pressed' ) ) {
el . removeState ( 'pressed' ) ;
} else {
el . addState ( 'pressed' ) ;
}
}
} ,
onPressedEnded : function ( ) {
if ( this . el . is ( 'pressed' ) ) {
return ;
}
this . el . setAttribute ( 'material' , {
color : this . color
} ) ;
}
} ) ;
2023-05-12 22:06:21 +02:00
window . AFRAME . registerComponent ( 'xrf-get' , {
2023-05-10 19:12:15 +02:00
schema : {
2023-05-12 22:06:21 +02:00
name : { type : 'string' } ,
2023-05-17 21:31:28 +02:00
clone : { type : 'boolean' , default : false }
2023-05-10 19:12:15 +02:00
} ,
init : function ( ) {
2023-05-12 22:06:21 +02:00
2023-05-10 19:12:15 +02:00
var el = this . el ;
2023-05-12 22:06:21 +02:00
var meshname = this . data . name || this . data ;
this . el . addEventListener ( 'update' , ( evt ) => {
2023-05-12 22:40:09 +02:00
let scene = AFRAME . XRF . scene
2023-05-12 22:06:21 +02:00
let mesh = scene . getObjectByName ( meshname ) ;
if ( ! mesh ) {
console . error ( "mesh with name '" + meshname + "' not found in model" )
2023-05-10 19:12:15 +02:00
return ;
}
2023-06-07 17:42:21 +02:00
// convert to worldcoordinates
mesh . getWorldPosition ( mesh . position )
mesh . getWorldScale ( mesh . scale )
mesh . getWorldQuaternion ( mesh . quaternion )
2023-05-17 21:31:28 +02:00
if ( ! this . data . clone ) mesh . parent . remove ( mesh )
2023-06-07 17:42:21 +02:00
mesh . isXRF = true // mark for deletion by xrf
2023-05-17 21:31:28 +02:00
this . el . setObject3D ( 'mesh' , mesh ) ;
if ( ! this . el . id ) this . el . setAttribute ( "id" , ` xrf- ${ mesh . name } ` )
2023-05-12 22:06:21 +02:00
} )
2023-05-10 19:12:15 +02:00
2023-05-22 18:58:05 +02:00
if ( this . el . className == "ray" ) this . el . emit ( "update" )
2023-05-12 22:40:09 +02:00
2023-05-10 19:12:15 +02:00
}
2023-05-12 22:06:21 +02:00
2023-05-10 19:12:15 +02:00
} ) ;
2023-05-12 22:06:21 +02:00
2023-05-22 18:58:05 +02:00
window . AFRAME . registerComponent ( 'xrf-wear' , {
schema : {
el : { type : "selector" } ,
position : { type : "vec3" } ,
rotation : { type : "vec3" }
} ,
init : function ( ) {
$ ( 'a-scene' ) . addEventListener ( 'enter-vr' , ( e ) => this . wear ( e ) )
$ ( 'a-scene' ) . addEventListener ( 'exit-vr' , ( e ) => this . unwear ( e ) )
} ,
wear : function ( ) {
if ( ! this . wearable ) {
let d = this . data
this . wearable = new THREE . Group ( )
this . el . object3D . children . map ( ( c ) => this . wearable . add ( c ) )
this . wearable . position . set ( d . position . x , d . position . y , d . position . z )
this . wearable . rotation . set ( d . rotation . x , d . rotation . y , d . rotation . z )
}
this . data . el . object3D . add ( this . wearable )
} ,
unwear : function ( ) {
this . data . el . remove ( this . wearable )
this . wearable . children . map ( ( c ) => this . el . object3D . add ( c ) )
delete this . wearable
}
} )