2023-05-04 16:24:54 +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 ) ) ;
} ;
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-04 16:24:54 +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-04 16:24:54 +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-09-15 19:43:11 +02:00
Frag _h [ "tag" ] = xrfragment _XRF . ASSET | xrfragment _XRF . T _STRING ;
2023-08-15 18:27:26 +02:00
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-04 16:24:54 +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-04 16:24:54 +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-04 16:24:54 +02:00
return true ;
}
2023-06-27 09:43:10 +02:00
var v = new xrfragment _XRF ( key , Frag _h [ key ] ) ;
2023-05-04 16:24:54 +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-04 16:24:54 +02:00
return false ;
}
2023-06-27 09:43:10 +02:00
store [ key ] = v ;
2023-05-04 16:24:54 +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-04 16:24:54 +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-09-14 10:20:10 +02:00
v . noXRF = true ;
store [ key ] = v ;
2023-05-04 16:24:54 +02:00
}
return true ;
} ;
var xrfragment _Query = $hx _exports [ "xrfragment" ] [ "Query" ] = function ( str ) {
this . isNumber = new EReg ( "^[0-9\\.]+$" , "" ) ;
2023-06-22 08:48:52 +02:00
this . isRoot = new EReg ( "^[-]?/" , "" ) ;
2023-05-04 16:24:54 +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 ;
}
, get : function ( ) {
return this . q ;
}
2023-06-27 09:43:10 +02:00
, parse : function ( str ) {
2023-05-04 16:24:54 +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 = "=" ;
}
2023-09-15 19:43:11 +02:00
var rule = { } ;
if ( _gthis . isNumber . match ( v ) ) {
rule [ oper ] = parseFloat ( v ) ;
2023-05-04 16:24:54 +02:00
} else {
2023-09-15 19:43:11 +02:00
rule [ oper ] = v ;
2023-05-04 16:24:54 +02:00
}
2023-09-15 19:43:11 +02:00
filter [ "rules" ] . push ( rule ) ;
q [ k ] = filter ;
2023-05-04 16:24:54 +02:00
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-04 16:24:54 +02:00
}
} ;
var _g = 0 ;
var _g1 = token . length ;
while ( _g < _g1 ) {
var i = _g ++ ;
2023-09-15 19:43:11 +02:00
process ( token [ i ] ) ;
2023-05-04 16:24:54 +02:00
}
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-04 16:24:54 +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 ;
2023-05-05 13:26:17 +02:00
xrfragment _XRF . NAVIGATOR = 32 ;
2023-08-15 18:27:26 +02:00
xrfragment _XRF . METADATA = 64 ;
2023-05-04 16:24:54 +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 )
2023-10-13 11:45:17 +02:00
return xrf
2023-06-07 17:42:21 +02:00
}
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-09-15 19:43:11 +02:00
xrf . hasTag = ( tag , tags ) => String ( tags ) . match ( new RegExp ( ` (^| ) ${ tag } ( | $ ) ` , ` g ` ) )
2023-06-09 16:40:08 +02:00
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 )
2023-09-15 19:43:11 +02:00
delete opts . XRF
2023-06-07 17:42:21 +02:00
if ( ! opts . promise . halted ) resolve ( )
2023-09-15 19:43:11 +02:00
delete opts . promise
2023-06-07 17:42:21 +02:00
} )
}
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-09-15 19:43:11 +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
//
// it collects metadata-keys ('foo' e.g.), names and tags across 3D scene-nodes (.userData.foo e.g.)
let XRWG = xrf . XRWG = [ ]
XRWG . word = ( key ) => XRWG . find ( ( w ) => w . word == word )
XRWG . cleankey = ( word ) => String ( word ) . replace ( /[^0-9\.a-zA-Z_]/g , '' )
. toLowerCase ( )
. replace ( /.*:\/\// , '' )
XRWG . get = ( v , k ) => XRWG . find ( ( x ) => x [ k || 'word' ] == v )
XRWG . match = ( str , types , level ) => {
level = level || 1000
types = types || [ ]
let res = XRWG . filter ( ( n ) => {
types . map ( ( type ) => n [ type ] ? n = false : false )
return n
} )
str = str . toLowerCase ( )
if ( level < 10 ) res = res . filter ( ( n ) => n . key == str )
if ( level < 20 ) res = res . filter ( ( n ) => n . word == str || n . key == str )
if ( level < 30 ) res = res . filter ( ( n ) => n . word . match ( str ) || n . key == str )
if ( level < 40 ) res = res . filter ( ( n ) => n . word . match ( str ) || n . key == str || String ( n . value || '' ) . match ( str ) )
if ( level < 1001 ) res = res . filter ( ( n ) => n . word . match ( str ) != null || n . key . match ( str ) != null || String ( n . value || '' ) . match ( str ) != null )
return res
}
XRWG . generate = ( opts ) => {
let { scene , model } = opts
XRWG . slice ( 0 , 0 ) // empty
// collect words from 3d nodes
let add = ( key , spatialNode , type ) => {
if ( ! key || key . match ( /(^#$|name)/ ) ) return
let node = XRWG . get ( XRWG . cleankey ( key ) )
if ( node ) {
node . nodes . push ( spatialNode )
} else {
2023-10-11 20:12:48 +02:00
node = { word : XRWG . cleankey ( key ) , key , nodes : [ spatialNode ] }
2023-09-15 19:43:11 +02:00
if ( spatialNode . userData [ key ] ) node . value = spatialNode . userData [ key ]
node [ type ] = true
xrf . emit ( 'XRWG' , node )
XRWG . push ( node )
}
}
scene . traverse ( ( o ) => {
add ( ` # ${ o . name } ` , o , 'name' )
for ( let k in o . userData ) {
if ( k == 'tag' ) {
let tagArr = o . userData . tag . split ( " " )
. map ( ( t ) => t . trim ( ) )
. filter ( ( t ) => t )
. map ( ( w ) => add ( w , o , 'tag' ) )
} else if ( k . match ( /^(href|src)$/ ) ) add ( o . userData [ k ] , o , k )
else if ( k [ 0 ] == '#' ) add ( k , o , 'pv' )
else add ( k , o , 'query' )
}
} )
// sort by n
XRWG . sort ( ( a , b ) => a . nodes . length - b . nodes . length )
XRWG = XRWG . reverse ( ) // the cleankey/get functions e.g. will persist
console . dir ( XRWG )
}
// the hashbus (QueryString eventBus) is mentioned in the spec
//
// it allows metadata-keys ('foo' e.g.) of 3D scene-nodes (.userData.foo e.g.) to
// react by executing code
let pub = function ( url , model , flags ) { // evaluate fragments in url
if ( ! url ) return
if ( ! url . match ( /#/ ) ) url = ` # ${ url } `
model = model || xrf . model
let { THREE , camera } = xrf
let frag = xrf . URI . parse ( url , flags != undefined ? flags : xrf . XRF . NAVIGATOR )
let opts = { frag , mesh : xrf . camera , model , camera : xrf . camera , scene : xrf . scene , renderer : xrf . renderer , THREE : xrf . THREE , hashbus : xrf . hashbus }
xrf . emit ( 'hashbus' , opts )
. then ( ( ) => {
for ( let k in frag ) {
pub . fragment ( k , opts )
}
} )
return frag
}
pub . 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 : model . scene , renderer : xrf . renderer , THREE : xrf . THREE , hashbus : xrf . hashbus }
mesh . userData . XRF = frag // allow fragment impl to access XRF obj already
xrf . emit ( 'mesh' , opts )
. then ( ( ) => pub . fragment ( k , opts ) )
}
}
}
pub . fragment = ( k , opts ) => { // evaluate one fragment
let frag = opts . frag [ k ] ;
2023-10-12 17:04:46 +02:00
2023-09-15 19:43:11 +02:00
// call native function (xrf/env.js e.g.), or pass it to user decorator
xrf . emit ( k , opts )
. then ( ( ) => {
let func = xrf . frag [ k ] || function ( ) { }
if ( xrf [ k ] ) xrf [ k ] ( func , frag , opts )
else func ( frag , opts )
} )
}
xrf . hashbus = { pub }
xrf . frag = { }
2023-08-04 09:11:26 +02:00
xrf . model = { }
xrf . init = ( ( init ) => function ( opts ) {
2023-09-21 13:05:30 +02:00
let scene = new opts . THREE . Group ( )
opts . scene . add ( scene )
opts . scene = scene
2023-08-04 09:11:26 +02:00
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
2023-09-15 19:43:11 +02:00
model . scene . traverse ( ( mesh ) => xrf . hashbus . pub . mesh ( mesh , model ) )
2023-08-04 09:11:26 +02:00
// add animations
model . clock = new xrf . THREE . Clock ( ) ;
model . mixer = new xrf . THREE . AnimationMixer ( model . scene )
2023-10-11 13:46:38 +02:00
model . animations . map ( ( anim ) => {
anim . action = model . mixer . clipAction ( anim )
2023-10-12 17:04:46 +02:00
//anim.action.setLoop(0)
2023-10-11 13:46:38 +02:00
anim . action . play ( )
} )
2023-09-15 19:43:11 +02:00
let tmp = new xrf . THREE . Vector3 ( )
2023-08-04 09:11:26 +02:00
model . render = function ( ) {
2023-10-11 20:12:48 +02:00
let time = model . clock . getDelta ( )
model . mixer . update ( time )
2023-08-04 09:11:26 +02:00
2023-09-15 19:43:11 +02:00
// update focusline
2023-09-21 13:05:30 +02:00
xrf . focusLine . material . color . r = ( 1.0 + Math . sin ( model . clock . getElapsedTime ( ) * 10 ) ) / 2
2023-09-15 19:43:11 +02:00
xrf . focusLine . material . dashSize = 0.2 + 0.02 * Math . sin ( model . clock . getElapsedTime ( ) )
xrf . focusLine . material . gapSize = 0.1 + 0.02 * Math . sin ( model . clock . getElapsedTime ( ) * 3 )
2023-10-11 20:12:48 +02:00
xrf . focusLine . material . opacity = ( 0.25 + 0.15 * Math . sin ( model . clock . getElapsedTime ( ) * 3 ) ) * xrf . focusLine . opacity ;
2023-10-12 17:04:46 +02:00
if ( xrf . focusLine . opacity > 0.0 ) xrf . focusLine . opacity -= time * 0.2
2023-10-11 20:12:48 +02:00
if ( xrf . focusLine . opacity < 0.0 ) xrf . focusLine . opacity = 0
2023-08-04 09:11:26 +02:00
}
2023-10-12 17:04:46 +02:00
2023-08-04 09:11:26 +02:00
}
2023-09-15 19:43:11 +02:00
xrf . getLastModel = ( ) => xrf . model . last
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 )
2023-09-21 13:05:30 +02:00
xrf . add ( xrf . interactive )
xrf . layers = 0
2023-08-04 09:11:26 +02:00
}
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-04 16:24:54 +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 } ;
2023-10-14 20:10:06 +02:00
let object = { selected : false }
2023-05-12 22:06:21 +02:00
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 ;
2023-10-11 13:46:38 +02:00
scope . objects = [ ]
2023-05-12 22:06:21 +02:00
const raycaster = new Raycaster ( ) ;
const tempMatrix = new Matrix4 ( ) ;
// 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 ) ;
2023-10-11 13:46:38 +02:00
const intersects = raycaster . intersectObjects ( scope . objects , false ) ;
2023-05-12 22:06:21 +02:00
if ( intersects . length > 0 ) {
const intersection = intersects [ 0 ] ;
2023-10-14 20:10:06 +02:00
object = intersection . object ;
2023-05-12 22:06:21 +02:00
const uv = intersection . uv ;
_event . type = event . type ;
_event . data . set ( uv . x , 1 - uv . y ) ;
object . dispatchEvent ( _event ) ;
2023-10-14 20:10:06 +02:00
} else {
if ( object . selected ) {
_event . type = 'mouseleave'
object . dispatchEvent ( _event )
}
}
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 ) ;
2023-10-11 13:46:38 +02:00
const intersections = raycaster . intersectObjects ( scope . objects , false ) ;
2023-05-12 22:06:21 +02:00
if ( intersections . length > 0 ) {
const intersection = intersections [ 0 ] ;
2023-10-14 20:10:06 +02:00
object = intersection . object ;
2023-05-12 22:06:21 +02:00
const uv = intersection . uv ;
_event . type = events [ event . type ] ;
_event . data . set ( uv . x , 1 - uv . y ) ;
object . dispatchEvent ( _event ) ;
2023-10-14 20:10:06 +02:00
} else {
if ( object . selected ) {
_event . type = 'mouseleave'
object . dispatchEvent ( _event )
}
}
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 ) ;
}
2023-10-11 13:46:38 +02:00
add ( obj ) {
Group . prototype . add . call ( this , obj )
this . objects = ( [ ] ) . concat ( this . children )
}
2023-05-12 22:06:21 +02:00
}
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-09-15 19:43:11 +02:00
let hashbus = xrf . hashbus
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-10-15 13:35:54 +02:00
if ( ! file || ( ! data && xrf . model . file == file ) ) { // we're already loaded
2023-09-15 19:43:11 +02:00
hashbus . pub ( url , xrf . model , flags ) // and eval local URI XR fragments
2023-09-14 10:20:10 +02:00
xrf . navigator . updateHash ( hash )
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-09-15 19:43:11 +02:00
// spec: 1. generate the XRWG
xrf . XRWG . generate ( { model , scene : model . scene } )
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 } )
2023-10-12 17:04:46 +02:00
// spec: 2. init metadata
let frag = hashbus . pub ( url , model ) // and eval URI XR fragments
// spec: predefined view(s) from URL (https://xrfragment.org/#predefined_view)
setTimeout ( ( ) => { // give external objects some slack
xrf . frag . updatePredefinedView ( { model , scene : model . scene , frag } )
} , 2000 )
2023-08-15 18:27:26 +02:00
xrf . add ( model . scene )
2023-09-14 10:20:10 +02:00
xrf . navigator . updateHash ( hash )
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-10-12 17:04:46 +02:00
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-10-12 17:04:46 +02:00
window . addEventListener ( 'hashchange' , function ( e ) {
xrf . emit ( 'hash' , { hash : document . location . hash } )
} )
2023-09-15 19:43:11 +02:00
// this allows selectionlines to be updated according to the camera (renderloop)
xrf . focusLine = new xrf . THREE . Group ( )
xrf . focusLine . material = new xrf . THREE . LineDashedMaterial ( { color : 0xFF00FF , linewidth : 3 , scale : 1 , dashSize : 0.2 , gapSize : 0.1 , opacity : 0.3 , transparent : true } )
xrf . focusLine . isXRF = true
xrf . focusLine . position . set ( 0 , 0 , - 0.5 ) ;
xrf . focusLine . points = [ ]
xrf . focusLine . lines = [ ]
xrf . camera . add ( xrf . focusLine )
2023-05-17 21:31:28 +02:00
xrf . navigator . init . inited = true
2023-05-12 22:06:21 +02:00
}
2023-09-15 19:43:11 +02:00
xrf . navigator . updateHash = ( hash , opts ) => {
if ( hash . replace ( /^#/ , '' ) == document . location . hash . substr ( 1 ) || 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
2023-09-15 19:43:11 +02:00
xrf . emit ( 'hash' , { ... opts , hash : ` # ${ hash } ` } )
2023-06-22 08:48:52 +02:00
}
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-09-15 19:43:11 +02:00
xrf . emit ( 'pushState' , { file , hash } )
2023-05-12 22:06:21 +02:00
}
2023-09-15 19:43:11 +02:00
xrf . addEventListener ( 'bg' , ( opts ) => {
2023-08-24 13:36:41 +02:00
let { frag , mesh , model , camera , scene , renderer , THREE } = opts
2023-09-15 19:43:11 +02:00
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 )
2023-09-14 10:20:10 +02:00
} )
2023-09-15 19:43:11 +02:00
xrf . addEventListener ( 'env' , ( opts ) => {
2023-09-14 10:20:10 +02:00
let { frag , mesh , model , camera , scene , renderer , THREE } = opts
if ( frag . env && ! scene . environment ) {
2023-09-15 19:43:11 +02:00
let env = scene . getObjectByName ( frag . env . string )
if ( ! env ) env = xrf . scene . getObjectByName ( frag . env . string ) // repurpose from parent scene
if ( ! env ) return console . warn ( "xrf.env " + frag . env . string + " not found" )
2023-09-14 10:20:10 +02:00
env . material . map . mapping = THREE . EquirectangularReflectionMapping ;
scene . environment = env . material . map
//scene.texture = env.material.map
renderer . toneMapping = THREE . ACESFilmicToneMapping ;
renderer . toneMappingExposure = 2 ;
console . log ( ` └ applied image ' ${ frag . env . string } ' as environment map ` )
}
2023-09-15 19:43:11 +02:00
2023-09-14 10:20:10 +02:00
} )
2023-09-15 19:43:11 +02:00
xrf . addEventListener ( 'fog' , ( opts ) => {
2023-09-14 10:20:10 +02:00
let { frag , mesh , model , camera , scene , renderer , THREE } = opts
2023-09-15 19:43:11 +02:00
let v = frag . fog
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 ) ;
2023-09-14 10:20:10 +02:00
} )
xrf . macros = { }
2023-08-24 13:36:41 +02:00
2023-09-15 19:43:11 +02:00
xrf . addEventListener ( 'mesh' , ( opts ) => {
let { frag , mesh , model , camera , scene , renderer , THREE , hashbus } = opts
2023-09-14 10:20:10 +02:00
for ( let k in frag ) {
let id = mesh . name + "_" + k
let fragment = frag [ k ]
if ( k . match ( /^!/ ) ) {
if ( mesh . material ) mesh . material = mesh . material . clone ( )
if ( mesh . isSRC || scene . isSRC ) return ; // dont allow recursion for now
if ( xrf . macros [ k ] ) return // already initialized
console . log ( "└ initing xrmacro: " + k )
xrf . macros [ k ] = fragment
fragment . args = fragment . string . split ( "|" )
fragment . trigger = ( e ) => {
xrf
. emit ( 'macro' , { click : true , mesh , xrf : frag } ) // let all listeners agree
. then ( ( ) => {
rrFrag = fragment . args [ xrf . roundrobin ( fragment , model ) ]
console . log ( "└ xrmacro: " + rrFrag )
if ( xrf . macros [ rrFrag ] ) {
xrf . macros [ rrFrag ] . trigger ( )
} else {
2023-09-15 19:43:11 +02:00
xrf . navigator . to ( rrFrag , null , 0 )
2023-09-14 10:20:10 +02:00
}
} )
}
let selected = ( state ) => ( ) => {
if ( mesh . selected == state ) return // nothing changed
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
}
// update mouse cursor
if ( ! renderer . domElement . lastCursor )
renderer . domElement . lastCursor = renderer . domElement . style . cursor
renderer . domElement . style . cursor = state ? 'pointer' : renderer . domElement . lastCursor
xrf
. emit ( 'macro' , { selected : state , mesh , xrf : frag } ) // let all listeners agree
. then ( ( ) => mesh . selected = state )
}
mesh . addEventListener ( 'click' , fragment . trigger )
mesh . addEventListener ( 'mousemove' , selected ( true ) )
mesh . addEventListener ( 'nocollide' , selected ( false ) )
// lazy add mesh to interactive group (because we're inside a recursive traverse)
setTimeout ( ( mesh ) => {
const world = {
pos : new THREE . Vector3 ( ) ,
scale : new THREE . Vector3 ( ) ,
quat : new THREE . Quaternion ( )
}
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 ) ;
xrf . interactive . add ( mesh )
2023-10-11 13:46:38 +02:00
xrf . emit ( 'interactionReady' , { mesh , xrf : fragment , clickHandler : fragment . trigger } )
2023-09-14 10:20:10 +02:00
} , 10 , mesh )
}
}
} )
2023-09-15 19:43:11 +02:00
xrf . addEventListener ( 'mov' , ( opts ) => {
2023-09-14 10:20:10 +02:00
let { frag , mesh , model , camera , scene , renderer , THREE } = opts
if ( frag . mov && frag . q ) {
// let wait for the queried objects (as we're inside promise which traverses the graph)
setTimeout ( ( v ) => {
frag . q . getObjects ( ) . map ( ( o ) => {
o . position . add ( new THREE . Vector3 ( v . x , v . y , v . z ) )
} )
} , 10 , frag . mov )
}
} )
2023-09-15 19:43:11 +02:00
xrf . addEventListener ( 'pos' , ( opts ) => {
2023-09-14 10:20:10 +02:00
let { frag , mesh , model , camera , scene , renderer , THREE } = opts
if ( frag . pos && frag . q ) {
// 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
} )
}
} )
2023-09-15 19:43:11 +02:00
xrf . addEventListener ( 'rot' , ( opts ) => {
2023-09-14 10:20:10 +02:00
let { frag , mesh , model , camera , scene , renderer , THREE } = opts
if ( frag . rot && frag . q ) {
// 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
)
} )
}
}
} )
2023-09-15 19:43:11 +02:00
xrf . addEventListener ( 'scale' , ( opts ) => {
2023-09-14 10:20:10 +02:00
let { frag , mesh , model , camera , scene , renderer , THREE } = opts
if ( frag . scale && frag . q ) {
// apply roundrobin (if any)
if ( v . args ) v = v . args [ xrf . roundrobin ( v , model ) ]
frag . q . getObjects ( ) . map ( ( o ) => {
o . scale . x = v . x
o . scale . y = v . y
o . scale . z = v . z
} )
}
} )
2023-09-15 19:43:11 +02:00
xrf . addEventListener ( 'show' , ( opts ) => {
2023-09-14 10:20:10 +02:00
let { frag , mesh , model , camera , scene , renderer , THREE } = opts
if ( frag . show && frag . q ) {
let show = frag . show
// apply roundrobin (if any)
if ( show . args ) v = show . args [ xrf . roundrobin ( show , model ) ]
else v = show . int
// let wait for the queried objects (as we're inside promise which traverses the graph)
setTimeout ( ( v ) => {
frag . q . getObjects ( ) . map ( ( o ) => {
o . visible = v . int == 1 ;
} )
} , 20 , v )
}
} )
2023-08-24 13:36:41 +02:00
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 ( ) ;
}
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-09-21 13:05:30 +02:00
let { frag , mesh , model , camera , scene , renderer , THREE } = opts
2023-05-05 18:53:42 +02:00
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-05 18:53:42 +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 ( ) {
2023-09-21 13:05:30 +02:00
vec3 direction = normalize ( vWorldPosition - cameraPosition ) ;
2023-05-22 14:10:44 +02:00
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 ;
2023-10-12 17:04:46 +02:00
vec4 grayscale _color = selected ? color : vec4 ( vec3 ( luminance ) + vec3 ( 0.33 ) , color . a ) ;
2023-05-22 14:10:44 +02:00
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-10-11 13:46:38 +02:00
2023-06-22 13:59:17 +02:00
let isLocal = v . string [ 0 ] == '#'
2023-09-15 19:43:11 +02:00
let lastPos = ` pos= ${ camera . position . x . toFixed ( 2 ) } , ${ camera . position . y . toFixed ( 2 ) } , ${ camera . position . z . toFixed ( 2 ) } `
2023-09-21 13:05:30 +02:00
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-08-31 12:40:41 +02:00
const flags = v . string [ 0 ] == '#' ? xrf . XRF . PV _OVERRIDE : undefined
2023-09-21 13:05:30 +02:00
let toFrag = xrf . URI . parse ( v . string , xrf . XRF . NAVIGATOR | xrf . XRF . PV _OVERRIDE | xrf . XRF . METADATA )
2023-09-14 10:20:10 +02:00
// always keep a trail of last positions before we navigate
2023-10-11 20:12:48 +02:00
if ( ! document . location . hash . match ( lastPos ) ) xrf . navigator . to ( ` # ${ lastPos } ` )
2023-09-15 19:43:11 +02:00
xrf . navigator . to ( v . string ) // let's surf to HREF!
2023-06-08 17:45:21 +02:00
} )
2023-10-11 13:46:38 +02:00
. catch ( console . error )
2023-05-22 14:10:44 +02:00
}
2023-10-14 20:10:06 +02:00
let selected = mesh . userData . XRF . href . selected = ( state ) => ( ) => {
2023-05-22 14:10:44 +02:00
if ( mesh . selected == state ) return // nothing changed
2023-10-12 17:04:46 +02:00
xrf . interactive . objects . map ( ( o ) => {
let newState = o . name == mesh . name ? state : false
if ( o . material ) {
if ( o . material . uniforms ) o . material . uniforms . selected . value = newState
if ( o . material . emissive ) o . material . emissive . r = o . material . emissive . g = o . material . emissive . b = newState ? 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-10-12 17:04:46 +02:00
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 ) )
2023-10-14 20:10:06 +02:00
mesh . addEventListener ( 'mouseleave' , 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-10-11 13:46:38 +02:00
xrf . emit ( 'interactionReady' , { mesh , xrf : v , clickHandler : mesh . userData . XRF . href . exec } )
} , 0 , mesh )
2023-05-05 18:53:42 +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 . pos = function ( v , opts ) {
let { frag , mesh , model , camera , scene , renderer , THREE } = opts
2023-09-14 10:20:10 +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-10-12 17:04:46 +02:00
let { frag , scene , model , renderer } = 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
// Selection of Interest if predefined_view matches object name
2023-10-12 17:04:46 +02:00
if ( mesh . visible && mesh . material ) {
2023-09-15 19:43:11 +02:00
xrf . emit ( 'focus' , { ... opts , frag } )
2023-06-22 08:48:52 +02:00
. then ( ( ) => {
2023-09-15 19:43:11 +02:00
const color = new THREE . Color ( ) ;
const colors = [ ]
let from = new THREE . Vector3 ( )
let getCenterPoint = ( mesh ) => {
var geometry = mesh . geometry ;
geometry . computeBoundingBox ( ) ;
var center = new THREE . Vector3 ( ) ;
geometry . boundingBox . getCenter ( center ) ;
mesh . localToWorld ( center ) ;
return center ;
}
xrf . camera . updateMatrixWorld ( true ) ; // always keeps me diving into the docs :]
xrf . camera . getWorldPosition ( from )
from . y -= 0.5 // originate from the heart chakra! :p
const points = [ from , getCenterPoint ( mesh ) ]
const geometry = new THREE . BufferGeometry ( ) . setFromPoints ( points ) ;
let line = new THREE . Line ( geometry , xrf . focusLine . material ) ;
line . isXRF = true
line . computeLineDistances ( ) ;
xrf . focusLine . lines . push ( line )
xrf . focusLine . points . push ( from )
2023-10-11 20:12:48 +02:00
xrf . focusLine . opacity = 1
2023-09-15 19:43:11 +02:00
scene . add ( line )
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-09-15 19:43:11 +02:00
//// spec: https://xrfragment.org/#predefined_view
//const predefinedView = (frag,scene,mesh) => {
// 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 )
// 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 }
// if( frag[k].is( xrf.XRF.PV_EXECUTE ) && scene.XRF_PV_ORIGIN != k ){ // cyclic detection
// highlightInScene(frag[k],scene) // recurse predefined views
// }
// }
// })
// }
//}
const highlightInScene = ( v , scene ) => {
2023-06-22 08:48:52 +02:00
if ( ! scene ) return
2023-09-15 19:43:11 +02:00
let remove = [ ]
let id = v . string || v . fragment
if ( id == '#' ) return
let match = xrf . XRWG . match ( id )
console . dir ( { id , match , XRWG : xrf . XRWG } )
// erase previous lines
xrf . focusLine . lines . map ( ( line ) => scene . remove ( line ) )
xrf . focusLine . points = [ ]
xrf . focusLine . lines = [ ]
scene . traverse ( ( n ) => n . selection ? remove . push ( n ) : false )
remove . map ( ( n ) => scene . remove ( n . selection ) )
// create new selections
match . map ( ( w ) => {
2023-10-12 17:04:46 +02:00
if ( w . key == ` # ${ id } ` ) {
if ( w . value && w . value [ 0 ] == '#' ) {
// if value is alias, execute fragment value
xrf . hashbus . pub ( w . value , xrf . model , xrf . XRF . METADATA | xrf . XRF . PV _OVERRIDE | xrf . XRF . NAVIGATOR )
}
2023-10-11 20:12:48 +02:00
}
2023-10-12 17:04:46 +02:00
w . nodes . map ( ( mesh ) => selectionOfInterest ( v , scene , mesh ) )
2023-06-22 08:48:52 +02:00
} )
}
2023-05-22 17:18:15 +02:00
2023-09-14 10:20:10 +02:00
// if this query was triggered by an src-value, lets filter it
const isSRC = opts . embedded && opts . embedded . fragment == 'src'
if ( isSRC ) { // spec : https://xrfragment.org/#src
console . log ( "filtering predefined view of src" )
console . dir ( frag )
} else {
2023-10-12 17:04:46 +02:00
console . log ( "updatePredefinedView" )
console . dir ( frag )
2023-09-14 10:20:10 +02:00
for ( let i in frag ) {
let v = frag [ i ]
if ( v . is ( xrf . XRF . PV _EXECUTE ) ) {
scene . XRF _PV _ORIGIN = v . string
// wait for nested instances to arrive at the scene ?
2023-09-15 19:43:11 +02:00
highlightInScene ( v , scene )
2023-09-14 10:20:10 +02:00
}
}
2023-05-22 17:18:15 +02:00
}
2023-05-04 21:28:12 +02:00
}
2023-06-08 17:45:21 +02:00
2023-09-15 19:43:11 +02:00
// react to enduser typing url
xrf . addEventListener ( 'hash' , ( opts ) => {
2023-09-14 10:20:10 +02:00
let frag = xrf . URI . parse ( opts . hash , xrf . XRF . NAVIGATOR | xrf . XRF . PV _OVERRIDE | xrf . XRF . METADATA )
xrf . frag . updatePredefinedView ( { frag , scene : xrf . scene } )
} )
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-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 ] ;
2023-09-15 19:43:11 +02:00
if ( qobj . tag && o . userData . tag && xrf . hasTag ( name , o . userData . tag ) ) objs . push ( o )
2023-08-15 18:27:26 +02:00
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-09-14 10:20:10 +02:00
xrf . frag . q . filter ( scene , frag ) // spec : https://xrfragment.org/#queries
}
2023-08-15 18:27:26 +02:00
2023-09-14 10:20:10 +02:00
xrf . frag . q . filter = function ( scene , frag ) {
2023-06-27 09:43:10 +02:00
// spec: https://xrfragment.org/#queries
2023-09-15 19:43:11 +02:00
let q = frag . q . query
2023-09-14 10:20:10 +02:00
scene . traverse ( ( mesh ) => {
for ( let i in q ) {
let isMeshId = q [ i ] . id != undefined
2023-09-15 19:43:11 +02:00
let isMeshProperty = q [ i ] . rules != undefined && q [ i ] . rules . length && ! isMeshId
2023-09-14 10:20:10 +02:00
if ( q [ i ] . root && mesh . isSRC ) continue ; // ignore nested object for root-items (queryseletor '/foo' e.g.)
2023-09-15 19:43:11 +02:00
if ( isMeshId &&
( i == mesh . name || xrf . hasTag ( i , mesh . userData . tag ) ) ) mesh . visible = q [ i ] . id
if ( isMeshProperty && mesh . userData [ i ] ) mesh . visible = ( new xrf . Query ( frag . q . string ) ) . testProperty ( i , mesh . userData [ i ] )
2023-09-14 10:20:10 +02:00
}
} )
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
2023-09-14 10:20:10 +02:00
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 ( )
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-10-12 17:04:46 +02:00
let { mesh , model , camera , scene , renderer , THREE , hashbus , frag } = opts
2023-07-04 18:15:08 +02:00
console . log ( " └ instancing src" )
2023-09-21 13:05:30 +02:00
let src ;
let url = v . string
2023-10-12 17:04:46 +02:00
let vfrag = xrfragment . URI . parse ( url )
2023-09-21 13:05:30 +02:00
opts . isPlane = mesh . geometry && mesh . geometry . attributes . uv && mesh . geometry . attributes . uv . count == 4
const addScene = ( scene , url , frag ) => {
src = xrf . frag . src . filterScene ( scene , { ... opts , frag } )
xrf . frag . src . scale ( src , opts , url )
xrf . frag . src . eval ( src , opts , url )
2023-10-12 17:04:46 +02:00
enableSourcePortation ( src )
2023-09-21 13:05:30 +02:00
mesh . add ( src )
2023-10-11 20:12:48 +02:00
mesh . traverse ( ( n ) => n . isSRC = n . isXRF = true )
2023-09-21 13:05:30 +02:00
if ( mesh . material ) mesh . material . visible = false
2023-05-05 18:53:42 +02:00
}
2023-07-04 18:15:08 +02:00
2023-10-12 17:04:46 +02:00
const enableSourcePortation = ( src ) => {
if ( vfrag . href || v . string [ 0 ] == '#' ) return
let scale = new THREE . Vector3 ( )
let size = new THREE . Vector3 ( )
mesh . getWorldScale ( scale )
new THREE . Box3 ( ) . setFromObject ( src ) . getSize ( size )
const geo = new THREE . SphereGeometry ( Math . max ( size . x , size . y , size . z ) / scale . x , 10 , 10 )
const mat = new THREE . MeshBasicMaterial ( )
mat . transparent = true
mat . roughness = 0.05
mat . metalness = 1
mat . opacity = 0
const cube = new THREE . Mesh ( geo , mat )
console . log ( "todo: sourceportate" )
//mesh.add(cube)
}
2023-09-21 13:05:30 +02:00
const externalSRC = ( url , frag , src ) => {
fetch ( url , { method : 'HEAD' } )
2023-07-05 16:43:07 +02:00
. then ( ( res ) => {
2023-09-21 13:05:30 +02:00
console . log ( ` loading src ${ url } ` )
2023-07-05 16:43:07 +02:00
let mimetype = res . headers . get ( 'Content-type' )
2023-09-21 13:05:30 +02:00
if ( url . replace ( /#.*/ , '' ) . match ( /\.(gltf|glb)$/ ) ) mimetype = 'gltf'
//if( url.match(/\.(fbx|stl|obj)$/) ) mimetype =
2023-07-05 16:43:07 +02:00
console . log ( "src mimetype: " + mimetype )
2023-09-21 13:05:30 +02:00
opts = { ... opts , src , frag }
return xrf . frag . src . type [ mimetype ] ? xrf . frag . src . type [ mimetype ] ( url , opts ) : xrf . frag . src . type . unknown ( url , opts )
2023-07-05 16:43:07 +02:00
} )
2023-09-21 13:05:30 +02:00
. then ( ( model ) => {
if ( model && model . scene ) addScene ( model . scene , url , frag )
2023-07-05 16:43:07 +02:00
} )
2023-09-21 13:05:30 +02:00
. finally ( ( ) => { } )
2023-07-05 16:43:07 +02:00
. catch ( console . error )
2023-07-04 18:15:08 +02:00
}
2023-10-12 17:04:46 +02:00
if ( url [ 0 ] == "#" ) addScene ( scene , url , vfrag ) // current file
else externalSRC ( url , vfrag ) // external file
2023-07-05 16:43:07 +02:00
}
2023-09-15 19:43:11 +02:00
xrf . frag . src . eval = function ( scene , opts , url ) {
let { mesh , model , camera , renderer , THREE , hashbus } = opts
2023-07-05 16:43:07 +02:00
if ( url ) {
2023-09-21 13:05:30 +02:00
console . log ( mesh . name + " url=" + url )
//let {urlObj,dir,file,hash,ext} = xrf.parseUrl(url)
//let frag = xrfragment.URI.parse(url)
//// scale URI XR Fragments (queries) inside src-value
//for( var i in frag ){
// hashbus.pub.fragment(i, Object.assign(opts,{frag, model:{scene},scene}))
//}
//hashbus.pub( '#', {scene} ) // execute the default projection '#' (if exist)
//hashbus.pub( url, {scene} ) // and eval URI XR fragments
2023-07-06 15:27:27 +02:00
}
2023-09-15 19:43:11 +02:00
}
// scale embedded XR fragments https://xrfragment.org/#scaling%20of%20instanced%20objects
xrf . frag . src . scale = function ( scene , opts , url ) {
let { mesh , model , camera , renderer , THREE } = opts
2023-09-21 13:05:30 +02:00
let restrictTo3DBoundingBox = mesh . geometry
if ( restrictTo3DBoundingBox ) {
2023-08-04 09:11:26 +02:00
// spec 3 of https://xrfragment.org/#src
// spec 1 of https://xrfragment.org/#scaling%20of%20instanced%20objects
// normalize instanced objectsize to boundingbox
2023-09-21 13:05:30 +02:00
let sizeFrom = new THREE . Vector3 ( )
let sizeTo = new THREE . Vector3 ( )
let empty = new THREE . Object3D ( )
// *TODO* exclude invisible objects from boundingbox size-detection
//
// THREE.Box3.prototype.expandByObject = (function(expandByObject){
// return function(object,precise){
// return expandByObject.call(this, object.visible ? object : empty, precise)
// }
// })(THREE.Box3.prototype.expandByObject)
new THREE . Box3 ( ) . setFromObject ( mesh ) . getSize ( sizeTo )
new THREE . Box3 ( ) . setFromObject ( scene ) . getSize ( sizeFrom )
let ratio = sizeFrom . divide ( sizeTo )
scene . scale . multiplyScalar ( 1.0 / Math . max ( ratio . x , ratio . y , ratio . z ) ) ;
// let factor = getMax(sizeTo) < getMax(sizeFrom) ? getMax(sizeTo) / getMax(sizeFrom) : getMax(sizeFrom) / getMax(sizeTo)
// 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-09-21 13:05:30 +02:00
console . log ( "normal scale: " + url )
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-09-21 13:05:30 +02:00
}
xrf . frag . src . filterScene = ( scene , opts ) => {
let { mesh , model , camera , renderer , THREE , hashbus , frag } = opts
let obj , src
// cherrypicking of object(s)
if ( ! frag . q ) {
src = new THREE . Group ( )
if ( Object . keys ( frag ) . length > 0 ) {
for ( var i in frag ) {
if ( scene . getObjectByName ( i ) ) {
src . add ( obj = scene . getObjectByName ( i ) . clone ( true ) )
}
hashbus . pub . fragment ( i , Object . assign ( opts , { frag , model , scene } ) )
}
} else src = scene . clone ( true )
if ( src . children . length == 1 ) obj . position . set ( 0 , 0 , 0 ) ;
}
// filtering of objects using query
if ( frag . q ) {
src = scene . clone ( true ) ;
xrf . frag . q . filter ( src , frag )
}
src . traverse ( ( m ) => {
if ( m . userData && ( m . userData . src || m . userData . href ) ) return ; // prevent infinite recursion
hashbus . pub . mesh ( m , { scene , recursive : true } ) // cool idea: recursion-depth based distance between face & src
} )
return src
2023-07-05 16:43:07 +02:00
}
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
* /
2023-09-21 13:05:30 +02:00
xrf . frag . src . type [ 'gltf' ] = function ( url , opts ) {
2023-07-05 16:43:07 +02:00
return new Promise ( ( resolve , reject ) => {
2023-09-21 13:05:30 +02:00
let { mesh , src } = opts
2023-07-05 16:43:07 +02:00
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
2023-09-21 13:05:30 +02:00
dir = dir [ 0 ] == './' ? dir : ` ./ ${ dir } `
2023-07-05 16:43:07 +02:00
loader = new Loader ( ) . setPath ( dir )
} else loader = new Loader ( )
2023-09-21 13:05:30 +02:00
loader . load ( url , ( model ) => {
2023-07-05 16:43:07 +02:00
resolve ( model )
2023-09-21 13:05:30 +02:00
} )
2023-07-05 16:43:07 +02:00
} )
2023-05-05 18:53:42 +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
2023-09-21 13:05:30 +02:00
let restrictTo3DBoundingBox = mesh . geometry
2023-07-06 16:54:12 +02:00
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-10-11 13:46:38 +02:00
xrf . frag . t = function ( v , opts ) {
let { frag , mesh , model , camera , scene , renderer , THREE } = opts
console . log ( ` └ setting animation loop to ${ v . x } ${ v . y } ${ v . z } ` )
if ( ! model . animations || model . animations [ 0 ] == undefined ) return console . warn ( 'no animation in scene' )
model . mixer . t = v
let duration = model . animations [ 0 ] . duration
let frames = model . animations [ 0 ] . tracks [ 0 ] . times . length
let mixer = model . mixer
mixer . loop = mixer . loop || { frameStart : 0 , frameStop : 99999999 , speed : 1 }
let fps = frames / duration
mixer . loop . speed = v . x
mixer . loop . speedAbs = Math . abs ( v . x )
mixer . loop . frameStart = v . y || mixer . loop . frameStart
mixer . loop . frameStop = v . z || mixer . loop . frameStop
// always recalculate time using frameStart/Stop
mixer . loop . timeStart = mixer . loop . frameStart / ( fps * mixer . loop . speedAbs )
mixer . loop . timeStop = mixer . loop . frameStop / ( fps * mixer . loop . speedAbs )
// update speed
mixer . timeScale = mixer . loop . speed
let updateTime = ( time ) => {
mixer . setTime ( time )
mixer . time = Math . abs ( mixer . time )
mixer . update ( 0 ) // (forgetting) this little buddy costed me lots of time :]
}
if ( v . y > 0 || v . z > 0 ) updateTime ( mixer . loop . timeStart )
2023-10-11 20:12:48 +02:00
// update loop when needed
2023-10-11 13:46:38 +02:00
if ( ! mixer . update . patched ) {
let update = mixer . update
mixer . update = function ( time ) {
mixer . time = Math . abs ( mixer . time )
if ( time == 0 ) return update . call ( mixer , time )
if ( mixer . loop . speed > 0.0 && mixer . time > mixer . loop . timeStop * mixer . loop . speedAbs ) {
setTimeout ( ( time ) => updateTime ( time ) , 0 , mixer . loop . timeStart ) // prevent recursion
}
return update . call ( mixer , time )
}
mixer . update . patched = true
}
}