Compare commits

...

70 Commits
pages ... main

Author SHA1 Message Date
Leon van Kammen fb7b9c2c9c better scrollbar position homepage 2025-01-15 11:56:34 +01:00
Leon van Kammen 2ce4a39ea8 better pseudocode in homepage 2025-01-15 11:54:52 +01:00
Leon van Kammen 4c15b4cbf1 repositioned scrollbar in docs 2025-01-15 11:47:59 +01:00
Leon van Kammen 3a3f8f555c update docs 2025-01-15 11:44:50 +01:00
Leon van Kammen 6cc43f4011 removed black testcube 2025-01-15 11:23:17 +01:00
Leon van Kammen bf6efb173a removed 'camera'-dependency on xrf [bugfix] 2025-01-15 10:53:56 +01:00
Leon van Kammen a79e6414d2 .env glob typofix 2025-01-14 15:44:49 +01:00
Leon van Kammen 519d1a5894 .env URL typofix 2025-01-14 15:44:23 +01:00
Leon van Kammen 287879997d .env shellscript typofix 2025-01-14 15:44:00 +01:00
Leon van Kammen e6eb94910b typofix 2025-01-14 15:43:37 +01:00
Leon van Kammen 43e5f82402 copy assets to webroot based on url 2025-01-14 15:43:03 +01:00
Leon van Kammen 57053d40c2 deploy fix 2025-01-14 15:41:56 +01:00
Leon van Kammen 970805b5c6 updated .env 2025-01-14 15:41:11 +01:00
Leon van Kammen 41496ea9cf updated .env 2025-01-14 15:38:48 +01:00
Leon van Kammen ab77db680d updated .env 2025-01-14 15:38:15 +01:00
Leon van Kammen 94346dbdff new build 2025-01-14 15:36:36 +01:00
Leon van Kammen 9efaeab5b5 attempt #2 to fix aframe upgrade data-issue 2025-01-14 14:58:29 +01:00
Leon van Kammen 1ae1a46580 removed debugger 2025-01-14 14:47:52 +01:00
Leon van Kammen f9be7a946a bugfix regarding aframe update 2025-01-14 14:47:01 +01:00
Leon van Kammen 659c2da2a5 bugfix: AFRAME.scenes[0] gets created later in v1.6.0 2024-12-16 14:19:12 +01:00
Leon van Kammen 78b60210bc added camera dependency to xrf component 2024-12-16 14:17:12 +01:00
Leon van Kammen ba90411e6c better compatibilty with other aframe plugins + build 2024-12-16 13:49:18 +01:00
Leon van Kammen 7abdcf1016 added joystick 2024-12-11 09:52:27 +00:00
Leon van Kammen aacb9d13e2 main: update documentation 2024-12-10 14:58:44 +00:00
Leon van Kammen 090016089c update website asset 2024-12-10 14:52:36 +00:00
Leon van Kammen a37de3ec8d new build 2024-12-10 14:49:42 +00:00
Leon van Kammen 935e0898c9 disable headrotation for hrefs 2024-12-10 14:49:23 +00:00
Leon van Kammen 9a13746c1c added website example 2024-12-10 14:41:38 +00:00
Leon van Kammen 7025074612 reverted camera positioning strategy 2024-12-10 14:41:07 +00:00
Leon van Kammen e7d282f268 attempt to fix export [hamburgermenu>share>download button] 2024-12-10 14:36:52 +00:00
Leon van Kammen 9c23a6e2a3 add note in console to use seconds [not frames] for animations 2024-12-10 14:36:11 +00:00
Leon van Kammen edfab56347 cleanup audio node 2024-12-10 14:35:39 +00:00
Leon van Kammen 9fc7d3c6f6 bugfix: move camera only in world coordinates 2024-12-10 14:35:11 +00:00
Leon van Kammen 186f2af440 bugfix: safer if 2024-12-10 14:34:22 +00:00
Leon van Kammen ab69dac0ec bugfix: enable stencil [required since aframe 1.6.0] + better audio cleanup 2024-12-10 12:54:26 +00:00
Leon van Kammen 4bc3d1c520 upgraded aframe to v1.6.0 + added check for duplicate movement-controls & touch-controls 2024-12-09 15:42:34 +00:00
Leon van Kammen c3b0636bf7 main: update documentation 2024-12-09 14:24:17 +01:00
Leon van Kammen 43ca5c1cc6 updated build 2024-10-14 11:54:39 +02:00
Leon van Kammen 0bd37a3b30 simplified #pos.js and bugfixed envmapping.js 2024-10-14 11:54:14 +02:00
Leon van Kammen b7f3c91b7f bugfix crossdomain back-button 2024-10-14 11:53:17 +02:00
Leon van Kammen 28770956b4 better truncating of hrefs 2024-10-14 11:53:00 +02:00
Leon van Kammen e0ee7e10e0 applied scale to world2 2024-10-14 11:52:48 +02:00
Leon van Kammen 5318eed885 updated docs on reflection mapping 2024-10-14 11:52:35 +02:00
Leon van Kammen 89147de484 bugfix: ignore non-XRF fragments 2024-10-13 14:08:45 +02:00
Leon van Kammen 69e801e4a5 improved cam positioning + elevator demo 2024-10-13 14:03:31 +02:00
Leon van Kammen 4d68b50ede main: minor bugfix 2024-10-08 17:11:14 +02:00
Leon van Kammen a54ff56a8d main: update documentation 2024-10-07 19:03:02 +00:00
Leon van Kammen 676b8d1fd2 better support for camera-switching/animation 2024-10-07 19:02:52 +00:00
Leon van Kammen 2ce60fc493 main: update documentation 2024-10-07 08:57:20 +00:00
Leon van Kammen 60ed159b0c main: update documentation 2024-09-29 12:53:19 +02:00
Leon van Kammen f0d97b91ab main: update documentation 2024-09-29 12:17:23 +02:00
Leon van Kammen 8d776ba4b0 added matrix link 2024-09-28 11:36:34 +02:00
Leon van Kammen b288e0343d addded override mention to spec 2024-09-27 21:01:04 +02:00
Leon van Kammen f73ddba0c0 main: update documentation 2024-09-27 18:58:23 +02:00
Leon van Kammen 0a5047ec3d main: work in progress [might break] 2024-09-27 18:56:02 +02:00
Leon van Kammen 79a3b7e151 main: update documentation 2024-09-27 18:42:53 +02:00
Leon van Kammen b72914c49c fixed podcast 2024-09-27 18:39:57 +02:00
Leon van Kammen de1acb539a added podcasts 2024-09-27 10:54:08 +02:00
Leon van Kammen 4b92836422 main: update documentation 2024-09-25 16:58:57 +02:00
Leon van Kammen 457935a426 main: work in progress [might break] 2024-09-25 16:03:59 +02:00
Leon van Kammen 0dc2b1f2e4 main: update documentation 2024-09-25 16:00:36 +02:00
Leon van Kammen 91b08014fa main: update documentation 2024-09-25 10:55:11 +02:00
Leon van Kammen f865c22694 main: update documentation 2024-09-25 10:21:27 +02:00
Leon van Kammen cc49c4ad92 bugfix: dont request fingers before they exist 2024-09-24 18:21:46 +02:00
Leon van Kammen 82ab6e2882 main: update documentation 2024-09-24 16:12:10 +02:00
Leon van Kammen f14ff60252 added 'extras' in homepage 2024-09-24 15:36:34 +02:00
Leon van Kammen cffd1e1446 doc: uppercase to lowercase fix 2024-09-20 17:02:24 +02:00
Leon van Kammen c813a45eb1 updated VP's in doc 2024-09-20 16:53:07 +02:00
Leon van Kammen de5fe7002a main: update documentation 2024-08-30 11:10:35 +02:00
Leon van Kammen e0b50f039b debug 2024-08-06 10:35:57 +02:00
51 changed files with 53387 additions and 2895 deletions

24
.env Normal file
View File

@ -0,0 +1,24 @@
which nix && test -z "$NIX_SHELL_XRF" && {
# automatically mirror main between forgejo<->codeberg
#git(){
# set -x
# test $1 = "push" && test $3 = main && mirror=1
# $(which git) "$@"
# test -n "$mirror" && {
# set -x
# shift ; shift # remove first to args
# $(which git) push codeberg "$@"
# }
# set +x
#}
echo '[i] installing nix-shell' && nix-shell
}
test "$GITEA_ROOT_URL" = "https://forgejo.isvery.ninja/" && {
# on the website xrfragment.org copy examples to root-dir
# (so https://xrfragment.org/index.glb can be requested remotely)
# because that really emphasizes a nice WebXR experience-at-website-root paradigm
cp -r example/assets/* .
}

2
dist/aframe.min.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

407
dist/xrfragment.js vendored
View File

@ -1,104 +1,469 @@
/*
* v0.5.1 generated at Fri Aug 2 10:04:04 AM UTC 2024
* v0.5.1 generated at Wed Jan 15 10:52:05 AM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Fri Aug 2 09:38:49 AM UTC 2024
* v0.5.1 generated at Wed Jan 15 10:51:41 AM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Fri Aug 2 09:32:50 AM UTC 2024
* v0.5.1 generated at Wed Jan 15 10:51:31 AM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Fri Aug 2 10:44:23 AM CEST 2024
* v0.5.1 generated at Wed Jan 15 10:51:15 AM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Thu Aug 1 04:21:30 PM CEST 2024
* v0.5.1 generated at Wed Jan 15 10:50:01 AM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Thu Aug 1 04:19:15 PM CEST 2024
* v0.5.1 generated at Wed Jan 15 10:49:22 AM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Thu Aug 1 04:14:05 PM CEST 2024
* v0.5.1 generated at Wed Jan 15 10:48:58 AM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Thu Aug 1 04:09:52 PM CEST 2024
* v0.5.1 generated at Wed Jan 15 10:47:50 AM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Thu Aug 1 04:08:30 PM CEST 2024
* v0.5.1 generated at Wed Jan 15 10:43:55 AM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Thu Aug 1 03:58:35 PM CEST 2024
* v0.5.1 generated at Wed Jan 15 10:39:36 AM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Thu Aug 1 03:54:34 PM CEST 2024
* v0.5.1 generated at Wed Jan 15 10:38:56 AM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Thu Aug 1 03:50:48 PM CEST 2024
* v0.5.1 generated at Wed Jan 15 10:38:18 AM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Thu Aug 1 03:47:02 PM CEST 2024
* v0.5.1 generated at Wed Jan 15 10:37:32 AM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Thu Aug 1 03:46:18 PM CEST 2024
* v0.5.1 generated at Wed Jan 15 10:37:04 AM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Thu Aug 1 02:52:22 PM CEST 2024
* v0.5.1 generated at Wed Jan 15 10:35:19 AM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Thu Aug 1 02:48:18 PM CEST 2024
* v0.5.1 generated at Wed Jan 15 10:30:49 AM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Thu Aug 1 02:44:38 PM CEST 2024
* v0.5.1 generated at Wed Jan 15 10:30:17 AM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Thu Aug 1 02:35:08 PM CEST 2024
* v0.5.1 generated at Wed Jan 15 10:27:52 AM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Thu Aug 1 02:32:36 PM CEST 2024
* v0.5.1 generated at Wed Jan 15 10:27:22 AM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Thu Aug 1 11:31:12 AM CEST 2024
* v0.5.1 generated at Wed Jan 15 10:25:57 AM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
// Generated by Haxe 4.3.3
/*
* v0.5.1 generated at Wed Jan 15 10:22:36 AM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Wed Jan 15 10:19:46 AM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Wed Jan 15 10:19:21 AM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Wed Jan 15 10:15:58 AM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Wed Jan 15 10:15:30 AM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Wed Jan 15 10:15:28 AM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 08:15:04 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 08:14:22 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 08:13:57 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 08:13:42 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 08:13:18 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 08:12:32 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 08:11:05 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 08:10:11 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 08:07:36 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 08:07:12 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 08:06:48 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 08:06:27 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 08:06:11 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 08:05:47 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 03:35:01 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 03:33:48 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 03:33:30 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 03:33:24 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 03:32:59 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 03:26:30 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 03:26:19 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 03:26:05 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 03:24:14 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 03:23:45 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 03:23:27 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 03:23:12 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 03:22:13 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 03:21:51 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 03:21:36 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 03:20:54 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 03:20:39 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 03:20:13 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 03:19:55 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 03:18:34 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 03:01:28 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 03:00:58 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 02:55:26 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 02:55:01 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 02:54:21 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 02:54:04 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 02:53:31 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 02:52:11 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 02:51:59 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 02:51:44 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 02:51:01 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 02:50:51 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 02:50:39 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 02:49:27 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 02:48:13 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 02:42:07 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 02:41:43 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 02:41:25 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 02:40:51 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 02:39:41 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 02:38:52 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 02:38:19 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 02:37:01 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 02:36:19 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 02:35:59 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 02:35:44 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 02:35:28 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 02:34:46 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 02:32:50 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 02:31:07 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 02:30:27 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 02:24:55 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Fri Jan 10 07:51:18 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
// Generated by Haxe 4.3.6
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"] || {};

2
dist/xrfragment.lua vendored
View File

@ -1,4 +1,4 @@
-- Generated by Haxe 4.3.3
-- Generated by Haxe 4.3.6
local _hx_hidden = {__id__=true, hx__closures=true, super=true, prototype=true, __fields__=true, __ifields__=true, __class__=true, __properties__=true, __fields__=true, __name__=true}
_hx_array_mt = {

File diff suppressed because one or more lines are too long

47873
dist/xrfragment.module.js vendored

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,5 @@
/*
* v0.5.1 generated at Fri Aug 2 10:04:04 AM UTC 2024
* v0.5.1 generated at Wed Jan 15 10:52:05 AM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

View File

@ -1,5 +1,5 @@
/*
* v0.5.1 generated at Fri Aug 2 10:04:04 AM UTC 2024
* v0.5.1 generated at Wed Jan 15 10:52:05 AM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
@ -397,6 +397,26 @@ document.head.innerHTML += `
div.tab-frame > input:nth-of-type(2):checked ~ .tab:nth-of-type(2),
div.tab-frame > input:nth-of-type(3):checked ~ .tab:nth-of-type(3){ display:block;}
/*
* joystick.js controller
*/
.controller {
position: fixed;
display: block;
width: 100px;
height: 100px;
left: 25px;
bottom: 20px;
cursor:pointer;
z-index: 999;
border-radius: 50%;
border: 5px solid #333;
filter: alpha(opacity=50);
-khtml-opacity: 0.3;
-moz-opacity: 0.3;
opacity:0.3;
transition: opacity 1s ease-out;
}
/*
* css icons from https://css.gg

File diff suppressed because one or more lines are too long

View File

@ -1,5 +1,5 @@
/*
* v0.5.1 generated at Fri Aug 2 10:04:04 AM UTC 2024
* v0.5.1 generated at Wed Jan 15 10:52:05 AM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

View File

@ -1,5 +1,5 @@
/*
* v0.5.1 generated at Fri Aug 2 10:04:04 AM UTC 2024
* v0.5.1 generated at Wed Jan 15 10:52:05 AM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

View File

@ -1,5 +1,5 @@
/*
* v0.5.1 generated at Fri Aug 2 10:04:04 AM UTC 2024
* v0.5.1 generated at Wed Jan 15 10:52:05 AM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

2
dist/xrfragment.py vendored
View File

@ -1,4 +1,4 @@
# Generated by Haxe 4.3.3
# Generated by Haxe 4.3.6
# coding: utf-8
import sys

View File

@ -1,104 +1,469 @@
/*
* v0.5.1 generated at Fri Aug 2 10:04:04 AM UTC 2024
* v0.5.1 generated at Wed Jan 15 10:52:05 AM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Fri Aug 2 09:38:49 AM UTC 2024
* v0.5.1 generated at Wed Jan 15 10:51:41 AM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Fri Aug 2 09:32:50 AM UTC 2024
* v0.5.1 generated at Wed Jan 15 10:51:31 AM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Fri Aug 2 10:44:23 AM CEST 2024
* v0.5.1 generated at Wed Jan 15 10:51:15 AM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Thu Aug 1 04:21:30 PM CEST 2024
* v0.5.1 generated at Wed Jan 15 10:50:01 AM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Thu Aug 1 04:19:15 PM CEST 2024
* v0.5.1 generated at Wed Jan 15 10:49:22 AM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Thu Aug 1 04:14:05 PM CEST 2024
* v0.5.1 generated at Wed Jan 15 10:48:58 AM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Thu Aug 1 04:09:52 PM CEST 2024
* v0.5.1 generated at Wed Jan 15 10:47:50 AM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Thu Aug 1 04:08:30 PM CEST 2024
* v0.5.1 generated at Wed Jan 15 10:43:55 AM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Thu Aug 1 03:58:35 PM CEST 2024
* v0.5.1 generated at Wed Jan 15 10:39:36 AM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Thu Aug 1 03:54:34 PM CEST 2024
* v0.5.1 generated at Wed Jan 15 10:38:56 AM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Thu Aug 1 03:50:48 PM CEST 2024
* v0.5.1 generated at Wed Jan 15 10:38:18 AM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Thu Aug 1 03:47:02 PM CEST 2024
* v0.5.1 generated at Wed Jan 15 10:37:32 AM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Thu Aug 1 03:46:18 PM CEST 2024
* v0.5.1 generated at Wed Jan 15 10:37:04 AM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Thu Aug 1 02:52:22 PM CEST 2024
* v0.5.1 generated at Wed Jan 15 10:35:19 AM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Thu Aug 1 02:48:18 PM CEST 2024
* v0.5.1 generated at Wed Jan 15 10:30:49 AM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Thu Aug 1 02:44:38 PM CEST 2024
* v0.5.1 generated at Wed Jan 15 10:30:17 AM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Thu Aug 1 02:35:08 PM CEST 2024
* v0.5.1 generated at Wed Jan 15 10:27:52 AM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Thu Aug 1 02:32:36 PM CEST 2024
* v0.5.1 generated at Wed Jan 15 10:27:22 AM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Thu Aug 1 11:31:12 AM CEST 2024
* v0.5.1 generated at Wed Jan 15 10:25:57 AM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
// Generated by Haxe 4.3.3
/*
* v0.5.1 generated at Wed Jan 15 10:22:36 AM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Wed Jan 15 10:19:46 AM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Wed Jan 15 10:19:21 AM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Wed Jan 15 10:15:58 AM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Wed Jan 15 10:15:30 AM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Wed Jan 15 10:15:28 AM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 08:15:04 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 08:14:22 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 08:13:57 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 08:13:42 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 08:13:18 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 08:12:32 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 08:11:05 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 08:10:11 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 08:07:36 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 08:07:12 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 08:06:48 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 08:06:27 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 08:06:11 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 08:05:47 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 03:35:01 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 03:33:48 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 03:33:30 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 03:33:24 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 03:32:59 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 03:26:30 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 03:26:19 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 03:26:05 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 03:24:14 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 03:23:45 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 03:23:27 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 03:23:12 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 03:22:13 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 03:21:51 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 03:21:36 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 03:20:54 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 03:20:39 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 03:20:13 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 03:19:55 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 03:18:34 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 03:01:28 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 03:00:58 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 02:55:26 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 02:55:01 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 02:54:21 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 02:54:04 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 02:53:31 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 02:52:11 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 02:51:59 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 02:51:44 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 02:51:01 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 02:50:51 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 02:50:39 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 02:49:27 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 02:48:13 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 02:42:07 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 02:41:43 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 02:41:25 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 02:40:51 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 02:39:41 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 02:38:52 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 02:38:19 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 02:37:01 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 02:36:19 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 02:35:59 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 02:35:44 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 02:35:28 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 02:34:46 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 02:32:50 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 02:31:07 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 02:30:27 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 02:24:55 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Fri Jan 10 07:51:18 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
// Generated by Haxe 4.3.6
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"] || {};
@ -1696,7 +2061,7 @@ xrf.query = function(){
xrf.detectCameraRig = function(opts){
if( opts.camera ){ // detect rig (if any)
let getCam = ((cam) => () => cam)(opts.camera)
const getCam = ((cam) => () => cam)(opts.camera)
let offsetY = 0
while( opts.camera.parent.type != "Scene" ){
offsetY += opts.camera.position.y
@ -1704,6 +2069,7 @@ xrf.detectCameraRig = function(opts){
opts.camera.getCam = getCam
opts.camera.updateProjectionMatrix = () => opts.camera.getCam().updateProjectionMatrix()
}
if( !opts.camera.getCam ) opts.camera.getCam = getCam // always attach function
opts.camera.offsetY = offsetY
}
}
@ -1986,6 +2352,9 @@ xrf.init = ((init) => function(opts){
// operate in own subscene
let scene = new opts.THREE.Group()
xrf.clock = new opts.THREE.Clock()
// don't mess with original scene object
// but with our own sub-scene
opts.scene.add(scene)
opts.sceneRoot = opts.scene
opts.scene = scene
@ -2023,7 +2392,9 @@ xrf.parseModel = function(model,url){
model.file = file
model.isXRF = true
model.scene.isXRFRoot = true
model.scene.traverse( (n) => n.isXRF = true ) // mark for deletion during reset()
model.scene.traverse( (n) => {
n.isXRF = true
}) // mark for deletion during reset()
xrf.emit('parseModel',{model,url,file})
}
@ -2082,9 +2453,17 @@ xrf.reset = () => {
// allow others to reset certain events
xrf.emit('reset',{})
// reattach camera to root scene
xrf.scene.attach(xrf.camera)
xrf.camera.position.set(0,0,0)
xrf.camera.updateMatrixWorld()
xrf.camera.getCam().updateMatrixWorld()
const disposeObject = (obj) => {
if (obj.children.length > 0) obj.children.forEach((child) => disposeObject(child));
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();
@ -2128,21 +2507,26 @@ xrf.navigator = {
xrf.navigator.to = (url,flags,loader,data) => {
if( !url ) throw 'xrf.navigator.to(..) no url given'
let URI = xrfragment.URI.toAbsolute( xrf.navigator.URI, url )
URI.hash = xrf.navigator.reactifyHash(URI.hash) // automatically reflect hash-changes to navigator.to(...)
// decorate with extra state
URI.fileChange = URI.file && URI.URN + URI.file != xrf.navigator.URI.URN + xrf.navigator.URI.file
console.log( URI.URN + URI.file )
console.log( xrf.navigator.URI.URN + xrf.navigator.URI.file )
URI.external = URI.file && URI.URN != document.location.origin + document.location.pathname
URI.hasPos = URI.hash.pos ? true : false
URI.duplicatePos = URI.source == xrf.navigator.URI.source && URI.hasPos
URI.hashChange = String(xrf.navigator.URI.fragment||"") != String(URI.fragment||"")
let hashbus = xrf.hashbus
let URI
//console.dir({URI1:xrf.navigator.URI,URI2:URI})
if( typeof url == 'string' ){
URI = xrfragment.URI.toAbsolute( xrf.navigator.URI, url )
URI.hash = xrf.navigator.reactifyHash(URI.hash) // automatically reflect hash-changes to navigator.to(...)
// decorate with extra state
URI.fileChange = URI.file && URI.URN + URI.file != xrf.navigator.URI.URN + xrf.navigator.URI.file
console.log( URI.URN + URI.file )
console.log( xrf.navigator.URI.URN + xrf.navigator.URI.file )
URI.external = URI.file && URI.URN != document.location.origin + document.location.pathname
URI.hasPos = URI.hash.pos ? true : false
URI.duplicatePos = URI.source == xrf.navigator.URI.source && URI.hasPos
URI.hashChange = String(xrf.navigator.URI.fragment||"") != String(URI.fragment||"")
}else{
URI = url
url = URI.source
}
URI.last = xrf.navigator.URI
xrf.navigator.URI = URI
let {directory,file,fragment,fileExt} = URI;
@ -2219,7 +2603,7 @@ xrf.navigator.init = () => {
window.addEventListener('popstate', function (event){
if( xrf.navigator.updateHash.active ){ // ignore programmatic hash updates (causes infinite recursion)
xrf.navigator.to( document.location.href.replace(/.*\?/,'') )
xrf.navigator.to( xrf.navigator.URI.last )
}
})
@ -2347,7 +2731,7 @@ xrf.frag.href = function(v, opts){
let click = mesh.userData.XRF.href.exec = (e) => {
if( !mesh.material || !mesh.material.visible ) return // ignore invisible nodes
if( !mesh.material || !(mesh.material && mesh.material.visible) ) return // ignore invisible nodes
// update our values to the latest value (might be edited)
let URI = xrf.URI.template( mesh.userData.href, xrf.URI.vars.__object )
@ -2503,15 +2887,18 @@ xrf.frag.loop = function(v, opts){
xrf.frag.pos = function(v, opts){
let { frag, mesh, model, camera, scene, renderer, THREE} = opts
let pos = v
if( !scene )return
let pos = v
// spec: indirect coordinate using objectname: https://xrfragment.org/#navigating%203D
if( pos.x == undefined ){
let obj = scene.getObjectByName(v.string)
if( !obj ) return
pos = obj.position.clone()
obj.getWorldPosition(pos)
camera.position.copy(pos)
if( !obj ) return console.warn("#pos="+v.string+" not found")
obj.add(camera) // follow animation of targeted position
camera.position.set(0,0,0) // set playerheight
//let c = camera.rotation
//c.set( c.x, obj.rotation.y, c.z )
}else{
// spec: direct coordinate: https://xrfragment.org/#navigating%203D
camera.position.x = pos.x
@ -2524,6 +2911,7 @@ xrf.frag.pos = function(v, opts){
xrf.frag.pos.lastVector3 = camera.position.clone()
camera.updateMatrixWorld()
camera.getCam().updateMatrixWorld()
}
xrf.frag.pos.get = function(precision,randomize){
@ -2559,6 +2947,9 @@ xrf.addEventListener('reset', (opts) => {
xrf.frag.rot = function(v, opts){
let { frag, mesh, model, camera, scene, renderer, THREE} = opts
if( xrf.debug ) console.log("#rot.js: setting camera rotation to "+v.string)
if( !camera || !scene ) return
if( !model.isSRC ){
camera.rotation.set(
v.x * Math.PI / 180,
@ -2795,7 +3186,6 @@ xrf.frag.src.type['unknown'] = function( url, opts ){
xrf.frag.t = function(v, opts){
let { frag, mesh, model, camera, scene, renderer, THREE} = opts
// handle object media players
if( mesh && mesh.media ){
for( let i in mesh.media ) mesh.media[i].set("t",v)
@ -2845,13 +3235,14 @@ xrf.addEventListener('parseModel', (opts) => {
model.animations.map( (a) => mixer.duration = ( a.duration > mixer.duration ) ? a.duration : mixer.duration )
}
model.animations.map( (anim) => {
anim.optimize()
if( xrf.debug ) console.log("action: "+anim.name)
model.animations.map( (anim) => {
console.log("animation action: "+anim.name)
mixer.actions.push( mixer.clipAction( anim, model.scene ) )
})
mixer.play = (t) => {
let msg = `media fragment: ${t.x}-${t.y} seconds`
if( t.x > 49 ) msg += ", not frames (!)"
console.log(msg)
mixer.isPlaying = t.x !== undefined && t.x != t.y
mixer.updateLoop(t)
xrf.emit( mixer.isPlaying === false ? 'stop' : 'play',{isPlaying: mixer.isPlaying})
@ -3320,20 +3711,6 @@ xrf.sceneToTranscript = (scene, ignoreMesh ) => {
}
// switch camera when multiple cameras for url #mycameraname
xrf.addEventListener('dynamicKey', (opts) => {
// select active camera if any
let {id,match,v} = opts
match.map( (w) => {
w.nodes.map( (node) => {
if( node.isCamera ){
console.log("switching camera to cam: "+node.name)
xrf.model.camera = node
}
})
})
})
// switch camera when multiple cameras for url #mycameraname
xrf.addEventListener('navigateLoaded', (opts) => {
// select active camera if any
let {id,match,v,THREE} = opts
@ -3343,9 +3720,10 @@ xrf.addEventListener('navigateLoaded', (opts) => {
// Recursive function to traverse the graph
function traverseAndSetEnvMap(node, closestAncestorMaterialMap = null) {
// Check if the current node has a material
if (node.isMesh && node.material) {
if (node.isMesh && node.material ) {
if (node.material.map && closestAncestorMaterialMap) {
// If the node has a material map, set the closest ancestor material map
node.material = node.material.clone() // dont affect objects which share same material
node.material.envMap = closestAncestorMaterialMap;
}
}
@ -3396,6 +3774,8 @@ xrf.filter.scene = function(opts){
.sort(frag) // get (sorted) filters from XR Fragments
.process(frag,scene,opts) // show/hide things
if( !scene ) return
scene.visible = true // always enable scene
return scene
@ -3411,6 +3791,7 @@ xrf.filter.sort = function(frag){
// opts = {copyScene:true} in case you want a copy of the scene (not filter the current scene inplace)
xrf.filter.process = function(frag,scene,opts){
if( !scene || scene.children.length == 0 ) return
const cleanupKey = (k) => k.replace(/[-\*\/]/g,'')
let firstFilter = frag.filters.length ? frag.filters[0].filter.get() : false
const hasName = (m,name,filter) => m.name == name
@ -3582,7 +3963,7 @@ xrf.addEventListener('dynamicKeyValue', (opts) => {
xrf.frag.dynamic.material(v,opts) // check if fragment is an objectname
}
if( !xrf.URI.vars[ v.string ] ) return console.error(`'${v.string}' metadata-key not found in scene`)
if( !xrf.URI.vars[ v.string ] ) return // ignore non-template URI fragments
//if( xrf.URI.vars[ id ] && !match.length ) return console.error(`'${id}' object/tag/metadata-key not found in scene`)
if( xrf.debug ) console.log(`URI.vars[${id}] => '${v.string}'`)
@ -3663,7 +4044,7 @@ xrf.drawLineToMesh = (opts) => {
xrf.addEventListener('render', (opts) => {
// update focusline
let {time,model} = opts
if( !xrf.clock ) return
if( !xrf.clock || !xrf.focusLine ) return
xrf.focusLine.material.color.r = (1.0 + Math.sin( xrf.clock.getElapsedTime()*10 ))/2
xrf.focusLine.material.dashSize = 0.2 + 0.02*Math.sin( xrf.clock.getElapsedTime() )
xrf.focusLine.material.gapSize = 0.1 + 0.02*Math.sin( xrf.clock.getElapsedTime() *3 )
@ -3690,6 +4071,7 @@ let loadAudio = (mimetype) => function(url,opts){
let sound = isPositionalAudio ? new THREE.PositionalAudio( camera.listener)
: new THREE.Audio( camera.listener )
sound.isXRF = true
mesh.media = mesh.media || {}
mesh.media.audio = { set: (mediafragment,v) => mesh.media.audio[mediafragment] = v }
@ -3781,6 +4163,7 @@ xrf.addEventListener('reset', () => {
if( n.media && n.media.audio ){
if( n.media.audio.stop ) n.media.audio.stop()
if( n.media.audio.remove ) n.media.audio.remove()
n.remove()
}
})
})

View File

@ -1,104 +1,469 @@
/*
* v0.5.1 generated at Fri Aug 2 10:04:04 AM UTC 2024
* v0.5.1 generated at Wed Jan 15 10:52:05 AM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Fri Aug 2 09:38:49 AM UTC 2024
* v0.5.1 generated at Wed Jan 15 10:51:41 AM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Fri Aug 2 09:32:50 AM UTC 2024
* v0.5.1 generated at Wed Jan 15 10:51:31 AM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Fri Aug 2 10:44:23 AM CEST 2024
* v0.5.1 generated at Wed Jan 15 10:51:15 AM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Thu Aug 1 04:21:30 PM CEST 2024
* v0.5.1 generated at Wed Jan 15 10:50:01 AM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Thu Aug 1 04:19:15 PM CEST 2024
* v0.5.1 generated at Wed Jan 15 10:49:22 AM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Thu Aug 1 04:14:05 PM CEST 2024
* v0.5.1 generated at Wed Jan 15 10:48:58 AM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Thu Aug 1 04:09:52 PM CEST 2024
* v0.5.1 generated at Wed Jan 15 10:47:50 AM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Thu Aug 1 04:08:30 PM CEST 2024
* v0.5.1 generated at Wed Jan 15 10:43:55 AM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Thu Aug 1 03:58:35 PM CEST 2024
* v0.5.1 generated at Wed Jan 15 10:39:36 AM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Thu Aug 1 03:54:34 PM CEST 2024
* v0.5.1 generated at Wed Jan 15 10:38:56 AM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Thu Aug 1 03:50:48 PM CEST 2024
* v0.5.1 generated at Wed Jan 15 10:38:18 AM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Thu Aug 1 03:47:02 PM CEST 2024
* v0.5.1 generated at Wed Jan 15 10:37:32 AM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Thu Aug 1 03:46:18 PM CEST 2024
* v0.5.1 generated at Wed Jan 15 10:37:04 AM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Thu Aug 1 02:52:22 PM CEST 2024
* v0.5.1 generated at Wed Jan 15 10:35:19 AM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Thu Aug 1 02:48:18 PM CEST 2024
* v0.5.1 generated at Wed Jan 15 10:30:49 AM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Thu Aug 1 02:44:38 PM CEST 2024
* v0.5.1 generated at Wed Jan 15 10:30:17 AM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Thu Aug 1 02:35:08 PM CEST 2024
* v0.5.1 generated at Wed Jan 15 10:27:52 AM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Thu Aug 1 02:32:36 PM CEST 2024
* v0.5.1 generated at Wed Jan 15 10:27:22 AM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Thu Aug 1 11:31:12 AM CEST 2024
* v0.5.1 generated at Wed Jan 15 10:25:57 AM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
// Generated by Haxe 4.3.3
/*
* v0.5.1 generated at Wed Jan 15 10:22:36 AM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Wed Jan 15 10:19:46 AM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Wed Jan 15 10:19:21 AM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Wed Jan 15 10:15:58 AM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Wed Jan 15 10:15:30 AM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Wed Jan 15 10:15:28 AM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 08:15:04 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 08:14:22 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 08:13:57 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 08:13:42 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 08:13:18 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 08:12:32 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 08:11:05 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 08:10:11 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 08:07:36 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 08:07:12 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 08:06:48 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 08:06:27 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 08:06:11 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 08:05:47 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 03:35:01 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 03:33:48 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 03:33:30 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 03:33:24 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 03:32:59 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 03:26:30 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 03:26:19 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 03:26:05 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 03:24:14 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 03:23:45 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 03:23:27 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 03:23:12 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 03:22:13 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 03:21:51 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 03:21:36 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 03:20:54 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 03:20:39 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 03:20:13 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 03:19:55 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 03:18:34 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 03:01:28 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 03:00:58 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 02:55:26 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 02:55:01 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 02:54:21 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 02:54:04 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 02:53:31 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 02:52:11 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 02:51:59 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 02:51:44 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 02:51:01 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 02:50:51 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 02:50:39 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 02:49:27 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 02:48:13 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 02:42:07 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 02:41:43 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 02:41:25 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 02:40:51 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 02:39:41 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 02:38:52 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 02:38:19 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 02:37:01 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 02:36:19 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 02:35:59 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 02:35:44 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 02:35:28 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 02:34:46 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 02:32:50 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 02:31:07 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 02:30:27 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Tue Jan 14 02:24:55 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
/*
* v0.5.1 generated at Fri Jan 10 07:51:18 PM CET 2025
* https://xrfragment.org
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
// Generated by Haxe 4.3.6
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"] || {};
@ -1696,7 +2061,7 @@ xrf.query = function(){
xrf.detectCameraRig = function(opts){
if( opts.camera ){ // detect rig (if any)
let getCam = ((cam) => () => cam)(opts.camera)
const getCam = ((cam) => () => cam)(opts.camera)
let offsetY = 0
while( opts.camera.parent.type != "Scene" ){
offsetY += opts.camera.position.y
@ -1704,6 +2069,7 @@ xrf.detectCameraRig = function(opts){
opts.camera.getCam = getCam
opts.camera.updateProjectionMatrix = () => opts.camera.getCam().updateProjectionMatrix()
}
if( !opts.camera.getCam ) opts.camera.getCam = getCam // always attach function
opts.camera.offsetY = offsetY
}
}
@ -1986,6 +2352,9 @@ xrf.init = ((init) => function(opts){
// operate in own subscene
let scene = new opts.THREE.Group()
xrf.clock = new opts.THREE.Clock()
// don't mess with original scene object
// but with our own sub-scene
opts.scene.add(scene)
opts.sceneRoot = opts.scene
opts.scene = scene
@ -2023,7 +2392,9 @@ xrf.parseModel = function(model,url){
model.file = file
model.isXRF = true
model.scene.isXRFRoot = true
model.scene.traverse( (n) => n.isXRF = true ) // mark for deletion during reset()
model.scene.traverse( (n) => {
n.isXRF = true
}) // mark for deletion during reset()
xrf.emit('parseModel',{model,url,file})
}
@ -2082,9 +2453,17 @@ xrf.reset = () => {
// allow others to reset certain events
xrf.emit('reset',{})
// reattach camera to root scene
xrf.scene.attach(xrf.camera)
xrf.camera.position.set(0,0,0)
xrf.camera.updateMatrixWorld()
xrf.camera.getCam().updateMatrixWorld()
const disposeObject = (obj) => {
if (obj.children.length > 0) obj.children.forEach((child) => disposeObject(child));
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();
@ -2128,21 +2507,26 @@ xrf.navigator = {
xrf.navigator.to = (url,flags,loader,data) => {
if( !url ) throw 'xrf.navigator.to(..) no url given'
let URI = xrfragment.URI.toAbsolute( xrf.navigator.URI, url )
URI.hash = xrf.navigator.reactifyHash(URI.hash) // automatically reflect hash-changes to navigator.to(...)
// decorate with extra state
URI.fileChange = URI.file && URI.URN + URI.file != xrf.navigator.URI.URN + xrf.navigator.URI.file
console.log( URI.URN + URI.file )
console.log( xrf.navigator.URI.URN + xrf.navigator.URI.file )
URI.external = URI.file && URI.URN != document.location.origin + document.location.pathname
URI.hasPos = URI.hash.pos ? true : false
URI.duplicatePos = URI.source == xrf.navigator.URI.source && URI.hasPos
URI.hashChange = String(xrf.navigator.URI.fragment||"") != String(URI.fragment||"")
let hashbus = xrf.hashbus
let URI
//console.dir({URI1:xrf.navigator.URI,URI2:URI})
if( typeof url == 'string' ){
URI = xrfragment.URI.toAbsolute( xrf.navigator.URI, url )
URI.hash = xrf.navigator.reactifyHash(URI.hash) // automatically reflect hash-changes to navigator.to(...)
// decorate with extra state
URI.fileChange = URI.file && URI.URN + URI.file != xrf.navigator.URI.URN + xrf.navigator.URI.file
console.log( URI.URN + URI.file )
console.log( xrf.navigator.URI.URN + xrf.navigator.URI.file )
URI.external = URI.file && URI.URN != document.location.origin + document.location.pathname
URI.hasPos = URI.hash.pos ? true : false
URI.duplicatePos = URI.source == xrf.navigator.URI.source && URI.hasPos
URI.hashChange = String(xrf.navigator.URI.fragment||"") != String(URI.fragment||"")
}else{
URI = url
url = URI.source
}
URI.last = xrf.navigator.URI
xrf.navigator.URI = URI
let {directory,file,fragment,fileExt} = URI;
@ -2219,7 +2603,7 @@ xrf.navigator.init = () => {
window.addEventListener('popstate', function (event){
if( xrf.navigator.updateHash.active ){ // ignore programmatic hash updates (causes infinite recursion)
xrf.navigator.to( document.location.href.replace(/.*\?/,'') )
xrf.navigator.to( xrf.navigator.URI.last )
}
})
@ -2347,7 +2731,7 @@ xrf.frag.href = function(v, opts){
let click = mesh.userData.XRF.href.exec = (e) => {
if( !mesh.material || !mesh.material.visible ) return // ignore invisible nodes
if( !mesh.material || !(mesh.material && mesh.material.visible) ) return // ignore invisible nodes
// update our values to the latest value (might be edited)
let URI = xrf.URI.template( mesh.userData.href, xrf.URI.vars.__object )
@ -2503,15 +2887,18 @@ xrf.frag.loop = function(v, opts){
xrf.frag.pos = function(v, opts){
let { frag, mesh, model, camera, scene, renderer, THREE} = opts
let pos = v
if( !scene )return
let pos = v
// spec: indirect coordinate using objectname: https://xrfragment.org/#navigating%203D
if( pos.x == undefined ){
let obj = scene.getObjectByName(v.string)
if( !obj ) return
pos = obj.position.clone()
obj.getWorldPosition(pos)
camera.position.copy(pos)
if( !obj ) return console.warn("#pos="+v.string+" not found")
obj.add(camera) // follow animation of targeted position
camera.position.set(0,0,0) // set playerheight
//let c = camera.rotation
//c.set( c.x, obj.rotation.y, c.z )
}else{
// spec: direct coordinate: https://xrfragment.org/#navigating%203D
camera.position.x = pos.x
@ -2524,6 +2911,7 @@ xrf.frag.pos = function(v, opts){
xrf.frag.pos.lastVector3 = camera.position.clone()
camera.updateMatrixWorld()
camera.getCam().updateMatrixWorld()
}
xrf.frag.pos.get = function(precision,randomize){
@ -2559,6 +2947,9 @@ xrf.addEventListener('reset', (opts) => {
xrf.frag.rot = function(v, opts){
let { frag, mesh, model, camera, scene, renderer, THREE} = opts
if( xrf.debug ) console.log("#rot.js: setting camera rotation to "+v.string)
if( !camera || !scene ) return
if( !model.isSRC ){
camera.rotation.set(
v.x * Math.PI / 180,
@ -2795,7 +3186,6 @@ xrf.frag.src.type['unknown'] = function( url, opts ){
xrf.frag.t = function(v, opts){
let { frag, mesh, model, camera, scene, renderer, THREE} = opts
// handle object media players
if( mesh && mesh.media ){
for( let i in mesh.media ) mesh.media[i].set("t",v)
@ -2845,13 +3235,14 @@ xrf.addEventListener('parseModel', (opts) => {
model.animations.map( (a) => mixer.duration = ( a.duration > mixer.duration ) ? a.duration : mixer.duration )
}
model.animations.map( (anim) => {
anim.optimize()
if( xrf.debug ) console.log("action: "+anim.name)
model.animations.map( (anim) => {
console.log("animation action: "+anim.name)
mixer.actions.push( mixer.clipAction( anim, model.scene ) )
})
mixer.play = (t) => {
let msg = `media fragment: ${t.x}-${t.y} seconds`
if( t.x > 49 ) msg += ", not frames (!)"
console.log(msg)
mixer.isPlaying = t.x !== undefined && t.x != t.y
mixer.updateLoop(t)
xrf.emit( mixer.isPlaying === false ? 'stop' : 'play',{isPlaying: mixer.isPlaying})
@ -3320,20 +3711,6 @@ xrf.sceneToTranscript = (scene, ignoreMesh ) => {
}
// switch camera when multiple cameras for url #mycameraname
xrf.addEventListener('dynamicKey', (opts) => {
// select active camera if any
let {id,match,v} = opts
match.map( (w) => {
w.nodes.map( (node) => {
if( node.isCamera ){
console.log("switching camera to cam: "+node.name)
xrf.model.camera = node
}
})
})
})
// switch camera when multiple cameras for url #mycameraname
xrf.addEventListener('navigateLoaded', (opts) => {
// select active camera if any
let {id,match,v,THREE} = opts
@ -3343,9 +3720,10 @@ xrf.addEventListener('navigateLoaded', (opts) => {
// Recursive function to traverse the graph
function traverseAndSetEnvMap(node, closestAncestorMaterialMap = null) {
// Check if the current node has a material
if (node.isMesh && node.material) {
if (node.isMesh && node.material ) {
if (node.material.map && closestAncestorMaterialMap) {
// If the node has a material map, set the closest ancestor material map
node.material = node.material.clone() // dont affect objects which share same material
node.material.envMap = closestAncestorMaterialMap;
}
}
@ -3396,6 +3774,8 @@ xrf.filter.scene = function(opts){
.sort(frag) // get (sorted) filters from XR Fragments
.process(frag,scene,opts) // show/hide things
if( !scene ) return
scene.visible = true // always enable scene
return scene
@ -3411,6 +3791,7 @@ xrf.filter.sort = function(frag){
// opts = {copyScene:true} in case you want a copy of the scene (not filter the current scene inplace)
xrf.filter.process = function(frag,scene,opts){
if( !scene || scene.children.length == 0 ) return
const cleanupKey = (k) => k.replace(/[-\*\/]/g,'')
let firstFilter = frag.filters.length ? frag.filters[0].filter.get() : false
const hasName = (m,name,filter) => m.name == name
@ -3582,7 +3963,7 @@ xrf.addEventListener('dynamicKeyValue', (opts) => {
xrf.frag.dynamic.material(v,opts) // check if fragment is an objectname
}
if( !xrf.URI.vars[ v.string ] ) return console.error(`'${v.string}' metadata-key not found in scene`)
if( !xrf.URI.vars[ v.string ] ) return // ignore non-template URI fragments
//if( xrf.URI.vars[ id ] && !match.length ) return console.error(`'${id}' object/tag/metadata-key not found in scene`)
if( xrf.debug ) console.log(`URI.vars[${id}] => '${v.string}'`)
@ -3663,7 +4044,7 @@ xrf.drawLineToMesh = (opts) => {
xrf.addEventListener('render', (opts) => {
// update focusline
let {time,model} = opts
if( !xrf.clock ) return
if( !xrf.clock || !xrf.focusLine ) return
xrf.focusLine.material.color.r = (1.0 + Math.sin( xrf.clock.getElapsedTime()*10 ))/2
xrf.focusLine.material.dashSize = 0.2 + 0.02*Math.sin( xrf.clock.getElapsedTime() )
xrf.focusLine.material.gapSize = 0.1 + 0.02*Math.sin( xrf.clock.getElapsedTime() *3 )
@ -3690,6 +4071,7 @@ let loadAudio = (mimetype) => function(url,opts){
let sound = isPositionalAudio ? new THREE.PositionalAudio( camera.listener)
: new THREE.Audio( camera.listener )
sound.isXRF = true
mesh.media = mesh.media || {}
mesh.media.audio = { set: (mediafragment,v) => mesh.media.audio[mediafragment] = v }
@ -3781,6 +4163,7 @@ xrf.addEventListener('reset', () => {
if( n.media && n.media.audio ){
if( n.media.audio.stop ) n.media.audio.stop()
if( n.media.audio.remove ) n.media.audio.remove()
n.remove()
}
})
})

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 30 KiB

View File

@ -80,12 +80,15 @@ value: draft-XRFRAGMENTS-leonvankammen-00
<h1 class="special" id="abstract">Abstract</h1>
<p>This draft is a specification for 4D URI&rsquo;s &amp; <a href="https://github.com/coderofsalvation/hypermediatic">hypermediatic</a> navigation, to enable a spatial web for hypermedia browsers with- or without a network-connection.<br>
<p>This draft is a specification for interactive URI-controllable 3D files, enabling <a href="https://github.com/coderofsalvation/hypermediatic">hypermediatic</a> navigation, to enable a spatial web for hypermedia browsers with- or without a network-connection.<br>
The specification uses <a href="https://www.w3.org/TR/media-frags/">W3C Media Fragments</a> and <a href="https://www.rfc-editor.org/rfc/rfc6570">URI Templates (RFC6570)</a> to promote spatial addressibility, sharing, navigation, filtering and databinding objects for (XR) Browsers.<br>
XR Fragments allows us to better use existing metadata inside 3D scene(files), by connecting it to proven technologies like <a href="https://en.wikipedia.org/wiki/URI_fragment">URI Fragments</a>.<br>
XR Fragments views spatial webs thru the lens of 3D scene URI&rsquo;s, rather than thru code(frameworks) or protocol-specific browsers (webbrowser e.g.).</p>
<blockquote>
<p>XR Fragments is a <b>Meta scene format</b> which leverages heuristic rules derived from any 3D scene or well-established 3D file formats, to extract meaningful features from scene hierarchies.<br>
These heuristics, enable features that are both meaningful and consistent across different scene representations, allowing <b>higher interop</b> between fileformats, 3D editors, viewers and game-engines.</p>
<p>Almost every idea in this document is demonstrated at <a href="https://xrfragment.org">https://xrfragment.org</a></p>
</blockquote>
<section data-matter="main">
@ -178,6 +181,150 @@ Instead of forcing authors to combine 3D/2D objects programmatically (publishing
<p>Traditional webbrowsers can become 4D document-ready by:</p>
<h1 id="the-xr-fragments-trinity">The XR Fragments Trinity</h1>
<p>XR Fragments utilizes URLs:</p>
<ol>
<li>for 3D viewers/browser to manipulate the camera or objects (via URLbar)</li>
<li>as <strong>implicit</strong> metadata to reference (nested) objects <strong>inside</strong> 3D scene-file (local and remote)</li>
<li>via <strong>explicit</strong> metadata (&lsquo;extras&rsquo;) <strong>inside</strong> 3D scene-files (interaction e.g.) or</li>
<li>[optionally for developers] via <strong>explicit</strong> metadata <strong>outside</strong> 3D scene-files (via <a href="https://en.wikipedia.org/wiki/Sidecar_file">sidecarfile</a>)</li>
</ol>
<h1 id="list-of-uri-fragments">List of URI Fragments</h1>
<table>
<thead>
<tr>
<th>fragment</th>
<th>type</th>
<th>example</th>
<th>info</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>#pos</code></td>
<td>vector3</td>
<td><code>#pos=0.5,0,0</code> <code>#pos=room</code> <code>#pos=cam2</code></td>
<td>positions/parents camera(rig) (or XR floor) to xyz-coord/object/camera</td>
</tr>
<tr>
<td><code>#rot</code></td>
<td>vector3</td>
<td><code>#rot=0,90,0</code></td>
<td>rotates camera to xyz-coord 0.5,0,0</td>
</tr>
<tr>
<td><a href="https://www.w3.org/TR/media-frags/">Media Fragments</a></td>
<td><a href="#media%20fragments%20and%20datatypes">media fragment</a></td>
<td><code>#t=0,2&amp;loop</code></td>
<td>play (and loop) 3D animation from 0 seconds till 2 seconds</td>
</tr>
</tbody>
</table>
<h1 id="list-of-explicit-metadata">List of *<em>explicit</em> metadata</h1>
<p>These are the possible &lsquo;extras&rsquo; for 3D nodes and sidecar-files</p>
<table>
<thead>
<tr>
<th>key</th>
<th>type</th>
<th>example (JSON)</th>
<th>function</th>
<th>existing compatibility</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>href</code></td>
<td>string</td>
<td><code>&quot;href&quot;: &quot;b.gltf&quot;</code></td>
<td>XR teleport</td>
<td>custom property in 3D fileformats</td>
</tr>
<tr>
<td><code>src</code></td>
<td>string</td>
<td><code>&quot;src&quot;: &quot;#cube&quot;</code></td>
<td>XR embed / teleport</td>
<td>custom property in 3D fileformats</td>
</tr>
<tr>
<td><code>tag</code></td>
<td>string</td>
<td><code>&quot;tag&quot;: &quot;cubes geo&quot;</code></td>
<td>tag object (for filter-use / XRWG highlighting)</td>
<td>custom property in 3D fileformats</td>
</tr>
<tr>
<td><code>#</code></td>
<td>string</td>
<td><code>&quot;#&quot;: &quot;#mypreset</code></td>
<td>trigger default fragment on load</td>
<td>custom property in 3D fileformats</td>
</tr>
</tbody>
</table>
<blockquote>
<p>Supported popular compatible 3D fileformats: <code>.gltf</code>, <code>.obj</code>, <code>.fbx</code>, <code>.usdz</code>, <code>.json</code> (THREE.js), <code>.dae</code> and so on.</p>
</blockquote>
<h2 id="sidecar-file">Sidecar-file</h2>
<blockquote>
<p>NOTE: sidecar-files break the portability of XR (Fragments) experiences, therefore side-car files are discouraged for consumer usage/sharing. However, they can accomodate developers or applications who (for whatever reason) must not modify the 3D scene-file (a <code>.glb</code> e.g.).</p>
</blockquote>
<p>For developers, sidecar-file can allow for defining <strong>explicit</strong> XR Fragments metadata, outside of the 3D file.<br>
This can be done via a JSON-pointers <a href="https://www.rfc-editor.org/rfc/rfc6901">RFC6901</a> in a JSON <a href="https://en.wikipedia.org/wiki/Sidecar_file">sidecar-file</a>:</p>
<ul>
<li>experience.glb</li>
<li>experience.json</li>
</ul>
<pre><code class="language-json">{
&quot;/&quot;:{
&quot;#&quot;: &quot;#-penguin&quot;,
&quot;aria-description&quot;: &quot;description of scene&quot;,
},
&quot;/room/chair&quot;: {
&quot;href&quot;: &quot;#penguin&quot;
}
}
</code></pre>
<blockquote>
<p>This would mean: hide object(s) with name or <code>tag</code>-value &lsquo;penguin&rsquo; upon scene-load, and show it when the user clicks the chair</p>
</blockquote>
<p>So after loading <code>experience.glb</code> the existence of <code>experience.json</code> is detected, to apply the explicit metadata.<br>
The sidecar will define (or <strong>override</strong> already existing) extras, which can be handy for multi-user platforms (offer 3D scene customization/personalization to users).</p>
<blockquote>
<p>In THREE.js-code this would boil down to:</p>
</blockquote>
<pre><code class="language-javascript"> scene.userData['#'] = &quot;#chair&amp;penguin&quot;
scene.userData['aria-description'] = &quot;description of scene&quot;
scene.getObjectByName(&quot;room&quot;).getObjectByName(&quot;chair&quot;).userData.href = &quot;#penguin&quot;
// now the XR Fragments parser can process the XR Fragments userData 'extras' in the scene
</code></pre>
<h1 id="hypermediatic-feedbackloop-for-xr-browsers">Hypermediatic FeedbackLoop for XR browsers</h1>
<p><code>href</code> metadata traditionally implies <strong>click</strong> AND <strong>navigate</strong>, however XR Fragments adds stateless <strong>click</strong> (<code>xrf://#....</code>) or <strong>navigate</strong> (<code>xrf://#pos=...</code>)
@ -358,104 +505,9 @@ For example, to render a portal with a preview-version of the scene, create an 3
<p>It also allows <strong>sourceportation</strong>, which basically means the enduser can teleport to the original XR Document of an <code>src</code> embedded object, and see a visible connection to the particular embedded object. Basically an embedded link becoming an outbound link by activating it.</p>
</blockquote>
<h1 id="list-of-uri-fragments">List of URI Fragments</h1>
<h2 id="level2-implicit-uri-fragments">Level2: Implicit URI Fragments</h2>
<table>
<thead>
<tr>
<th>fragment</th>
<th>type</th>
<th>example</th>
<th>info</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>#pos</code></td>
<td>vector3</td>
<td><code>#pos=0.5,0,0</code></td>
<td>positions camera (or XR floor) to xyz-coord 0.5,0,0,</td>
</tr>
<tr>
<td><code>#rot</code></td>
<td>vector3</td>
<td><code>#rot=0,90,0</code></td>
<td>rotates camera to xyz-coord 0.5,0,0</td>
</tr>
<tr>
<td><a href="https://www.w3.org/TR/media-frags/">Media Fragments</a></td>
<td><a href="#media%20fragments%20and%20datatypes">media fragment</a></td>
<td><code>#t=0,2&amp;loop</code></td>
<td>play (and loop) 3D animation from 0 seconds till 2 seconds</td>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
<td>but can also crop, animate &amp; configure uv-coordinates/shader uniforms</td>
</tr>
</tbody>
</table>
<h2 id="list-of-metadata-for-3d-nodes">List of metadata for 3D nodes</h2>
<table>
<thead>
<tr>
<th>key</th>
<th>type</th>
<th>example (JSON)</th>
<th>function</th>
<th>existing compatibility</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>href</code></td>
<td>string</td>
<td><code>&quot;href&quot;: &quot;b.gltf&quot;</code></td>
<td>XR teleport</td>
<td>custom property in 3D fileformats</td>
</tr>
<tr>
<td><code>src</code></td>
<td>string</td>
<td><code>&quot;src&quot;: &quot;#cube&quot;</code></td>
<td>XR embed / teleport</td>
<td>custom property in 3D fileformats</td>
</tr>
<tr>
<td><code>tag</code></td>
<td>string</td>
<td><code>&quot;tag&quot;: &quot;cubes geo&quot;</code></td>
<td>tag object (for filter-use / XRWG highlighting)</td>
<td>custom property in 3D fileformats</td>
</tr>
<tr>
<td><code>#</code></td>
<td>string</td>
<td><code>&quot;#&quot;: &quot;#mypreset</code></td>
<td>trigger default fragment on load</td>
<td>custom property in 3D fileformats</td>
</tr>
</tbody>
</table>
<blockquote>
<p>Supported popular compatible 3D fileformats: <code>.gltf</code>, <code>.obj</code>, <code>.fbx</code>, <code>.usdz</code>, <code>.json</code> (THREE.js), <code>.dae</code> and so on.</p>
</blockquote>
<h2 id="fragment-to-metadata-mapping">Fragment-to-metadata mapping</h2>
<p>These are automatic fragment-to-metadata mappings, which only trigger if the 3D scene metadata matches a specific identifier:</p>
<p>These fragments are derived from objectnames (or their extras) within a 3D scene, and trigger certain actions when evaluated by the browser:</p>
<table>
<thead>
@ -474,7 +526,7 @@ For example, to render a portal with a preview-version of the scene, create an 3
<td><code>#&lt;preset&gt;</code></td>
<td>string</td>
<td><code>#cubes</code></td>
<td>evaluates preset (<code>#foo&amp;bar</code>) defined in 3D Object metadata (<code>#cubes: #foo&amp;bar</code> e.g.) while URL-browserbar reflects <code>#cubes</code>. Only works when metadata-key starts with <code>#</code></td>
<td>evaluates preset (<code>#foo&amp;bar</code>) when a scene contains extra (<code>#cubes: #foo&amp;bar</code> e.g.) while URL-browserbar reflects <code>#cubes</code>. Only works when metadata-key starts with <code>#</code></td>
</tr>
<tr>
@ -493,14 +545,6 @@ For example, to render a portal with a preview-version of the scene, create an 3
<td>will reset (<code>!</code>), show/focus or hide (<code>-</code>) focus object(s) with <code>tag: person</code> or name <code>person</code> by looking up XRWG (<code>*</code>=including children)</td>
</tr>
<tr>
<td><strong>CAMERASWITCH</strong></td>
<td><code>#&lt;cameraname&gt;</code></td>
<td>string</td>
<td><code>#cam01</code></td>
<td>sets camera with name <code>cam01</code> as active camera</td>
</tr>
<tr>
<td><strong>MATERIALUPDATE</strong></td>
<td><code>#&lt;tag_or_objectname&gt;[*]=&lt;materialname&gt;</code></td>
@ -701,8 +745,14 @@ For example, to render a portal with a preview-version of the scene, create an 3
<tbody>
<tr>
<td><b>#pos</b>=0,0,0</td>
<td>vector3 or string</td>
<td>(re)position camera based on coordinates directly, or indirectly using objectname (its worldposition)</td>
<td>vector3</td>
<td>position camera to 0,0,0 (+userheight in VR)</td>
</tr>
<tr>
<td><b>#pos</b>=room</td>
<td>string</td>
<td>position camera to position of objectname <code>room</code> (+userheight in VR)</td>
</tr>
<tr>
@ -715,15 +765,18 @@ For example, to render a portal with a preview-version of the scene, create an 3
<p><a href="https://github.com/coderofsalvation/xrfragment/blob/main/src/3rd/js/three/xrf/pos.js">» example implementation</a><br>
<a href="https://github.com/coderofsalvation/xrfragment/issues/5">» discussion</a><br></p>
<p>Here&rsquo;s the basic <strong>level1</strong> flow (with optional level2 features):</p>
<ol>
<li>the Y-coordinate of <code>pos</code> identifies the floorposition. This means that desktop-projections usually need to add 1.5m (average person height) on top (which is done automatically by VR/AR headsets).</li>
<li>the Y-coordinate of <code>pos</code> identifies the floorposition. This means that desktop-projections usually need to add 1.5m (average person height) on top (which is done automatically by VR/AR headsets), except in case of camera-switching.</li>
<li>set the position of the camera accordingly to the vector3 values of <code>#pos</code></li>
<li><code>rot</code> sets the rotation of the camera (only for non-VR/AR headsets)</li>
<li>mediafragment <code>t</code> in the top-URL sets the playbackspeed and animation-range of the global scene animation</li>
<li>if the referenced <code>#pos</code> object is animated, parent the current camera to that object (so it animates too)</li>
<li><code>rot</code> sets the rotation of the camera (only for non-VR/AR headsets, however a camera-value overrules this)</li>
<li><strong>level2</strong>: mediafragment <code>t</code> in the top-URL sets the playbackspeed and animation-range of the global scene animation</li>
<li>before scene load: the scene is cleared</li>
<li>after scene load: in case the scene (rootnode) contains an <code>#</code> default view with a fragment value: execute non-positional fragments via the hashbus (no top-level URL change)</li>
<li>after scene load: in case the scene (rootnode) contains an <code>#</code> default view with a fragment value: execute positional fragment via the hashbus + update top-level URL</li>
<li>in case of no default <code>#</code> view on the scene (rootnode), default player(rig) position <code>0,0,0</code> is assumed.</li>
<li><strong>level2</strong>: after scene load: in case the scene (rootnode) contains an <code>#</code> default view with a fragment value: execute non-positional fragments via the hashbus (no top-level URL change)</li>
<li><strong>level2</strong>: after scene load: in case the scene (rootnode) contains an <code>#</code> default view with a fragment value: execute positional fragment via the hashbus + update top-level URL</li>
<li><strong>level2</strong>: in case of no default <code>#</code> view on the scene (rootnode), default player(rig) position <code>0,0,0</code> is assumed.</li>
<li>in case a <code>href</code> does not mention any <code>pos</code>-coordinate, the current position will be assumed</li>
</ol>
@ -1281,90 +1334,105 @@ The XR Fragment-compatible browser can let the enduser access visual-meta(data)-
<p>Environment mapping is crucial for creating realistic reflections and lighting effects on 3D objects.
To apply environment mapping efficiently in a 3D scene, traverse the scene graph and assign each object&rsquo;s environment map based on the nearest ancestor&rsquo;s texture map. This ensures that objects inherit the correct environment mapping from their closest parent with a texture, enhancing the visual consistency and realism.</p>
<p>&rdquo;&ldquo;&rdquo;
+&mdash;&mdash;&mdash;&mdash;&mdash;&mdash;&mdash;&mdash;&mdash;&mdash;&ndash;+<br>
| |<br>
| index.usdz |<br>
| │ |<br>
| └── ◻ sphere (texture:foo) |
| └ ◻ cube (texture:bar) | envMap = foo
<pre><code> +--------------------------------+
| |
| index.usdz |
| │ |
| └── ◻ sphere (texture:foo) |
| └ ◻ cube (texture:bar) | envMap = foo
| └ ◻ cylinder | envMap = bar
+&mdash;&mdash;&mdash;&mdash;&mdash;&mdash;&mdash;&mdash;&mdash;&mdash;&ndash;+</p>
<pre><code>
Most 3D viewers apply one and the same environment map for various models, however this logic
allows a more natural &amp; automatic strategy for reflection mapping.
# Transclusion (broken link) resolution
In spirit of Ted Nelson's 'transclusion resolution', there's a soft-mechanism to harden links &amp; minimize broken links in various ways:
1. defining a different transport protocol (https vs ipfs or DAT) in `src` or `href` values can make a difference
2. mirroring files on another protocol using (HTTP) errorcode tags in `src` or `href` properties
3. in case of `src`: nesting a copy of the embedded object in the placeholder object (`embeddedObject`) will not be replaced when the request fails
&gt; due to the popularity, maturity and extensiveness of HTTP codes for client/server communication, non-HTTP protocols easily map to HTTP codes (ipfs ERR_NOT_FOUND maps to 404 e.g.)
For example:
+--------------------------------+
</code></pre>
<p>+────────────────────────────────────────────────────────+
<p>Most 3D viewers apply one and the same environment map for various models, however this logic
allows a more natural &amp; automatic strategy for reflection mapping:</p>
<ol>
<li>traverse the scene graph depth-first</li>
<li>remember the most recent parentnode (P) with a texture material</li>
<li>for every non-root node with a texture material
3.1 clone that material (as materials might be shared across objects)
3.2 set the environmentmap to the last known parent texture (P)</li>
</ol>
<h1 id="transclusion-broken-link-resolution">Transclusion (broken link) resolution</h1>
<p>In spirit of Ted Nelson&rsquo;s &lsquo;transclusion resolution&rsquo;, there&rsquo;s a soft-mechanism to harden links &amp; minimize broken links in various ways:</p>
<ol>
<li>defining a different transport protocol (https vs ipfs or DAT) in <code>src</code> or <code>href</code> values can make a difference</li>
<li>mirroring files on another protocol using (HTTP) errorcode tags in <code>src</code> or <code>href</code> properties</li>
<li>in case of <code>src</code>: nesting a copy of the embedded object in the placeholder object (<code>embeddedObject</code>) will not be replaced when the request fails</li>
</ol>
<blockquote>
<p>due to the popularity, maturity and extensiveness of HTTP codes for client/server communication, non-HTTP protocols easily map to HTTP codes (ipfs ERR_NOT_FOUND maps to 404 e.g.)</p>
</blockquote>
<p>For example:</p>
<pre><code> +────────────────────────────────────────────────────────+
│ │
│ index.gltf │
│ │ │
│ │ #: #-offlinetext │
│ │ │
│ ├── ◻ buttonA │
│ │ └ href: <a href="http://foo.io/campagne.fbx">http://foo.io/campagne.fbx</a>
│ │ └ href: http://foo.io/campagne.fbx │
│ │ └ href@404: ipfs://foo.io/campagne.fbx │
│ │ └ href@400: #clienterrortext │
│ │ └ ◻ offlinetext │
│ │ │
│ └── ◻ embeddedObject &lt;&mdash;&mdash;&mdash; the meshdata inside embeddedObject will (not)
│ └ src: <a href="https://foo.io/bar.gltf">https://foo.io/bar.gltf</a> │ be flushed when the request (does not) succeed.
│ └ src@404: <a href="http://foo.io/bar.gltf">http://foo.io/bar.gltf</a> │ So worstcase the 3D data (of the time of publishing index.gltf)
│ └ src@400: <a href="https://archive.org/l2kj43.gltf">https://archive.org/l2kj43.gltf</a> │ will be displayed.
│ └── ◻ embeddedObject &lt;--------- the meshdata inside embeddedObject will (not)
│ └ src: https://foo.io/bar.gltf │ be flushed when the request (does not) succeed.
│ └ src@404: http://foo.io/bar.gltf │ So worstcase the 3D data (of the time of publishing index.gltf)
│ └ src@400: https://archive.org/l2kj43.gltf │ will be displayed.
│ │
+────────────────────────────────────────────────────────+</p>
<pre><code>
# Topic-based index-less Webrings
As hashtags in URLs map to the XWRG, `href`-values can be used to promote topic-based index-less webrings.&lt;br&gt;
Consider 3D scenes linking to eachother using these `href` values:
* `href: schoolA.edu/projects.gltf#math`
* `href: schoolB.edu/projects.gltf#math`
* `href: university.edu/projects.gltf#math`
These links would all show visible links to math-tagged objects in the scene.&lt;br&gt;
To filter out non-related objects one could take it a step further using filters:
* `href: schoolA.edu/projects.gltf#math&amp;-topics math`
* `href: schoolB.edu/projects.gltf#math&amp;-courses math`
* `href: university.edu/projects.gltf#math&amp;-theme math`
&gt; This would hide all object tagged with `topic`, `courses` or `theme` (including math) so that later only objects tagged with `math` will be visible
This makes spatial content multi-purpose, without the need to separate content into separate files, or show/hide things using a complex logiclayer like javascript.
# URI Templates (RFC6570)
XR Fragments adopts Level1 URI **Fragment** expansion to provide safe interactivity.&lt;br&gt;
The following demonstrates a simple video player:
+────────────────────────────────────────────────────────+
</code></pre>
<p>+─────────────────────────────────────────────+
<h1 id="topic-based-index-less-webrings">Topic-based index-less Webrings</h1>
<p>As hashtags in URLs map to the XWRG, <code>href</code>-values can be used to promote topic-based index-less webrings.<br>
Consider 3D scenes linking to eachother using these <code>href</code> values:</p>
<ul>
<li><code>href: schoolA.edu/projects.gltf#math</code></li>
<li><code>href: schoolB.edu/projects.gltf#math</code></li>
<li><code>href: university.edu/projects.gltf#math</code></li>
</ul>
<p>These links would all show visible links to math-tagged objects in the scene.<br>
To filter out non-related objects one could take it a step further using filters:</p>
<ul>
<li><code>href: schoolA.edu/projects.gltf#math&amp;-topics math</code></li>
<li><code>href: schoolB.edu/projects.gltf#math&amp;-courses math</code></li>
<li><code>href: university.edu/projects.gltf#math&amp;-theme math</code></li>
</ul>
<blockquote>
<p>This would hide all object tagged with <code>topic</code>, <code>courses</code> or <code>theme</code> (including math) so that later only objects tagged with <code>math</code> will be visible</p>
</blockquote>
<p>This makes spatial content multi-purpose, without the need to separate content into separate files, or show/hide things using a complex logiclayer like javascript.</p>
<h1 id="uri-templates-rfc6570">URI Templates (RFC6570)</h1>
<p>XR Fragments adopts Level1 URI <strong>Fragment</strong> expansion to provide safe interactivity.<br>
The following demonstrates a simple video player:</p>
<pre><code>
+─────────────────────────────────────────────+
│ │
│ foo.usdz │<br>
│ │ │<br>
│ │ │<br>
│ foo.usdz │
│ │ │
│ │ │
│ ├── ◻ stopbutton │
│ │ ├ #: #-stopbutton │
│ │ └ href: #player=stop&amp;-stopbutton │ (stop and hide stop-button)
│ │ │<br>
│ │ │
│ └── ◻ plane │
│ ├ play: #t=l:0,10 │
│ ├ stop: #t=0,0 │
@ -1372,9 +1440,10 @@ The following demonstrates a simple video player:
│ └ src: cat.mp4#{player} │
│ │
│ │
+─────────────────────────────────────────────+</p>
+─────────────────────────────────────────────+
<p>&rdquo;`</p>
</code></pre>
<h1 id="additional-scene-metadata">Additional scene metadata</h1>
@ -1499,11 +1568,123 @@ Therefore a 2-button navigation-interface is the bare minimum interface:</p>
<li>the TTS reads the href-value (and/or aria-description if available)</li>
</ol>
<h2 id="overlap-with-fileformat-specific-extensions">Overlap with fileformat-specific extensions</h2>
<p>Some 3D scene-fileformats have support for extensions.
What if the functionality of those overlap?
For example, GLTF has the <code>OMI_LINK</code> extension which might overlap with XR Fragment&rsquo;s <code>href</code>:</p>
<blockquote>
<p>Priority Order and Precedence, otherwise fallback applies</p>
</blockquote>
<p>1.<strong>Extensions Take Precedence</strong>: Since glTF-specific extensions are designed with the formats
specific needs and optimizations in mind, they should take precedence over extras metadata
in cases where both contain overlapping functionality.
This approach aligns with the idea that extensions are more likely to be interpreted uniformly by glTF-compatible software.</p>
<ol start="2">
<li><strong>Fallback Fall-through Mechanism</strong>:
If a glTF implementation does not support a particular extension, the (XRF) extras field can serve as a fallback. This way, metadata provided in extras can still be useful for applications that don&rsquo;t handle certain extensions.</li>
</ol>
<blockquote>
<p><strong>Example 1</strong> In case of the OMI_LINK glTF extension (<code>href: https://nlnet.nl</code>) and an XR Fragment (<code>href: #pos=otherroom</code> or <code>href: otherplanet.glb</code>), it is clear that <code>https://nlnet.nl</code> should open in a browsertab, whereas the XR Fragment links should teleport the user. If the OMI_LINK contains an XR Fragment (<code>#pos=</code> e.g.) a teleport should be performed only (and other [overlapping] metadata should be ignored).</p>
<p><strong>Example 2</strong> If an Extensions uses XR Fragments in URI&rsquo;s (<code>href: #pos=otherroom</code> or <code>href: xrf://-walls</code> in OMI_LINK e.g.), then perform them according to XR Fragment spec (teleport user). But only once: ignore further overlapping metadata for that usecase.</p>
</blockquote>
<h2 id="vendor-prefixes">Vendor Prefixes</h2>
<p>Vendor-specific metadata in a 3D scenefiles, are similar to vendor-specific <a href="https://en.wikipedia.org/wiki/CSS#Vendor_prefixes">CSS-prefixes</a> (<code>-moz-opacity: 0.2</code> e.g.).
This allows popular 3D engines/frameworks, to initialize specific features when loading a scene/object, in a progressive enhanced way.</p>
<p>Vendor Prefixes allows embedding 3D engines/framework-specific features a 3D file via metadata:</p>
<table>
<thead>
<tr>
<th>what</th>
<th>XR metadata</th>
<th>Lowest common denominator</th>
</tr>
</thead>
<tbody>
<tr>
<td>CSS</td>
<td>vendor-agnostic</td>
<td>2D canvas + object referencing/styling</td>
</tr>
<tr>
<td>XR Fragments</td>
<td>vendor-agnostic</td>
<td>3D camera + object(file) load/embed/click/referencing</td>
</tr>
<tr>
<td>Vendor prefixs</td>
<td>vendor-<strong>specific</strong></td>
<td>Specialized Entity-Component implementation</td>
</tr>
</tbody>
</table>
<blockquote>
<p>Why? Because not all XR interactions can/should be solved/standardized by embedding XR Fragments into any 3D file.
The lowest common denominator between 3D engines is the &lsquo;entity&rsquo;-part of their entity-component-system (ECS). The &lsquo;component&rsquo;-part can be progressively enhanced via vendor prefixes.</p>
</blockquote>
<p>For example, the following metadata can be added to a .glb file, to make an object grabbable in AFRAME:</p>
<pre><code>+────────────────────────────────────────────────────────────────────────────────────────────────────────+
│ http://y.io/z.glb | AFRAME app │
│-----------------------------------------------+--------------------------------------------------------│
│ | │
│ | after loading the glb, john can be placed into the │
│ +-[3D mesh]-+ | castle via hands, because the author added metadata to │
│ | / \ | | john via either: │
│ | / \ | | │
│ | / \ | | 1. Blender (custom property-box, no plugins needed) │
│ | |_____| | | │
│ +-----│-----+ | 2. javascript-code: │
│ │ | │
│ ├─ name: castle | for( var com in this.el.components ){ │
│ └─ tag: house baroque | this.el.object3D.userData[`-AFRAME-${com}`] = '' │
│ | } │
│ [3D mesh-+ | // save to z.glb in AFRAME inspector │
│ | ├─ name: john | │
│ | O ├─ age: 23 | │
│ | /|\ ├─ -aframe-grabbable: '' | &gt; inits 'grabbable' component on object john │
│ | / \ ├─ -aframe-material.color: '#F0A' | &gt; inits 'material' component on object john │
│ | ├─ -aframe-text.value: '{name}{age}'| &gt; inits 'text' component (*) with value 'john' │
│ | ├─ -three-material.fog: false | &gt; changes material settings in THREE.js app │
│ | ├─ -godot-Label3D.text: '{name}{age}'| &gt; inits 'Label3D' component (*) in Godot │
│ +--------+ | │
│ | │
├─ -GODOT-version: '4.3' | &gt; exporters/authors can report targeted version │
├─ -AFRAME-version: '1.6.0' | and (optionally) hint component-repo│
├─ -AFRAME-info: 'https://git.benetou.fr/comps' │
│ | │
+────────────────────────────────────────────────────────────────────────────────────────────────────────+
</code></pre>
<ul>
<li>key/value syntax: -<code>&lt;vendorname&gt;</code>-<code>&lt;component|version&gt;</code>.<code>&lt;key&gt;</code> <code>[string/boolean/float/int]</code>-value</li>
</ul>
<p>String-templatevalues are evaluated as per <a href="https://www.rfc-editor.org/rfc/rfc6570">URI Templates (RFC6570)</a> Level 1.</p>
<blockquote>
<p>This &lsquo;separating of mechanism from policy&rsquo; (unix rule) does <strong>somewhat</strong> break portability of an XR experience, but still prevents (E-waste of) handcoded virtual worlds. It allows for (XR experience) metadata to survive in future 3D engines and scene-fileformats.</p>
</blockquote>
<h1 id="security-considerations">Security Considerations</h1>
<p>The only dynamic parts are <a href="https://www.w3.org/TR/media-frags/">W3C Media Fragments</a> and <a href="https://www.rfc-editor.org/rfc/rfc6570">URI Templates (RFC6570)</a>.<br>
The use of URI Templates is limited to pre-defined variables and Level0 fragments-expansion only, which makes it quite safe.<br>
In fact, it is much safer than relying on a scripting language (javascript) which can change URN too.</p>
n fact, it is much safer than relying on a scripting language (javascript) which can change URN too.</p>
<h1 id="faq">FAQ</h1>

View File

@ -93,11 +93,14 @@ value: draft-XRFRAGMENTS-leonvankammen-00
.# Abstract
This draft is a specification for 4D URI's & [hypermediatic](https://github.com/coderofsalvation/hypermediatic) navigation, to enable a spatial web for hypermedia browsers with- or without a network-connection.<br>
This draft is a specification for interactive URI-controllable 3D files, enabling [hypermediatic](https://github.com/coderofsalvation/hypermediatic) navigation, to enable a spatial web for hypermedia browsers with- or without a network-connection.<br>
The specification uses [W3C Media Fragments](https://www.w3.org/TR/media-frags/) and [URI Templates (RFC6570)](https://www.rfc-editor.org/rfc/rfc6570) to promote spatial addressibility, sharing, navigation, filtering and databinding objects for (XR) Browsers.<br>
XR Fragments allows us to better use existing metadata inside 3D scene(files), by connecting it to proven technologies like [URI Fragments](https://en.wikipedia.org/wiki/URI_fragment).<br>
XR Fragments views spatial webs thru the lens of 3D scene URI's, rather than thru code(frameworks) or protocol-specific browsers (webbrowser e.g.).
> XR Fragments is a <b>Meta scene format</b> which leverages heuristic rules derived from any 3D scene or well-established 3D file formats, to extract meaningful features from scene hierarchies.<br>
These heuristics, enable features that are both meaningful and consistent across different scene representations, allowing <b>higher interop</b> between fileformats, 3D editors, viewers and game-engines.
> Almost every idea in this document is demonstrated at [https://xrfragment.org](https://xrfragment.org)
{mainmatter}
@ -185,6 +188,77 @@ Below you can see how this translates back into good-old URLs:
Traditional webbrowsers can become 4D document-ready by:
# The XR Fragments Trinity
XR Fragments utilizes URLs:
1. for 3D viewers/browser to manipulate the camera or objects (via URLbar)
2. as **implicit** metadata to reference (nested) objects **inside** 3D scene-file (local and remote)
3. via **explicit** metadata ('extras') **inside** 3D scene-files (interaction e.g.) or
4. [optionally for developers] via **explicit** metadata **outside** 3D scene-files (via [sidecarfile](https://en.wikipedia.org/wiki/Sidecar_file))
# List of URI Fragments
| fragment | type | example | info |
|-------------------|------------|--------------------|----------------------------------------------------------------------|
| `#pos` | vector3 | `#pos=0.5,0,0` `#pos=room` `#pos=cam2` | positions/parents camera(rig) (or XR floor) to xyz-coord/object/camera |
| `#rot` | vector3 | `#rot=0,90,0` | rotates camera to xyz-coord 0.5,0,0 |
| [Media Fragments](https://www.w3.org/TR/media-frags/) | [media fragment](#media%20fragments%20and%20datatypes) | `#t=0,2&loop` | play (and loop) 3D animation from 0 seconds till 2 seconds|
# List of **explicit* metadata
These are the possible 'extras' for 3D nodes and sidecar-files
| key | type | example (JSON) | function | existing compatibility |
|--------------|----------|------------------------|---------------------|----------------------------------------|
| `href` | string | `"href": "b.gltf"` | XR teleport | custom property in 3D fileformats |
| `src` | string | `"src": "#cube"` | XR embed / teleport | custom property in 3D fileformats |
| `tag` | string | `"tag": "cubes geo"` | tag object (for filter-use / XRWG highlighting) | custom property in 3D fileformats |
| `#` | string | `"#": "#mypreset` | trigger default fragment on load | custom property in 3D fileformats |
> Supported popular compatible 3D fileformats: `.gltf`, `.obj`, `.fbx`, `.usdz`, `.json` (THREE.js), `.dae` and so on.
## Sidecar-file
> NOTE: sidecar-files break the portability of XR (Fragments) experiences, therefore side-car files are discouraged for consumer usage/sharing. However, they can accomodate developers or applications who (for whatever reason) must not modify the 3D scene-file (a `.glb` e.g.).
For developers, sidecar-file can allow for defining **explicit** XR Fragments metadata, outside of the 3D file.<br>
This can be done via a JSON-pointers [RFC6901](https://www.rfc-editor.org/rfc/rfc6901) in a JSON [sidecar-file](https://en.wikipedia.org/wiki/Sidecar_file):
* experience.glb
* experience.json
```json
{
"/":{
"#": "#-penguin",
"aria-description": "description of scene",
},
"/room/chair": {
"href": "#penguin"
}
}
```
> This would mean: hide object(s) with name or `tag`-value 'penguin' upon scene-load, and show it when the user clicks the chair
So after loading `experience.glb` the existence of `experience.json` is detected, to apply the explicit metadata.<br>
The sidecar will define (or **override** already existing) extras, which can be handy for multi-user platforms (offer 3D scene customization/personalization to users).
> In THREE.js-code this would boil down to:
```javascript
scene.userData['#'] = "#chair&penguin"
scene.userData['aria-description'] = "description of scene"
scene.getObjectByName("room").getObjectByName("chair").userData.href = "#penguin"
// now the XR Fragments parser can process the XR Fragments userData 'extras' in the scene
```
# Hypermediatic FeedbackLoop for XR browsers
`href` metadata traditionally implies **click** AND **navigate**, however XR Fragments adds stateless **click** (`xrf://#....`) or **navigate** (`xrf://#pos=...`)
@ -277,37 +351,15 @@ For example, to render a portal with a preview-version of the scene, create an 3
> It also allows **sourceportation**, which basically means the enduser can teleport to the original XR Document of an `src` embedded object, and see a visible connection to the particular embedded object. Basically an embedded link becoming an outbound link by activating it.
## Level2: Implicit URI Fragments
# List of URI Fragments
| fragment | type | example | info |
|-------------------|------------|--------------------|----------------------------------------------------------------------|
| `#pos` | vector3 | `#pos=0.5,0,0` | positions camera (or XR floor) to xyz-coord 0.5,0,0, |
| `#rot` | vector3 | `#rot=0,90,0` | rotates camera to xyz-coord 0.5,0,0 |
| [Media Fragments](https://www.w3.org/TR/media-frags/) | [media fragment](#media%20fragments%20and%20datatypes) | `#t=0,2&loop` | play (and loop) 3D animation from 0 seconds till 2 seconds|
| | | | but can also crop, animate & configure uv-coordinates/shader uniforms |
## List of metadata for 3D nodes
| key | type | example (JSON) | function | existing compatibility |
|--------------|----------|------------------------|---------------------|----------------------------------------|
| `href` | string | `"href": "b.gltf"` | XR teleport | custom property in 3D fileformats |
| `src` | string | `"src": "#cube"` | XR embed / teleport | custom property in 3D fileformats |
| `tag` | string | `"tag": "cubes geo"` | tag object (for filter-use / XRWG highlighting) | custom property in 3D fileformats |
| `#` | string | `"#": "#mypreset` | trigger default fragment on load | custom property in 3D fileformats |
> Supported popular compatible 3D fileformats: `.gltf`, `.obj`, `.fbx`, `.usdz`, `.json` (THREE.js), `.dae` and so on.
## Fragment-to-metadata mapping
These are automatic fragment-to-metadata mappings, which only trigger if the 3D scene metadata matches a specific identifier:
These fragments are derived from objectnames (or their extras) within a 3D scene, and trigger certain actions when evaluated by the browser:
| |fragment | type | example | info |
|------|------------------|----------|-------------------|-------------------------------------------------------------------------------|
| **PRESET** | `#<preset>` | string | `#cubes` | evaluates preset (`#foo&bar`) defined in 3D Object metadata (`#cubes: #foo&bar` e.g.) while URL-browserbar reflects `#cubes`. Only works when metadata-key starts with `#` |
| **PRESET** | `#<preset>` | string | `#cubes` | evaluates preset (`#foo&bar`) when a scene contains extra (`#cubes: #foo&bar` e.g.) while URL-browserbar reflects `#cubes`. Only works when metadata-key starts with `#` |
| **FOCUS** | `#<tag_or_objectname>` | string | `#person` | (and show) object(s) with `tag: person` or name `person` (XRWG lookup) |
| **FILTERS** | `#[!][-]<tag_or_objectname>[*]` | string | `#person` (`#-person`) | will reset (`!`), show/focus or hide (`-`) focus object(s) with `tag: person` or name `person` by looking up XRWG (`*`=including children) |
| **CAMERASWITCH** | `#<cameraname>` | string | `#cam01` | sets camera with name `cam01` as active camera |
| **MATERIALUPDATE** | `#<tag_or_objectname>[*]=<materialname>` | string=string | `#car=metallic`| sets material of car to material with name `metallic` (`*`=including children)|
| | | | `#soldout*=halfopacity`| set material of objects tagged with `product` to material with name `metallic` |
| **VARIABLE UPDATE** | `#<variable>=<metadata-key>` | string=string | `#foo=bar` | sets [URI Template](https://www.rfc-editor.org/rfc/rfc6570) variable `foo` to the value `#t=0` from **existing** object metadata (`bar`:`#t=0` e.g.), This allows for reactive [URI Template](https://www.rfc-editor.org/rfc/rfc6570) defined in object metadata elsewhere (`src`:`://m.com/cat.mp4#{foo}` e.g., to play media using [media fragment URI](https://www.w3.org/TR/media-frags/#valid-uri)). NOTE: metadata-key should not start with `#` |
@ -376,21 +428,25 @@ Example URI's:
| fragment | type | functionality |
|----------|--------|------------------------------|
| <b>#pos</b>=0,0,0 | vector3 or string| (re)position camera based on coordinates directly, or indirectly using objectname (its worldposition) |
| <b>#pos</b>=0,0,0 | vector3 |position camera to 0,0,0 (+userheight in VR) |
| <b>#pos</b>=room | string | position camera to position of objectname `room` (+userheight in VR) |
| <b>#rot</b>=0,90,0 | vector3 | rotate camera |
[» example implementation](https://github.com/coderofsalvation/xrfragment/blob/main/src/3rd/js/three/xrf/pos.js)<br>
[» discussion](https://github.com/coderofsalvation/xrfragment/issues/5)<br>
1. the Y-coordinate of `pos` identifies the floorposition. This means that desktop-projections usually need to add 1.5m (average person height) on top (which is done automatically by VR/AR headsets).
Here's the basic **level1** flow (with optional level2 features):
1. the Y-coordinate of `pos` identifies the floorposition. This means that desktop-projections usually need to add 1.5m (average person height) on top (which is done automatically by VR/AR headsets), except in case of camera-switching.
2. set the position of the camera accordingly to the vector3 values of `#pos`
3. `rot` sets the rotation of the camera (only for non-VR/AR headsets)
4. mediafragment `t` in the top-URL sets the playbackspeed and animation-range of the global scene animation
5. before scene load: the scene is cleared
6. after scene load: in case the scene (rootnode) contains an `#` default view with a fragment value: execute non-positional fragments via the hashbus (no top-level URL change)
7. after scene load: in case the scene (rootnode) contains an `#` default view with a fragment value: execute positional fragment via the hashbus + update top-level URL
8. in case of no default `#` view on the scene (rootnode), default player(rig) position `0,0,0` is assumed.
9. in case a `href` does not mention any `pos`-coordinate, the current position will be assumed
3. if the referenced `#pos` object is animated, parent the current camera to that object (so it animates too)
4. `rot` sets the rotation of the camera (only for non-VR/AR headsets, however a camera-value overrules this)
5. **level2**: mediafragment `t` in the top-URL sets the playbackspeed and animation-range of the global scene animation
6. before scene load: the scene is cleared
7. **level2**: after scene load: in case the scene (rootnode) contains an `#` default view with a fragment value: execute non-positional fragments via the hashbus (no top-level URL change)
8. **level2**: after scene load: in case the scene (rootnode) contains an `#` default view with a fragment value: execute positional fragment via the hashbus + update top-level URL
9. **level2**: in case of no default `#` view on the scene (rootnode), default player(rig) position `0,0,0` is assumed.
10. in case a `href` does not mention any `pos`-coordinate, the current position will be assumed
Here's an ascii representation of a 3D scene-graph which contains 3D objects `◻` and their metadata:
@ -791,7 +847,7 @@ For usecases like importing/exporting/p2p casting a scene, the issue of external
Environment mapping is crucial for creating realistic reflections and lighting effects on 3D objects.
To apply environment mapping efficiently in a 3D scene, traverse the scene graph and assign each object's environment map based on the nearest ancestor's texture map. This ensures that objects inherit the correct environment mapping from their closest parent with a texture, enhancing the visual consistency and realism.
``````
```
+--------------------------------+
| |
| index.usdz |
@ -803,7 +859,13 @@ To apply environment mapping efficiently in a 3D scene, traverse the scene graph
```
Most 3D viewers apply one and the same environment map for various models, however this logic
allows a more natural & automatic strategy for reflection mapping.
allows a more natural & automatic strategy for reflection mapping:
1. traverse the scene graph depth-first
2. remember the most recent parentnode (P) with a texture material
3. for every non-root node with a texture material
3.1 clone that material (as materials might be shared across objects)
3.2 set the environmentmap to the last known parent texture (P)
# Transclusion (broken link) resolution
@ -955,11 +1017,89 @@ Therefore a 2-button navigation-interface is the bare minimum interface:
2. objects with href metadata can be activated via a key (enter on a keyboard)
3. the TTS reads the href-value (and/or aria-description if available)
## Overlap with fileformat-specific extensions
Some 3D scene-fileformats have support for extensions.
What if the functionality of those overlap?
For example, GLTF has the `OMI_LINK` extension which might overlap with XR Fragment's `href`:
> Priority Order and Precedence, otherwise fallback applies
1.**Extensions Take Precedence**: Since glTF-specific extensions are designed with the formats
specific needs and optimizations in mind, they should take precedence over extras metadata
in cases where both contain overlapping functionality.
This approach aligns with the idea that extensions are more likely to be interpreted uniformly by glTF-compatible software.
2. **Fallback Fall-through Mechanism**:
If a glTF implementation does not support a particular extension, the (XRF) extras field can serve as a fallback. This way, metadata provided in extras can still be useful for applications that don't handle certain extensions.
> **Example 1** In case of the OMI_LINK glTF extension (`href: https://nlnet.nl`) and an XR Fragment (`href: #pos=otherroom` or `href: otherplanet.glb`), it is clear that `https://nlnet.nl` should open in a browsertab, whereas the XR Fragment links should teleport the user. If the OMI_LINK contains an XR Fragment (`#pos=` e.g.) a teleport should be performed only (and other [overlapping] metadata should be ignored).
> **Example 2** If an Extensions uses XR Fragments in URI's (`href: #pos=otherroom` or `href: xrf://-walls` in OMI_LINK e.g.), then perform them according to XR Fragment spec (teleport user). But only once: ignore further overlapping metadata for that usecase.
## Vendor Prefixes
Vendor-specific metadata in a 3D scenefiles, are similar to vendor-specific [CSS-prefixes](https://en.wikipedia.org/wiki/CSS#Vendor_prefixes) (`-moz-opacity: 0.2` e.g.).
This allows popular 3D engines/frameworks, to initialize specific features when loading a scene/object, in a progressive enhanced way.
Vendor Prefixes allows embedding 3D engines/framework-specific features a 3D file via metadata:
| what | XR metadata | Lowest common denominator |
|------------------|---------------------|-------------------------------------------------------|
| CSS | vendor-agnostic | 2D canvas + object referencing/styling |
| XR Fragments | vendor-agnostic | 3D camera + object(file) load/embed/click/referencing |
| Vendor prefixs | vendor-**specific** | Specialized Entity-Component implementation |
> Why? Because not all XR interactions can/should be solved/standardized by embedding XR Fragments into any 3D file.
The lowest common denominator between 3D engines is the 'entity'-part of their entity-component-system (ECS). The 'component'-part can be progressively enhanced via vendor prefixes.
For example, the following metadata can be added to a .glb file, to make an object grabbable in AFRAME:
```
+────────────────────────────────────────────────────────────────────────────────────────────────────────+
│ http://y.io/z.glb | AFRAME app │
│-----------------------------------------------+--------------------------------------------------------│
│ | │
│ | after loading the glb, john can be placed into the │
│ +-[3D mesh]-+ | castle via hands, because the author added metadata to │
│ | / \ | | john via either: │
│ | / \ | | │
│ | / \ | | 1. Blender (custom property-box, no plugins needed) │
│ | |_____| | | │
│ +-----│-----+ | 2. javascript-code: │
│ │ | │
│ ├─ name: castle | for( var com in this.el.components ){ │
│ └─ tag: house baroque | this.el.object3D.userData[`-AFRAME-${com}`] = '' │
│ | } │
│ [3D mesh-+ | // save to z.glb in AFRAME inspector │
│ | ├─ name: john | │
│ | O ├─ age: 23 | │
│ | /|\ ├─ -aframe-grabbable: '' | > inits 'grabbable' component on object john │
│ | / \ ├─ -aframe-material.color: '#F0A' | > inits 'material' component on object john │
│ | ├─ -aframe-text.value: '{name}{age}'| > inits 'text' component (*) with value 'john' │
│ | ├─ -three-material.fog: false | > changes material settings in THREE.js app │
│ | ├─ -godot-Label3D.text: '{name}{age}'| > inits 'Label3D' component (*) in Godot │
│ +--------+ | │
│ | │
├─ -GODOT-version: '4.3' | > exporters/authors can report targeted version │
├─ -AFRAME-version: '1.6.0' | and (optionally) hint component-repo│
├─ -AFRAME-info: 'https://git.benetou.fr/comps' │
│ | │
+────────────────────────────────────────────────────────────────────────────────────────────────────────+
```
* key/value syntax: -`<vendorname>`-`<component|version>`.`<key>` `[string/boolean/float/int]`-value
String-templatevalues are evaluated as per [URI Templates (RFC6570)](https://www.rfc-editor.org/rfc/rfc6570) Level 1.
> This 'separating of mechanism from policy' (unix rule) does **somewhat** break portability of an XR experience, but still prevents (E-waste of) handcoded virtual worlds. It allows for (XR experience) metadata to survive in future 3D engines and scene-fileformats.
# Security Considerations
The only dynamic parts are [W3C Media Fragments](https://www.w3.org/TR/media-frags/) and [URI Templates (RFC6570)](https://www.rfc-editor.org/rfc/rfc6570).<br>
The use of URI Templates is limited to pre-defined variables and Level0 fragments-expansion only, which makes it quite safe.<br>
In fact, it is much safer than relying on a scripting language (javascript) which can change URN too.
n fact, it is much safer than relying on a scripting language (javascript) which can change URN too.
# FAQ

File diff suppressed because it is too large Load Diff

View File

@ -10,13 +10,16 @@
<workgroup>Jens &amp; Leon Internet Engineering Task Force</workgroup>
<abstract>
<t>This draft is a specification for 4D URI's &amp; <eref target="https://github.com/coderofsalvation/hypermediatic">hypermediatic</eref> navigation, to enable a spatial web for hypermedia browsers with- or without a network-connection.<br />
<t>This draft is a specification for interactive URI-controllable 3D files, enabling <eref target="https://github.com/coderofsalvation/hypermediatic">hypermediatic</eref> navigation, to enable a spatial web for hypermedia browsers with- or without a network-connection.<br />
The specification uses <eref target="https://www.w3.org/TR/media-frags/">W3C Media Fragments</eref> and <eref target="https://www.rfc-editor.org/rfc/rfc6570">URI Templates (RFC6570)</eref> to promote spatial addressibility, sharing, navigation, filtering and databinding objects for (XR) Browsers.<br />
XR Fragments allows us to better use existing metadata inside 3D scene(files), by connecting it to proven technologies like <eref target="https://en.wikipedia.org/wiki/URI_fragment">URI Fragments</eref>.<br />
XR Fragments views spatial webs thru the lens of 3D scene URI's, rather than thru code(frameworks) or protocol-specific browsers (webbrowser e.g.).</t>
<t>XR Fragments is a &lt;b&gt;Meta scene format&lt;/b&gt; which leverages heuristic rules derived from any 3D scene or well-established 3D file formats, to extract meaningful features from scene hierarchies.<br />
These heuristics, enable features that are both meaningful and consistent across different scene representations, allowing &lt;b&gt;higher interop&lt;/b&gt; between fileformats, 3D editors, viewers and game-engines.</t>
<t>Almost every idea in this document is demonstrated at <eref target="https://xrfragment.org">https://xrfragment.org</eref></t>
</abstract>
@ -106,6 +109,138 @@ But approaches things from a higherlevel feedbackloop/hypermedia browser-perspec
</blockquote><t>Traditional webbrowsers can become 4D document-ready by:</t>
</section>
<section anchor="the-xr-fragments-trinity"><name>The XR Fragments Trinity</name>
<t>XR Fragments utilizes URLs:</t>
<ol spacing="compact">
<li>for 3D viewers/browser to manipulate the camera or objects (via URLbar)</li>
<li>as <strong>implicit</strong> metadata to reference (nested) objects <strong>inside</strong> 3D scene-file (local and remote)</li>
<li>via <strong>explicit</strong> metadata ('extras') <strong>inside</strong> 3D scene-files (interaction e.g.) or</li>
<li>[optionally for developers] via <strong>explicit</strong> metadata <strong>outside</strong> 3D scene-files (via <eref target="https://en.wikipedia.org/wiki/Sidecar_file">sidecarfile</eref>)</li>
</ol>
</section>
<section anchor="list-of-uri-fragments"><name>List of URI Fragments</name>
<table>
<thead>
<tr>
<th>fragment</th>
<th>type</th>
<th>example</th>
<th>info</th>
</tr>
</thead>
<tbody>
<tr>
<td><tt>#pos</tt></td>
<td>vector3</td>
<td><tt>#pos=0.5,0,0</tt> <tt>#pos=room</tt> <tt>#pos=cam2</tt></td>
<td>positions/parents camera(rig) (or XR floor) to xyz-coord/object/camera</td>
</tr>
<tr>
<td><tt>#rot</tt></td>
<td>vector3</td>
<td><tt>#rot=0,90,0</tt></td>
<td>rotates camera to xyz-coord 0.5,0,0</td>
</tr>
<tr>
<td><eref target="https://www.w3.org/TR/media-frags/">Media Fragments</eref></td>
<td><eref target="#media%20fragments%20and%20datatypes">media fragment</eref></td>
<td><tt>#t=0,2&amp;loop</tt></td>
<td>play (and loop) 3D animation from 0 seconds till 2 seconds</td>
</tr>
</tbody>
</table></section>
<section anchor="list-of-explicit-metadata"><name>List of *<em>explicit</em> metadata</name>
<t>These are the possible 'extras' for 3D nodes and sidecar-files</t>
<table>
<thead>
<tr>
<th>key</th>
<th>type</th>
<th>example (JSON)</th>
<th>function</th>
<th>existing compatibility</th>
</tr>
</thead>
<tbody>
<tr>
<td><tt>href</tt></td>
<td>string</td>
<td><tt>&quot;href&quot;: &quot;b.gltf&quot;</tt></td>
<td>XR teleport</td>
<td>custom property in 3D fileformats</td>
</tr>
<tr>
<td><tt>src</tt></td>
<td>string</td>
<td><tt>&quot;src&quot;: &quot;#cube&quot;</tt></td>
<td>XR embed / teleport</td>
<td>custom property in 3D fileformats</td>
</tr>
<tr>
<td><tt>tag</tt></td>
<td>string</td>
<td><tt>&quot;tag&quot;: &quot;cubes geo&quot;</tt></td>
<td>tag object (for filter-use / XRWG highlighting)</td>
<td>custom property in 3D fileformats</td>
</tr>
<tr>
<td><tt>#</tt></td>
<td>string</td>
<td><tt>&quot;#&quot;: &quot;#mypreset</tt></td>
<td>trigger default fragment on load</td>
<td>custom property in 3D fileformats</td>
</tr>
</tbody>
</table><blockquote><t>Supported popular compatible 3D fileformats: <tt>.gltf</tt>, <tt>.obj</tt>, <tt>.fbx</tt>, <tt>.usdz</tt>, <tt>.json</tt> (THREE.js), <tt>.dae</tt> and so on.</t>
</blockquote>
<section anchor="sidecar-file"><name>Sidecar-file</name>
<blockquote><t>NOTE: sidecar-files break the portability of XR (Fragments) experiences, therefore side-car files are discouraged for consumer usage/sharing. However, they can accomodate developers or applications who (for whatever reason) must not modify the 3D scene-file (a <tt>.glb</tt> e.g.).</t>
</blockquote><t>For developers, sidecar-file can allow for defining <strong>explicit</strong> XR Fragments metadata, outside of the 3D file.<br />
This can be done via a JSON-pointers <eref target="https://www.rfc-editor.org/rfc/rfc6901">RFC6901</eref> in a JSON <eref target="https://en.wikipedia.org/wiki/Sidecar_file">sidecar-file</eref>:</t>
<ul spacing="compact">
<li>experience.glb</li>
<li>experience.json</li>
</ul>
<sourcecode type="json"><![CDATA[{
"/":{
"#": "#-penguin",
"aria-description": "description of scene",
},
"/room/chair": {
"href": "#penguin"
}
}
]]>
</sourcecode>
<blockquote><t>This would mean: hide object(s) with name or <tt>tag</tt>-value 'penguin' upon scene-load, and show it when the user clicks the chair</t>
</blockquote><t>So after loading <tt>experience.glb</tt> the existence of <tt>experience.json</tt> is detected, to apply the explicit metadata.<br />
The sidecar will define (or <strong>override</strong> already existing) extras, which can be handy for multi-user platforms (offer 3D scene customization/personalization to users).</t>
<blockquote><t>In THREE.js-code this would boil down to:</t>
</blockquote>
<sourcecode type="javascript"><![CDATA[ scene.userData['#'] = "#chair&penguin"
scene.userData['aria-description'] = "description of scene"
scene.getObjectByName("room").getObjectByName("chair").userData.href = "#penguin"
// now the XR Fragments parser can process the XR Fragments userData 'extras' in the scene
]]>
</sourcecode>
</section>
</section>
<section anchor="hypermediatic-feedbackloop-for-xr-browsers"><name>Hypermediatic FeedbackLoop for XR browsers</name>
<t><tt>href</tt> metadata traditionally implies <strong>click</strong> AND <strong>navigate</strong>, however XR Fragments adds stateless <strong>click</strong> (<tt>xrf://#....</tt>) or <strong>navigate</strong> (<tt>xrf://#pos=...</tt>)
as well (which allows many extra interactions which otherwise need a scripting language). This is known as <strong>hashbus</strong>-only events (see image above).</t>
@ -263,99 +398,9 @@ For example, to render a portal with a preview-version of the scene, create an 3
<li>src: <tt>https://otherworld.gltf#mainobject</tt></li>
</ul>
<blockquote><t>It also allows <strong>sourceportation</strong>, which basically means the enduser can teleport to the original XR Document of an <tt>src</tt> embedded object, and see a visible connection to the particular embedded object. Basically an embedded link becoming an outbound link by activating it.</t>
</blockquote></section>
<section anchor="list-of-uri-fragments"><name>List of URI Fragments</name>
<table>
<thead>
<tr>
<th>fragment</th>
<th>type</th>
<th>example</th>
<th>info</th>
</tr>
</thead>
<tbody>
<tr>
<td><tt>#pos</tt></td>
<td>vector3</td>
<td><tt>#pos=0.5,0,0</tt></td>
<td>positions camera (or XR floor) to xyz-coord 0.5,0,0,</td>
</tr>
<tr>
<td><tt>#rot</tt></td>
<td>vector3</td>
<td><tt>#rot=0,90,0</tt></td>
<td>rotates camera to xyz-coord 0.5,0,0</td>
</tr>
<tr>
<td><eref target="https://www.w3.org/TR/media-frags/">Media Fragments</eref></td>
<td><eref target="#media%20fragments%20and%20datatypes">media fragment</eref></td>
<td><tt>#t=0,2&amp;loop</tt></td>
<td>play (and loop) 3D animation from 0 seconds till 2 seconds</td>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
<td>but can also crop, animate &amp; configure uv-coordinates/shader uniforms</td>
</tr>
</tbody>
</table>
<section anchor="list-of-metadata-for-3d-nodes"><name>List of metadata for 3D nodes</name>
<table>
<thead>
<tr>
<th>key</th>
<th>type</th>
<th>example (JSON)</th>
<th>function</th>
<th>existing compatibility</th>
</tr>
</thead>
<tbody>
<tr>
<td><tt>href</tt></td>
<td>string</td>
<td><tt>&quot;href&quot;: &quot;b.gltf&quot;</tt></td>
<td>XR teleport</td>
<td>custom property in 3D fileformats</td>
</tr>
<tr>
<td><tt>src</tt></td>
<td>string</td>
<td><tt>&quot;src&quot;: &quot;#cube&quot;</tt></td>
<td>XR embed / teleport</td>
<td>custom property in 3D fileformats</td>
</tr>
<tr>
<td><tt>tag</tt></td>
<td>string</td>
<td><tt>&quot;tag&quot;: &quot;cubes geo&quot;</tt></td>
<td>tag object (for filter-use / XRWG highlighting)</td>
<td>custom property in 3D fileformats</td>
</tr>
<tr>
<td><tt>#</tt></td>
<td>string</td>
<td><tt>&quot;#&quot;: &quot;#mypreset</tt></td>
<td>trigger default fragment on load</td>
<td>custom property in 3D fileformats</td>
</tr>
</tbody>
</table><blockquote><t>Supported popular compatible 3D fileformats: <tt>.gltf</tt>, <tt>.obj</tt>, <tt>.fbx</tt>, <tt>.usdz</tt>, <tt>.json</tt> (THREE.js), <tt>.dae</tt> and so on.</t>
</blockquote></section>
<section anchor="fragment-to-metadata-mapping"><name>Fragment-to-metadata mapping</name>
<t>These are automatic fragment-to-metadata mappings, which only trigger if the 3D scene metadata matches a specific identifier:</t>
</blockquote>
<section anchor="level2-implicit-uri-fragments"><name>Level2: Implicit URI Fragments</name>
<t>These fragments are derived from objectnames (or their extras) within a 3D scene, and trigger certain actions when evaluated by the browser:</t>
<table>
<thead>
<tr>
@ -373,7 +418,7 @@ For example, to render a portal with a preview-version of the scene, create an 3
<td><tt>#&lt;preset&gt;</tt></td>
<td>string</td>
<td><tt>#cubes</tt></td>
<td>evaluates preset (<tt>#foo&amp;bar</tt>) defined in 3D Object metadata (<tt>#cubes: #foo&amp;bar</tt> e.g.) while URL-browserbar reflects <tt>#cubes</tt>. Only works when metadata-key starts with <tt>#</tt></td>
<td>evaluates preset (<tt>#foo&amp;bar</tt>) when a scene contains extra (<tt>#cubes: #foo&amp;bar</tt> e.g.) while URL-browserbar reflects <tt>#cubes</tt>. Only works when metadata-key starts with <tt>#</tt></td>
</tr>
<tr>
@ -392,14 +437,6 @@ For example, to render a portal with a preview-version of the scene, create an 3
<td>will reset (<tt>!</tt>), show/focus or hide (<tt>-</tt>) focus object(s) with <tt>tag: person</tt> or name <tt>person</tt> by looking up XRWG (<tt>*</tt>=including children)</td>
</tr>
<tr>
<td><strong>CAMERASWITCH</strong></td>
<td><tt>#&lt;cameraname&gt;</tt></td>
<td>string</td>
<td><tt>#cam01</tt></td>
<td>sets camera with name <tt>cam01</tt> as active camera</td>
</tr>
<tr>
<td><strong>MATERIALUPDATE</strong></td>
<td><tt>#&lt;tag_or_objectname&gt;[*]=&lt;materialname&gt;</tt></td>
@ -592,8 +629,14 @@ For example, to render a portal with a preview-version of the scene, create an 3
<tbody>
<tr>
<td>&lt;b&gt;#pos&lt;/b&gt;=0,0,0</td>
<td>vector3 or string</td>
<td>(re)position camera based on coordinates directly, or indirectly using objectname (its worldposition)</td>
<td>vector3</td>
<td>position camera to 0,0,0 (+userheight in VR)</td>
</tr>
<tr>
<td>&lt;b&gt;#pos&lt;/b&gt;=room</td>
<td>string</td>
<td>position camera to position of objectname <tt>room</tt> (+userheight in VR)</td>
</tr>
<tr>
@ -606,16 +649,18 @@ For example, to render a portal with a preview-version of the scene, create an 3
<eref target="https://github.com/coderofsalvation/xrfragment/issues/5">» discussion</eref><br />
</t>
<t>Here's the basic <strong>level1</strong> flow (with optional level2 features):</t>
<ol spacing="compact">
<li>the Y-coordinate of <tt>pos</tt> identifies the floorposition. This means that desktop-projections usually need to add 1.5m (average person height) on top (which is done automatically by VR/AR headsets).</li>
<li>the Y-coordinate of <tt>pos</tt> identifies the floorposition. This means that desktop-projections usually need to add 1.5m (average person height) on top (which is done automatically by VR/AR headsets), except in case of camera-switching.</li>
<li>set the position of the camera accordingly to the vector3 values of <tt>#pos</tt></li>
<li><tt>rot</tt> sets the rotation of the camera (only for non-VR/AR headsets)</li>
<li>mediafragment <tt>t</tt> in the top-URL sets the playbackspeed and animation-range of the global scene animation</li>
<li>if the referenced <tt>#pos</tt> object is animated, parent the current camera to that object (so it animates too)</li>
<li><tt>rot</tt> sets the rotation of the camera (only for non-VR/AR headsets, however a camera-value overrules this)</li>
<li><strong>level2</strong>: mediafragment <tt>t</tt> in the top-URL sets the playbackspeed and animation-range of the global scene animation</li>
<li>before scene load: the scene is cleared</li>
<li>after scene load: in case the scene (rootnode) contains an <tt>#</tt> default view with a fragment value: execute non-positional fragments via the hashbus (no top-level URL change)</li>
<li>after scene load: in case the scene (rootnode) contains an <tt>#</tt> default view with a fragment value: execute positional fragment via the hashbus + update top-level URL</li>
<li>in case of no default <tt>#</tt> view on the scene (rootnode), default player(rig) position <tt>0,0,0</tt> is assumed.</li>
<li><strong>level2</strong>: after scene load: in case the scene (rootnode) contains an <tt>#</tt> default view with a fragment value: execute non-positional fragments via the hashbus (no top-level URL change)</li>
<li><strong>level2</strong>: after scene load: in case the scene (rootnode) contains an <tt>#</tt> default view with a fragment value: execute positional fragment via the hashbus + update top-level URL</li>
<li><strong>level2</strong>: in case of no default <tt>#</tt> view on the scene (rootnode), default player(rig) position <tt>0,0,0</tt> is assumed.</li>
<li>in case a <tt>href</tt> does not mention any <tt>pos</tt>-coordinate, the current position will be assumed</li>
</ol>
<t>Here's an ascii representation of a 3D scene-graph which contains 3D objects <tt></tt> and their metadata:</t>
@ -1134,99 +1179,113 @@ The XR Fragment-compatible browser can let the enduser access visual-meta(data)-
<section anchor="reflection-mapping"><name>Reflection Mapping</name>
<t>Environment mapping is crucial for creating realistic reflections and lighting effects on 3D objects.
To apply environment mapping efficiently in a 3D scene, traverse the scene graph and assign each object's environment map based on the nearest ancestor's texture map. This ensures that objects inherit the correct environment mapping from their closest parent with a texture, enhancing the visual consistency and realism.</t>
<t>``````
+--------------------------------+<br />
| |<br />
| index.usdz |<br />
| │ |<br />
| └── ◻ sphere (texture:foo) |
| └ ◻ cube (texture:bar) | envMap = foo
<artwork><![CDATA[ +--------------------------------+
| |
| index.usdz |
| │ |
| └── ◻ sphere (texture:foo) |
| └ ◻ cube (texture:bar) | envMap = foo
| └ ◻ cylinder | envMap = bar
+--------------------------------+</t>
<artwork><![CDATA[
Most 3D viewers apply one and the same environment map for various models, however this logic
allows a more natural & automatic strategy for reflection mapping.
# Transclusion (broken link) resolution
In spirit of Ted Nelson's 'transclusion resolution', there's a soft-mechanism to harden links & minimize broken links in various ways:
1. defining a different transport protocol (https vs ipfs or DAT) in `src` or `href` values can make a difference
2. mirroring files on another protocol using (HTTP) errorcode tags in `src` or `href` properties
3. in case of `src`: nesting a copy of the embedded object in the placeholder object (`embeddedObject`) will not be replaced when the request fails
> due to the popularity, maturity and extensiveness of HTTP codes for client/server communication, non-HTTP protocols easily map to HTTP codes (ipfs ERR_NOT_FOUND maps to 404 e.g.)
For example:
+--------------------------------+
]]>
</artwork>
<t>+────────────────────────────────────────────────────────+
<t>Most 3D viewers apply one and the same environment map for various models, however this logic
allows a more natural &amp; automatic strategy for reflection mapping:</t>
<ol spacing="compact">
<li>traverse the scene graph depth-first</li>
<li>remember the most recent parentnode (P) with a texture material</li>
<li>for every non-root node with a texture material
3.1 clone that material (as materials might be shared across objects)
3.2 set the environmentmap to the last known parent texture (P)</li>
</ol>
</section>
<section anchor="transclusion-broken-link-resolution"><name>Transclusion (broken link) resolution</name>
<t>In spirit of Ted Nelson's 'transclusion resolution', there's a soft-mechanism to harden links &amp; minimize broken links in various ways:</t>
<ol spacing="compact">
<li>defining a different transport protocol (https vs ipfs or DAT) in <tt>src</tt> or <tt>href</tt> values can make a difference</li>
<li>mirroring files on another protocol using (HTTP) errorcode tags in <tt>src</tt> or <tt>href</tt> properties</li>
<li>in case of <tt>src</tt>: nesting a copy of the embedded object in the placeholder object (<tt>embeddedObject</tt>) will not be replaced when the request fails</li>
</ol>
<blockquote><t>due to the popularity, maturity and extensiveness of HTTP codes for client/server communication, non-HTTP protocols easily map to HTTP codes (ipfs ERR_NOT_FOUND maps to 404 e.g.)</t>
</blockquote><t>For example:</t>
<artwork><![CDATA[ +────────────────────────────────────────────────────────+
│ │
│ index.gltf │
│ │ │
│ │ #: #-offlinetext │
│ │ │
│ ├── ◻ buttonA │
│ │ └ href: <eref target="http://foo.io/campagne.fbx">http://foo.io/campagne.fbx</eref>
│ │ └ href: http://foo.io/campagne.fbx │
│ │ └ href@404: ipfs://foo.io/campagne.fbx │
│ │ └ href@400: #clienterrortext │
│ │ └ ◻ offlinetext │
│ │ │
│ └── ◻ embeddedObject &lt;--------- the meshdata inside embeddedObject will (not)
│ └ src: <eref target="https://foo.io/bar.gltf">https://foo.io/bar.gltf</eref> │ be flushed when the request (does not) succeed.
│ └ src@404: <eref target="http://foo.io/bar.gltf">http://foo.io/bar.gltf</eref> │ So worstcase the 3D data (of the time of publishing index.gltf)
│ └ src@400: <eref target="https://archive.org/l2kj43.gltf">https://archive.org/l2kj43.gltf</eref> │ will be displayed.
│ └── ◻ embeddedObject <--------- the meshdata inside embeddedObject will (not)
│ └ src: https://foo.io/bar.gltf │ be flushed when the request (does not) succeed.
│ └ src@404: http://foo.io/bar.gltf │ So worstcase the 3D data (of the time of publishing index.gltf)
│ └ src@400: https://archive.org/l2kj43.gltf │ will be displayed.
│ │
+────────────────────────────────────────────────────────+</t>
<artwork><![CDATA[
# Topic-based index-less Webrings
As hashtags in URLs map to the XWRG, `href`-values can be used to promote topic-based index-less webrings.<br>
Consider 3D scenes linking to eachother using these `href` values:
* `href: schoolA.edu/projects.gltf#math`
* `href: schoolB.edu/projects.gltf#math`
* `href: university.edu/projects.gltf#math`
These links would all show visible links to math-tagged objects in the scene.<br>
To filter out non-related objects one could take it a step further using filters:
* `href: schoolA.edu/projects.gltf#math&-topics math`
* `href: schoolB.edu/projects.gltf#math&-courses math`
* `href: university.edu/projects.gltf#math&-theme math`
> This would hide all object tagged with `topic`, `courses` or `theme` (including math) so that later only objects tagged with `math` will be visible
This makes spatial content multi-purpose, without the need to separate content into separate files, or show/hide things using a complex logiclayer like javascript.
# URI Templates (RFC6570)
XR Fragments adopts Level1 URI **Fragment** expansion to provide safe interactivity.<br>
The following demonstrates a simple video player:
+────────────────────────────────────────────────────────+
]]>
</artwork>
<t>+─────────────────────────────────────────────+
</section>
<section anchor="topic-based-index-less-webrings"><name>Topic-based index-less Webrings</name>
<t>As hashtags in URLs map to the XWRG, <tt>href</tt>-values can be used to promote topic-based index-less webrings.<br />
Consider 3D scenes linking to eachother using these <tt>href</tt> values:</t>
<ul spacing="compact">
<li><tt>href: schoolA.edu/projects.gltf#math</tt></li>
<li><tt>href: schoolB.edu/projects.gltf#math</tt></li>
<li><tt>href: university.edu/projects.gltf#math</tt></li>
</ul>
<t>These links would all show visible links to math-tagged objects in the scene.<br />
To filter out non-related objects one could take it a step further using filters:</t>
<ul spacing="compact">
<li><tt>href: schoolA.edu/projects.gltf#math&amp;-topics math</tt></li>
<li><tt>href: schoolB.edu/projects.gltf#math&amp;-courses math</tt></li>
<li><tt>href: university.edu/projects.gltf#math&amp;-theme math</tt></li>
</ul>
<blockquote><t>This would hide all object tagged with <tt>topic</tt>, <tt>courses</tt> or <tt>theme</tt> (including math) so that later only objects tagged with <tt>math</tt> will be visible</t>
</blockquote><t>This makes spatial content multi-purpose, without the need to separate content into separate files, or show/hide things using a complex logiclayer like javascript.</t>
</section>
<section anchor="uri-templates-rfc6570"><name>URI Templates (RFC6570)</name>
<t>XR Fragments adopts Level1 URI <strong>Fragment</strong> expansion to provide safe interactivity.<br />
The following demonstrates a simple video player:</t>
<artwork><![CDATA[
+─────────────────────────────────────────────+
│ │
│ foo.usdz │<br />
│ │ │<br />
│ │ │<br />
│ foo.usdz │
│ │ │
│ │ │
│ ├── ◻ stopbutton │
│ │ ├ #: #-stopbutton │
│ │ └ href: #player=stop&amp;-stopbutton │ (stop and hide stop-button)
│ │ │<br />
│ │ └ href: #player=stop&-stopbutton │ (stop and hide stop-button)
│ │ │
│ └── ◻ plane │
│ ├ play: #t=l:0,10 │
│ ├ stop: #t=0,0 │
│ ├ href: #player=play&amp;stopbutton │ (play and show stop-button)
│ ├ href: #player=play&stopbutton │ (play and show stop-button)
│ └ src: cat.mp4#{player} │
│ │
│ │
+─────────────────────────────────────────────+</t>
<t>```</t>
+─────────────────────────────────────────────+
]]>
</artwork>
</section>
<section anchor="additional-scene-metadata"><name>Additional scene metadata</name>
@ -1339,6 +1398,100 @@ Therefore a 2-button navigation-interface is the bare minimum interface:</t>
<li>the TTS reads the href-value (and/or aria-description if available)</li>
</ol>
</section>
<section anchor="overlap-with-fileformat-specific-extensions"><name>Overlap with fileformat-specific extensions</name>
<t>Some 3D scene-fileformats have support for extensions.
What if the functionality of those overlap?
For example, GLTF has the <tt>OMI_LINK</tt> extension which might overlap with XR Fragment's <tt>href</tt>:</t>
<blockquote><t>Priority Order and Precedence, otherwise fallback applies</t>
</blockquote><t>1.<strong>Extensions Take Precedence</strong>: Since glTF-specific extensions are designed with the formats
specific needs and optimizations in mind, they should take precedence over extras metadata
in cases where both contain overlapping functionality.
This approach aligns with the idea that extensions are more likely to be interpreted uniformly by glTF-compatible software.</t>
<ol spacing="compact" start="2">
<li><strong>Fallback Fall-through Mechanism</strong>:
If a glTF implementation does not support a particular extension, the (XRF) extras field can serve as a fallback. This way, metadata provided in extras can still be useful for applications that don't handle certain extensions.</li>
</ol>
<blockquote><t><strong>Example 1</strong> In case of the OMI_LINK glTF extension (<tt>href: https://nlnet.nl</tt>) and an XR Fragment (<tt>href: #pos=otherroom</tt> or <tt>href: otherplanet.glb</tt>), it is clear that <tt>https://nlnet.nl</tt> should open in a browsertab, whereas the XR Fragment links should teleport the user. If the OMI_LINK contains an XR Fragment (<tt>#pos=</tt> e.g.) a teleport should be performed only (and other [overlapping] metadata should be ignored).</t>
<t><strong>Example 2</strong> If an Extensions uses XR Fragments in URI's (<tt>href: #pos=otherroom</tt> or <tt>href: xrf://-walls</tt> in OMI_LINK e.g.), then perform them according to XR Fragment spec (teleport user). But only once: ignore further overlapping metadata for that usecase.</t>
</blockquote></section>
<section anchor="vendor-prefixes"><name>Vendor Prefixes</name>
<t>Vendor-specific metadata in a 3D scenefiles, are similar to vendor-specific <eref target="https://en.wikipedia.org/wiki/CSS#Vendor_prefixes">CSS-prefixes</eref> (<tt>-moz-opacity: 0.2</tt> e.g.).
This allows popular 3D engines/frameworks, to initialize specific features when loading a scene/object, in a progressive enhanced way.</t>
<t>Vendor Prefixes allows embedding 3D engines/framework-specific features a 3D file via metadata:</t>
<table>
<thead>
<tr>
<th>what</th>
<th>XR metadata</th>
<th>Lowest common denominator</th>
</tr>
</thead>
<tbody>
<tr>
<td>CSS</td>
<td>vendor-agnostic</td>
<td>2D canvas + object referencing/styling</td>
</tr>
<tr>
<td>XR Fragments</td>
<td>vendor-agnostic</td>
<td>3D camera + object(file) load/embed/click/referencing</td>
</tr>
<tr>
<td>Vendor prefixs</td>
<td>vendor-<strong>specific</strong></td>
<td>Specialized Entity-Component implementation</td>
</tr>
</tbody>
</table><blockquote><t>Why? Because not all XR interactions can/should be solved/standardized by embedding XR Fragments into any 3D file.
The lowest common denominator between 3D engines is the 'entity'-part of their entity-component-system (ECS). The 'component'-part can be progressively enhanced via vendor prefixes.</t>
</blockquote><t>For example, the following metadata can be added to a .glb file, to make an object grabbable in AFRAME:</t>
<artwork><![CDATA[+────────────────────────────────────────────────────────────────────────────────────────────────────────+
│ http://y.io/z.glb | AFRAME app │
│-----------------------------------------------+--------------------------------------------------------│
│ | │
│ | after loading the glb, john can be placed into the │
│ +-[3D mesh]-+ | castle via hands, because the author added metadata to │
│ | / \ | | john via either: │
│ | / \ | | │
│ | / \ | | 1. Blender (custom property-box, no plugins needed) │
│ | |_____| | | │
│ +-----│-----+ | 2. javascript-code: │
│ │ | │
│ ├─ name: castle | for( var com in this.el.components ){ │
│ └─ tag: house baroque | this.el.object3D.userData[`-AFRAME-${com}`] = '' │
│ | } │
│ [3D mesh-+ | // save to z.glb in AFRAME inspector │
│ | ├─ name: john | │
│ | O ├─ age: 23 | │
│ | /|\ ├─ -aframe-grabbable: '' | > inits 'grabbable' component on object john │
│ | / \ ├─ -aframe-material.color: '#F0A' | > inits 'material' component on object john │
│ | ├─ -aframe-text.value: '{name}{age}'| > inits 'text' component (*) with value 'john' │
│ | ├─ -three-material.fog: false | > changes material settings in THREE.js app │
│ | ├─ -godot-Label3D.text: '{name}{age}'| > inits 'Label3D' component (*) in Godot │
│ +--------+ | │
│ | │
├─ -GODOT-version: '4.3' | > exporters/authors can report targeted version │
├─ -AFRAME-version: '1.6.0' | and (optionally) hint component-repo│
├─ -AFRAME-info: 'https://git.benetou.fr/comps' │
│ | │
+────────────────────────────────────────────────────────────────────────────────────────────────────────+
]]>
</artwork>
<ul spacing="compact">
<li>key/value syntax: -<tt>&lt;vendorname&gt;</tt>-<tt>&lt;component|version&gt;</tt>.<tt>&lt;key&gt;</tt> <tt>[string/boolean/float/int]</tt>-value</li>
</ul>
<t>String-templatevalues are evaluated as per <eref target="https://www.rfc-editor.org/rfc/rfc6570">URI Templates (RFC6570)</eref> Level 1.</t>
<blockquote><t>This 'separating of mechanism from policy' (unix rule) does <strong>somewhat</strong> break portability of an XR experience, but still prevents (E-waste of) handcoded virtual worlds. It allows for (XR experience) metadata to survive in future 3D engines and scene-fileformats.</t>
</blockquote></section>
</section>
<section anchor="security-considerations"><name>Security Considerations</name>
@ -1346,7 +1499,7 @@ Therefore a 2-button navigation-interface is the bare minimum interface:</t>
The use of URI Templates is limited to pre-defined variables and Level0 fragments-expansion only, which makes it quite safe.<br />
In fact, it is much safer than relying on a scripting language (javascript) which can change URN too.</t>
n fact, it is much safer than relying on a scripting language (javascript) which can change URN too.</t>
</section>
<section anchor="faq"><name>FAQ</name>

View File

@ -3,7 +3,7 @@
Internet Engineering Task Force L.R. van Kammen
Internet-Draft 9 May 2024
Internet-Draft 27 September 2024
Intended status: Informational
@ -38,7 +38,7 @@ Status of This Memo
time. It is inappropriate to use Internet-Drafts as reference
material or to cite them other than as "work in progress."
This Internet-Draft will expire on 10 November 2024.
This Internet-Draft will expire on 31 March 2025.
Copyright Notice
@ -53,9 +53,9 @@ Copyright Notice
van Kammen Expires 10 November 2024 [Page 1]
van Kammen Expires 31 March 2025 [Page 1]
Internet-Draft XR Macros May 2024
Internet-Draft XR Macros September 2024
extracted from this document must include Revised BSD License text as
@ -109,9 +109,9 @@ Table of Contents
van Kammen Expires 10 November 2024 [Page 2]
van Kammen Expires 31 March 2025 [Page 2]
Internet-Draft XR Macros May 2024
Internet-Draft XR Macros September 2024
3. Metadata-values can contain the | symbol to 🎲 roundrobin variable
@ -165,9 +165,9 @@ Internet-Draft XR Macros May 2024
van Kammen Expires 10 November 2024 [Page 3]
van Kammen Expires 31 March 2025 [Page 3]
Internet-Draft XR Macros May 2024
Internet-Draft XR Macros September 2024
+=========+======+===================+=================+=============+
@ -221,9 +221,9 @@ Internet-Draft XR Macros May 2024
van Kammen Expires 10 November 2024 [Page 4]
van Kammen Expires 31 March 2025 [Page 4]
Internet-Draft XR Macros May 2024
Internet-Draft XR Macros September 2024
Table 3
@ -277,9 +277,9 @@ Internet-Draft XR Macros May 2024
van Kammen Expires 10 November 2024 [Page 5]
van Kammen Expires 31 March 2025 [Page 5]
Internet-Draft XR Macros May 2024
Internet-Draft XR Macros September 2024
4.5. Usecase: present context menu with options
@ -333,9 +333,9 @@ click object with (`!clickme`:`!foo|!bar|!flop` e.g.)
van Kammen Expires 10 November 2024 [Page 6]
van Kammen Expires 31 March 2025 [Page 6]
Internet-Draft XR Macros May 2024
Internet-Draft XR Macros September 2024
| Note that only macro's can trigger roundrobin values or
@ -389,4 +389,4 @@ Internet-Draft XR Macros May 2024
van Kammen Expires 10 November 2024 [Page 7]
van Kammen Expires 31 March 2025 [Page 7]

View File

@ -17,10 +17,10 @@
</head>
<body>
<a-scene xr-mode-ui="XRMode: xr"
renderer="colorManagement: false; antialias:true; highRefreshRate:true; foveationLevel: 0.5; toneMapping: ACESFilmic; exposure: 3.0"
device-orientation-permission-ui
renderer="colorManagement: false; stencil: true; antialias:true; highRefreshRate:true; foveationLevel: 0.5; toneMapping: ACESFilmic; exposure: 3.0"
device-orientation-permission-ui xrf-gaze-always joystick
light="defaultLightsEnabled: false">
<a-entity id="player" movement-controls touch-controls wasd-controls="fly:false" look-controls="magicWindowTrackingEnabled:true">
<a-entity id="player" movement-controls touch-controls="axis:y" wasd-controls="fly:false" look-controls="magicWindowTrackingEnabled:true">
<a-entity camera="fov:90" position="0 1.6 0" id="camera"></a-entity>
<a-entity id="left-hand" hand-tracking-grab-controls="hand:left;modelColor:#cccccc" raycaster="objects:.ray" blink-controls="cameraRig:#player; teleportOrigin: #camera; collisionEntities: .floor">
<a-entity rotation="-35 0 0" position="0 0.1 0" id="navigator">
@ -31,7 +31,7 @@
<a-entity id="right-hand" hand-tracking-grab-controls="hand:right;modelColor:#cccccc" laser-controls="hand: right" raycaster="objects:.ray" blink-controls="cameraRig:#player; teleportOrigin: #camera; collisionEntities: .floor" xrf-pinchmove="rig: #player"></a-entity>
</a-entity>
<a-entity id="home" xrf="./../../assets/index.glb" xrf-menu></a-entity>
<a-entity id="home" xrf="./../../assets/index.glb"></a-entity>
</a-scene>
<!-- OPTIONAL -->

Binary file not shown.

BIN
example/assets/website.glb Normal file

Binary file not shown.

View File

@ -164,19 +164,16 @@
const handModel1 = new OculusHandModel( hand1 );
hand1.add( handModel1 );
cameraRig.add( hand1 );
console.dir(hand1)
const controllerGrip2 = renderer.xr.getControllerGrip( 1 );
controllerGrip2.add( controllerModelFactory.createControllerModel( controllerGrip2 ) );
cameraRig.add( controllerGrip2 );
console.dir(hand1)
// hand2
const hand2 = renderer.xr.getHand( 1 );
const handModel2 = new OculusHandModel( hand2 );
hand2.add( handModel2 );
cameraRig.add( hand2 );
console.dir(hand2)
// Add stats.js
stats = new Stats();

File diff suppressed because one or more lines are too long

2
make
View File

@ -123,7 +123,7 @@ build(){
cp src/3rd/js/plugin/frontend/\$editor.js dist/xrfragment.plugin.editor.js
cp src/3rd/js/plugin/frontend/css.js dist/xrfragment.plugin.frontend.css.js
jscat src/3rd/js/plugin/frontend/{snackbar,accessibility,\$menu,frontend,chatcommand/*}.js > dist/xrfragment.plugin.frontend.js
jscat src/3rd/js/plugin/frontend/{snackbar,accessibility,\$menu,frontend,chatcommand/*,joystick}.js > dist/xrfragment.plugin.frontend.js
jscat src/3rd/js/plugin/matrix/{matrix-crdt,matrix}.js > dist/xrfragment.plugin.matrix.js
jscat src/3rd/js/plugin/p2p/{trystero-torrent.min,trystero}.js > dist/xrfragment.plugin.p2p.js

View File

@ -16,3 +16,5 @@
bash
'';
}

View File

@ -3,8 +3,19 @@ window.AFRAME.registerComponent('xrf', {
http: { type:'string'},
https: { type:'string'},
},
init: async function () {
// fix needed since aframe 1.7.0 (non-key/value string values no longer accepted)
urlArr = []
for( var i in this.attrValue ){
if( this.attrValue[i] ){
if( i ) urlArr.push(i)
urlArr.push( this.attrValue[i] )
}
}
this.data = urlArr.join(":")
// override this.data when URL has passed (`://....com/?https://foo.com/index.glb` e.g.)
if( typeof this.data == "string" ){
let searchIsUri = document.location.search &&
@ -15,8 +26,6 @@ window.AFRAME.registerComponent('xrf', {
}
}
if( !AFRAME.scenes[0] ) return // ignore if no scene yet
if( !AFRAME.XRF ){
let camera = document.querySelector('[camera]')
@ -24,7 +33,7 @@ window.AFRAME.registerComponent('xrf', {
camera.setAttribute('xrf-fade','')
AFRAME.fade = camera.components['xrf-fade']
let aScene = AFRAME.scenes[0]
let aScene = document.querySelector('a-scene')
// enable XR fragments
let XRF = AFRAME.XRF = xrf.init({
@ -55,6 +64,7 @@ window.AFRAME.registerComponent('xrf', {
if( VRbutton ) VRbutton.addEventListener('click', () => AFRAME.XRF.hashbus.pub( '#VR' ) )
})
// not part of the spec, but convenient to only show AR button when negative VR-tag was defined in default fragment ('#' in rootscene file)
xrf.addEventListener('#', function(e){
if( e.frag['#'].string.match(/-VR/) ){

View File

@ -9,213 +9,216 @@ const MAX_DELTA = 0.2; // ms
const EPS = 10e-6;
const MOVED = 'moved';
AFRAME.registerComponent('movement-controls', {
if( !AFRAME.components['movement-controls'] ){
/*******************************************************************
* Schema
*/
AFRAME.registerComponent('movement-controls', {
dependencies: ['rotation'],
/*******************************************************************
* Schema
*/
schema: {
enabled: { default: true },
controls: { default: ['gamepad', 'trackpad', 'keyboard', 'touch'] },
speed: { default: 0.3, min: 0 },
fly: { default: false },
constrainToNavMesh: { default: false },
camera: { default: '[movement-controls] [camera]', type: 'selector' }
},
dependencies: ['rotation'],
/*******************************************************************
* Lifecycle
*/
schema: {
enabled: { default: true },
controls: { default: ['gamepad', 'trackpad', 'keyboard', 'touch'] },
speed: { default: 0.3, min: 0 },
fly: { default: false },
constrainToNavMesh: { default: false },
camera: { default: '[movement-controls] [camera]', type: 'selector' }
},
init: function () {
const el = this.el;
if (!this.data.camera) {
this.data.camera = el.querySelector('[camera]');
}
this.velocityCtrl = null;
/*******************************************************************
* Lifecycle
*/
this.velocity = new THREE.Vector3();
this.heading = new THREE.Quaternion();
this.eventDetail = {};
init: function () {
const el = this.el;
if (!this.data.camera) {
this.data.camera = el.querySelector('[camera]');
}
this.velocityCtrl = null;
// Navigation
this.navGroup = null;
this.navNode = null;
this.velocity = new THREE.Vector3();
this.heading = new THREE.Quaternion();
this.eventDetail = {};
if (el.sceneEl.hasLoaded) {
this.injectControls();
} else {
el.sceneEl.addEventListener('loaded', this.injectControls.bind(this));
}
},
// Navigation
this.navGroup = null;
this.navNode = null;
if (el.sceneEl.hasLoaded) {
this.injectControls();
} else {
el.sceneEl.addEventListener('loaded', this.injectControls.bind(this));
}
},
update: function (prevData) {
const el = this.el;
const data = this.data;
const nav = el.sceneEl.systems.nav;
if (el.sceneEl.hasLoaded) {
this.injectControls();
}
if (nav && data.constrainToNavMesh !== prevData.constrainToNavMesh) {
data.constrainToNavMesh
? nav.addAgent(this)
: nav.removeAgent(this);
}
if (data.enabled !== prevData.enabled) {
// Propagate the enabled change to all controls
for (let i = 0; i < data.controls.length; i++) {
const name = data.controls[i] + COMPONENT_SUFFIX;
this.el.setAttribute(name, { enabled: this.data.enabled });
}
}
},
injectControls: function () {
const data = this.data;
update: function (prevData) {
const el = this.el;
const data = this.data;
const nav = el.sceneEl.systems.nav;
if (el.sceneEl.hasLoaded) {
this.injectControls();
}
if (nav && data.constrainToNavMesh !== prevData.constrainToNavMesh) {
data.constrainToNavMesh
? nav.addAgent(this)
: nav.removeAgent(this);
}
if (data.enabled !== prevData.enabled) {
// Propagate the enabled change to all controls
for (let i = 0; i < data.controls.length; i++) {
const name = data.controls[i] + COMPONENT_SUFFIX;
this.el.setAttribute(name, { enabled: this.data.enabled });
}
}
},
},
injectControls: function () {
const data = this.data;
updateNavLocation: function () {
this.navGroup = null;
this.navNode = null;
},
for (let i = 0; i < data.controls.length; i++) {
const name = data.controls[i] + COMPONENT_SUFFIX;
this.el.setAttribute(name, { enabled: this.data.enabled });
}
},
/*******************************************************************
* Tick
*/
updateNavLocation: function () {
this.navGroup = null;
this.navNode = null;
},
tick: (function () {
const start = new THREE.Vector3();
const end = new THREE.Vector3();
const clampedEnd = new THREE.Vector3();
/*******************************************************************
* Tick
*/
return function (t, dt) {
if (!dt) return;
tick: (function () {
const start = new THREE.Vector3();
const end = new THREE.Vector3();
const clampedEnd = new THREE.Vector3();
const el = this.el;
const data = this.data;
return function (t, dt) {
if (!dt) return;
if (!data.enabled) return;
const el = this.el;
const data = this.data;
this.updateVelocityCtrl();
const velocityCtrl = this.velocityCtrl;
const velocity = this.velocity;
if (!data.enabled) return;
if (!velocityCtrl) return;
this.updateVelocityCtrl();
const velocityCtrl = this.velocityCtrl;
const velocity = this.velocity;
if (!velocityCtrl) return;
// Update velocity. If FPS is too low, reset.
if (dt / 1000 > MAX_DELTA) {
velocity.set(0, 0, 0);
} else {
this.updateVelocity(dt);
}
if (data.constrainToNavMesh
&& velocityCtrl.isNavMeshConstrained !== false) {
if (velocity.lengthSq() < EPS) return;
start.copy(el.object3D.position);
end
.copy(velocity)
.multiplyScalar(dt / 1000)
.add(start);
const nav = el.sceneEl.systems.nav;
this.navGroup = this.navGroup === null ? nav.getGroup(start) : this.navGroup;
this.navNode = this.navNode || nav.getNode(start, this.navGroup);
this.navNode = nav.clampStep(start, end, this.navGroup, this.navNode, clampedEnd);
el.object3D.position.copy(clampedEnd);
} else if (el.hasAttribute('velocity')) {
el.setAttribute('velocity', velocity);
} else {
el.object3D.position.x += velocity.x * dt / 1000;
el.object3D.position.y += velocity.y * dt / 1000;
el.object3D.position.z += velocity.z * dt / 1000;
}
};
}()),
/*******************************************************************
* Movement
*/
updateVelocityCtrl: function () {
const data = this.data;
if (data.enabled) {
for (let i = 0, l = data.controls.length; i < l; i++) {
const control = this.el.components[data.controls[i] + COMPONENT_SUFFIX];
if (control && control.isVelocityActive()) {
this.velocityCtrl = control;
return;
}
}
this.velocityCtrl = null;
}
},
updateVelocity: (function () {
const vector2 = new THREE.Vector2();
const quaternion = new THREE.Quaternion();
return function (dt) {
let dVelocity;
const el = this.el;
const control = this.velocityCtrl;
const velocity = this.velocity;
const data = this.data;
if (control) {
if (control.getVelocityDelta) {
dVelocity = control.getVelocityDelta(dt);
} else if (control.getVelocity) {
velocity.copy(control.getVelocity());
return;
} else if (control.getPositionDelta) {
velocity.copy(control.getPositionDelta(dt).multiplyScalar(1000 / dt));
return;
// Update velocity. If FPS is too low, reset.
if (dt / 1000 > MAX_DELTA) {
velocity.set(0, 0, 0);
} else {
throw new Error('Incompatible movement controls: ', control);
this.updateVelocity(dt);
}
}
if (el.hasAttribute('velocity') && !data.constrainToNavMesh) {
velocity.copy(this.el.getAttribute('velocity'));
}
if (data.constrainToNavMesh
&& velocityCtrl.isNavMeshConstrained !== false) {
if (dVelocity && data.enabled) {
const cameraEl = data.camera;
if (velocity.lengthSq() < EPS) return;
// Rotate to heading
quaternion.copy(cameraEl.object3D.quaternion);
quaternion.premultiply(el.object3D.quaternion);
dVelocity.applyQuaternion(quaternion);
start.copy(el.object3D.position);
end
.copy(velocity)
.multiplyScalar(dt / 1000)
.add(start);
const factor = dVelocity.length();
if (data.fly) {
velocity.copy(dVelocity);
velocity.multiplyScalar(this.data.speed * 16.66667);
const nav = el.sceneEl.systems.nav;
this.navGroup = this.navGroup === null ? nav.getGroup(start) : this.navGroup;
this.navNode = this.navNode || nav.getNode(start, this.navGroup);
this.navNode = nav.clampStep(start, end, this.navGroup, this.navNode, clampedEnd);
el.object3D.position.copy(clampedEnd);
} else if (el.hasAttribute('velocity')) {
el.setAttribute('velocity', velocity);
} else {
vector2.set(dVelocity.x, dVelocity.z);
vector2.setLength(factor * this.data.speed * 16.66667);
velocity.x = vector2.x;
velocity.y = 0;
velocity.z = vector2.y;
el.object3D.position.x += velocity.x * dt / 1000;
el.object3D.position.y += velocity.y * dt / 1000;
el.object3D.position.z += velocity.z * dt / 1000;
}
if (velocity.x !== 0 || velocity.y !== 0 || velocity.z !== 0) {
this.eventDetail.velocity = velocity;
this.el.emit(MOVED, this.eventDetail);
}
}
};
}())
});
};
}()),
/*******************************************************************
* Movement
*/
updateVelocityCtrl: function () {
const data = this.data;
if (data.enabled) {
for (let i = 0, l = data.controls.length; i < l; i++) {
const control = this.el.components[data.controls[i] + COMPONENT_SUFFIX];
if (control && control.isVelocityActive()) {
this.velocityCtrl = control;
return;
}
}
this.velocityCtrl = null;
}
},
updateVelocity: (function () {
const vector2 = new THREE.Vector2();
const quaternion = new THREE.Quaternion();
return function (dt) {
let dVelocity;
const el = this.el;
const control = this.velocityCtrl;
const velocity = this.velocity;
const data = this.data;
if (control) {
if (control.getVelocityDelta) {
dVelocity = control.getVelocityDelta(dt);
} else if (control.getVelocity) {
velocity.copy(control.getVelocity());
return;
} else if (control.getPositionDelta) {
velocity.copy(control.getPositionDelta(dt).multiplyScalar(1000 / dt));
return;
} else {
throw new Error('Incompatible movement controls: ', control);
}
}
if (el.hasAttribute('velocity') && !data.constrainToNavMesh) {
velocity.copy(this.el.getAttribute('velocity'));
}
if (dVelocity && data.enabled) {
const cameraEl = data.camera;
// Rotate to heading
quaternion.copy(cameraEl.object3D.quaternion);
quaternion.premultiply(el.object3D.quaternion);
dVelocity.applyQuaternion(quaternion);
const factor = dVelocity.length();
if (data.fly) {
velocity.copy(dVelocity);
velocity.multiplyScalar(this.data.speed * 16.66667);
} else {
vector2.set(dVelocity.x, dVelocity.z);
vector2.setLength(factor * this.data.speed * 16.66667);
velocity.x = vector2.x;
velocity.y = 0;
velocity.z = vector2.y;
}
if (velocity.x !== 0 || velocity.y !== 0 || velocity.z !== 0) {
this.eventDetail.velocity = velocity;
this.el.emit(MOVED, this.eventDetail);
}
}
};
}())
});
}

View File

@ -1,91 +1,96 @@
/**
* Touch-to-move-forward controls for mobile.
*/
AFRAME.registerComponent('touch-controls', {
schema: {
enabled: { default: true },
reverseEnabled: { default: true }
},
init: function () {
this.dVelocity = new THREE.Vector3();
this.bindMethods();
this.direction = 0;
},
if( !AFRAME.components['touch-controls'] ){
play: function () {
this.addEventListeners();
},
AFRAME.registerComponent('touch-controls', {
schema: {
axis: { default: "z", "type":"string" },
enabled: { default: true },
reverseEnabled: { default: true }
},
pause: function () {
this.removeEventListeners();
this.dVelocity.set(0, 0, 0);
},
init: function () {
this.dVelocity = new THREE.Vector3();
this.bindMethods();
this.direction = 0;
},
remove: function () {
this.pause();
},
play: function () {
this.addEventListeners();
},
addEventListeners: function () {
const sceneEl = this.el.sceneEl;
const canvasEl = sceneEl.canvas;
pause: function () {
this.removeEventListeners();
this.dVelocity.set(0, 0, 0);
},
if (!canvasEl) {
sceneEl.addEventListener('render-target-loaded', this.addEventListeners.bind(this));
return;
remove: function () {
this.pause();
},
addEventListeners: function () {
const sceneEl = this.el.sceneEl;
const canvasEl = sceneEl.canvas;
if (!canvasEl) {
sceneEl.addEventListener('render-target-loaded', this.addEventListeners.bind(this));
return;
}
canvasEl.addEventListener('touchstart', this.onTouchStart);
canvasEl.addEventListener('touchend', this.onTouchEnd);
const vrModeUI = sceneEl.getAttribute('vr-mode-ui');
if (vrModeUI && vrModeUI.cardboardModeEnabled) {
sceneEl.addEventListener('enter-vr', this.onEnterVR);
}
},
removeEventListeners: function () {
const canvasEl = this.el.sceneEl && this.el.sceneEl.canvas;
if (!canvasEl) { return; }
canvasEl.removeEventListener('touchstart', this.onTouchStart);
canvasEl.removeEventListener('touchend', this.onTouchEnd);
this.el.sceneEl.removeEventListener('enter-vr', this.onEnterVR)
},
isVelocityActive: function () {
return this.data.enabled && !!this.direction;
},
getVelocityDelta: function () {
this.dVelocity[ this.data.axis ] = this.direction;
return this.dVelocity.clone();
},
bindMethods: function () {
this.onTouchStart = this.onTouchStart.bind(this);
this.onTouchEnd = this.onTouchEnd.bind(this);
this.onEnterVR = this.onEnterVR.bind(this);
},
onTouchStart: function (e) {
this.direction = 0;
if (this.data.reverseEnabled && e.touches ){
if( e.touches.length === 3) this.direction = 1;
if( e.touches.length === 2) this.direction = -1;
}
//e.preventDefault();
},
onTouchEnd: function (e) {
this.direction = 0;
//e.preventDefault();
},
onEnterVR: function () {
// This is to make the Cardboard button on Chrome Android working
//const xrSession = this.el.sceneEl.xrSession;
//if (!xrSession) { return; }
//xrSession.addEventListener('selectstart', this.onTouchStart);
//xrSession.addEventListener('selectend', this.onTouchEnd);
}
canvasEl.addEventListener('touchstart', this.onTouchStart);
canvasEl.addEventListener('touchend', this.onTouchEnd);
const vrModeUI = sceneEl.getAttribute('vr-mode-ui');
if (vrModeUI && vrModeUI.cardboardModeEnabled) {
sceneEl.addEventListener('enter-vr', this.onEnterVR);
}
},
removeEventListeners: function () {
const canvasEl = this.el.sceneEl && this.el.sceneEl.canvas;
if (!canvasEl) { return; }
canvasEl.removeEventListener('touchstart', this.onTouchStart);
canvasEl.removeEventListener('touchend', this.onTouchEnd);
this.el.sceneEl.removeEventListener('enter-vr', this.onEnterVR)
},
isVelocityActive: function () {
return this.data.enabled && !!this.direction;
},
getVelocityDelta: function () {
this.dVelocity.z = this.direction;
return this.dVelocity.clone();
},
bindMethods: function () {
this.onTouchStart = this.onTouchStart.bind(this);
this.onTouchEnd = this.onTouchEnd.bind(this);
this.onEnterVR = this.onEnterVR.bind(this);
},
onTouchStart: function (e) {
this.direction = 0;
if (this.data.reverseEnabled && e.touches ){
if( e.touches.length === 3) this.direction = 1;
if( e.touches.length === 2) this.direction = -1;
}
//e.preventDefault();
},
onTouchEnd: function (e) {
this.direction = 0;
//e.preventDefault();
},
onEnterVR: function () {
// This is to make the Cardboard button on Chrome Android working
//const xrSession = this.el.sceneEl.xrSession;
//if (!xrSession) { return; }
//xrSession.addEventListener('selectstart', this.onTouchStart);
//xrSession.addEventListener('selectend', this.onTouchEnd);
}
})
})
}

View File

@ -41,6 +41,7 @@ window.AFRAME.registerComponent('xrf-get', {
}else{
// lets create a dummy add function so that the mesh won't get reparented during setObject3D
// as this would break animations
// maybe we need THREE.js attach() for this?
this.el.object3D.add = (a) => a
}
this.el.object3D.parent = mesh.parent

View File

@ -7,6 +7,7 @@ AFRAME.registerSystem('xrf-hands',{
},
tick: function(){
if( !this.indexFinger ) return
if( !this.el.sceneEl.renderer.xr.isPresenting || !this.indexFinger.length ) return
for( let i = 0; i < this.indexFinger.length; i++ ){
let indexFinger = this.indexFinger[i]

View File

@ -26,7 +26,7 @@ xrf.query = function(){
xrf.detectCameraRig = function(opts){
if( opts.camera ){ // detect rig (if any)
let getCam = ((cam) => () => cam)(opts.camera)
const getCam = ((cam) => () => cam)(opts.camera)
let offsetY = 0
while( opts.camera.parent.type != "Scene" ){
offsetY += opts.camera.position.y
@ -34,6 +34,7 @@ xrf.detectCameraRig = function(opts){
opts.camera.getCam = getCam
opts.camera.updateProjectionMatrix = () => opts.camera.getCam().updateProjectionMatrix()
}
if( !opts.camera.getCam ) opts.camera.getCam = getCam // always attach function
opts.camera.offsetY = offsetY
}
}

View File

@ -392,6 +392,26 @@ document.head.innerHTML += `
div.tab-frame > input:nth-of-type(2):checked ~ .tab:nth-of-type(2),
div.tab-frame > input:nth-of-type(3):checked ~ .tab:nth-of-type(3){ display:block;}
/*
* joystick.js controller
*/
.controller {
position: fixed;
display: block;
width: 100px;
height: 100px;
left: 25px;
bottom: 20px;
cursor:pointer;
z-index: 999;
border-radius: 50%;
border: 5px solid #333;
filter: alpha(opacity=50);
-khtml-opacity: 0.3;
-moz-opacity: 0.3;
opacity:0.3;
transition: opacity 1s ease-out;
}
/*
* css icons from https://css.gg

View File

@ -105,7 +105,12 @@ window.frontend = (opts) => new Proxy({
if( !data.selected ) return
let html = this.notify_links ? `<b class="badge">${data.mesh.isSRC && !data.mesh.portal ? 'src' : 'href'}</b>${ data.xrf ? data.xrf.string : data.mesh.userData.src}<br>` : ''
let topic = data.xrf ? data.xrf.string : data.mesh.userData.src
if( topic.match(/\.\.\//) || (topic.length > 20 && AFRAME.utils.device.isMobile() ) ){
topic = topic.replace(/.*\//,'')
}
let html = this.notify_links ? `<b class="badge">${data.mesh.isSRC && !data.mesh.portal ? 'src' : 'href'}</b>${ topic }<br>` : ''
let metadata = data.mesh.userData
let meta = xrf.Parser.getMetaData()
@ -255,9 +260,9 @@ window.frontend = (opts) => new Proxy({
.then( () => {
// setup exporters
let defaultExporter = THREE.GLTFExporter
xrf.loaders['gltf'].exporter = defaultExporter
xrf.loaders['glb'].exporter = defaultExporter
const exporter = new THREE.GLTFExporter()
if( !xrf.loaders['gltf'].exporter ) xrf.loaders['gltf'].exporter = defaultExporter
if( !xrf.loaders['glb'].exporter ) xrf.loaders['glb'].exporter = defaultExporter
const exporter = new xrf.loaders[ext]()
exporter.parse(
model.scene,
function ( glb ) { download(glb, `${file}`) }, // ready
@ -275,12 +280,13 @@ window.frontend = (opts) => new Proxy({
// load original scene and overwrite with updates
let url = document.location.search.replace(/\?/,'')
let {urlObj,dir,file,hash,ext} = xrf.navigator.origin = xrf.URI.parse(url)
const Loader = xrf.loaders[ext]
let {urlObj,dir,file,hash,fileExt} = xrf.navigator.origin = xrf.URI.parse(url)
debugger
const Loader = xrf.loaders[fileExt]
loader = new Loader().setPath( dir )
notify('exporting scene<br><br>please wait..')
loader.load(url, (model) => {
exportScene(model,ext,file)
exportScene(model,fileExt,file)
})
},

File diff suppressed because one or more lines are too long

View File

@ -6,6 +6,9 @@ xrf.init = ((init) => function(opts){
// operate in own subscene
let scene = new opts.THREE.Group()
xrf.clock = new opts.THREE.Clock()
// don't mess with original scene object
// but with our own sub-scene
opts.scene.add(scene)
opts.sceneRoot = opts.scene
opts.scene = scene
@ -43,7 +46,9 @@ xrf.parseModel = function(model,url){
model.file = file
model.isXRF = true
model.scene.isXRFRoot = true
model.scene.traverse( (n) => n.isXRF = true ) // mark for deletion during reset()
model.scene.traverse( (n) => {
n.isXRF = true
}) // mark for deletion during reset()
xrf.emit('parseModel',{model,url,file})
}
@ -102,9 +107,17 @@ xrf.reset = () => {
// allow others to reset certain events
xrf.emit('reset',{})
// reattach camera to root scene
xrf.scene.attach(xrf.camera)
xrf.camera.position.set(0,0,0)
xrf.camera.updateMatrixWorld()
xrf.camera.getCam().updateMatrixWorld()
const disposeObject = (obj) => {
if (obj.children.length > 0) obj.children.forEach((child) => disposeObject(child));
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();

View File

@ -14,21 +14,26 @@ xrf.navigator = {
xrf.navigator.to = (url,flags,loader,data) => {
if( !url ) throw 'xrf.navigator.to(..) no url given'
let URI = xrfragment.URI.toAbsolute( xrf.navigator.URI, url )
URI.hash = xrf.navigator.reactifyHash(URI.hash) // automatically reflect hash-changes to navigator.to(...)
// decorate with extra state
URI.fileChange = URI.file && URI.URN + URI.file != xrf.navigator.URI.URN + xrf.navigator.URI.file
console.log( URI.URN + URI.file )
console.log( xrf.navigator.URI.URN + xrf.navigator.URI.file )
URI.external = URI.file && URI.URN != document.location.origin + document.location.pathname
URI.hasPos = URI.hash.pos ? true : false
URI.duplicatePos = URI.source == xrf.navigator.URI.source && URI.hasPos
URI.hashChange = String(xrf.navigator.URI.fragment||"") != String(URI.fragment||"")
let hashbus = xrf.hashbus
let URI
//console.dir({URI1:xrf.navigator.URI,URI2:URI})
if( typeof url == 'string' ){
URI = xrfragment.URI.toAbsolute( xrf.navigator.URI, url )
URI.hash = xrf.navigator.reactifyHash(URI.hash) // automatically reflect hash-changes to navigator.to(...)
// decorate with extra state
URI.fileChange = URI.file && URI.URN + URI.file != xrf.navigator.URI.URN + xrf.navigator.URI.file
console.log( URI.URN + URI.file )
console.log( xrf.navigator.URI.URN + xrf.navigator.URI.file )
URI.external = URI.file && URI.URN != document.location.origin + document.location.pathname
URI.hasPos = URI.hash.pos ? true : false
URI.duplicatePos = URI.source == xrf.navigator.URI.source && URI.hasPos
URI.hashChange = String(xrf.navigator.URI.fragment||"") != String(URI.fragment||"")
}else{
URI = url
url = URI.source
}
URI.last = xrf.navigator.URI
xrf.navigator.URI = URI
let {directory,file,fragment,fileExt} = URI;
@ -105,7 +110,7 @@ xrf.navigator.init = () => {
window.addEventListener('popstate', function (event){
if( xrf.navigator.updateHash.active ){ // ignore programmatic hash updates (causes infinite recursion)
xrf.navigator.to( document.location.href.replace(/.*\?/,'') )
xrf.navigator.to( xrf.navigator.URI.last )
}
})

View File

@ -53,7 +53,7 @@ xrf.addEventListener('dynamicKeyValue', (opts) => {
xrf.frag.dynamic.material(v,opts) // check if fragment is an objectname
}
if( !xrf.URI.vars[ v.string ] ) return console.error(`'${v.string}' metadata-key not found in scene`)
if( !xrf.URI.vars[ v.string ] ) return // ignore non-template URI fragments
//if( xrf.URI.vars[ id ] && !match.length ) return console.error(`'${id}' object/tag/metadata-key not found in scene`)
if( xrf.debug ) console.log(`URI.vars[${id}] => '${v.string}'`)

View File

@ -55,7 +55,7 @@ xrf.drawLineToMesh = (opts) => {
xrf.addEventListener('render', (opts) => {
// update focusline
let {time,model} = opts
if( !xrf.clock ) return
if( !xrf.clock || !xrf.focusLine ) return
xrf.focusLine.material.color.r = (1.0 + Math.sin( xrf.clock.getElapsedTime()*10 ))/2
xrf.focusLine.material.dashSize = 0.2 + 0.02*Math.sin( xrf.clock.getElapsedTime() )
xrf.focusLine.material.gapSize = 0.1 + 0.02*Math.sin( xrf.clock.getElapsedTime() *3 )

View File

@ -1,14 +0,0 @@
// switch camera when multiple cameras for url #mycameraname
xrf.addEventListener('dynamicKey', (opts) => {
// select active camera if any
let {id,match,v} = opts
match.map( (w) => {
w.nodes.map( (node) => {
if( node.isCamera ){
console.log("switching camera to cam: "+node.name)
xrf.model.camera = node
}
})
})
})

View File

@ -9,9 +9,10 @@ xrf.addEventListener('navigateLoaded', (opts) => {
// Recursive function to traverse the graph
function traverseAndSetEnvMap(node, closestAncestorMaterialMap = null) {
// Check if the current node has a material
if (node.isMesh && node.material) {
if (node.isMesh && node.material ) {
if (node.material.map && closestAncestorMaterialMap) {
// If the node has a material map, set the closest ancestor material map
node.material = node.material.clone() // dont affect objects which share same material
node.material.envMap = closestAncestorMaterialMap;
}
}

View File

@ -30,6 +30,8 @@ xrf.filter.scene = function(opts){
.sort(frag) // get (sorted) filters from XR Fragments
.process(frag,scene,opts) // show/hide things
if( !scene ) return
scene.visible = true // always enable scene
return scene
@ -45,6 +47,7 @@ xrf.filter.sort = function(frag){
// opts = {copyScene:true} in case you want a copy of the scene (not filter the current scene inplace)
xrf.filter.process = function(frag,scene,opts){
if( !scene || scene.children.length == 0 ) return
const cleanupKey = (k) => k.replace(/[-\*\/]/g,'')
let firstFilter = frag.filters.length ? frag.filters[0].filter.get() : false
const hasName = (m,name,filter) => m.name == name

View File

@ -40,7 +40,7 @@ xrf.frag.href = function(v, opts){
let click = mesh.userData.XRF.href.exec = (e) => {
if( !mesh.material || !mesh.material.visible ) return // ignore invisible nodes
if( !mesh.material || !(mesh.material && mesh.material.visible) ) return // ignore invisible nodes
// update our values to the latest value (might be edited)
let URI = xrf.URI.template( mesh.userData.href, xrf.URI.vars.__object )

View File

@ -1,15 +1,18 @@
xrf.frag.pos = function(v, opts){
let { frag, mesh, model, camera, scene, renderer, THREE} = opts
let pos = v
if( !scene )return
let pos = v
// spec: indirect coordinate using objectname: https://xrfragment.org/#navigating%203D
if( pos.x == undefined ){
let obj = scene.getObjectByName(v.string)
if( !obj ) return
pos = obj.position.clone()
obj.getWorldPosition(pos)
camera.position.copy(pos)
if( !obj ) return console.warn("#pos="+v.string+" not found")
obj.add(camera) // follow animation of targeted position
camera.position.set(0,0,0) // set playerheight
//let c = camera.rotation
//c.set( c.x, obj.rotation.y, c.z )
}else{
// spec: direct coordinate: https://xrfragment.org/#navigating%203D
camera.position.x = pos.x
@ -22,6 +25,7 @@ xrf.frag.pos = function(v, opts){
xrf.frag.pos.lastVector3 = camera.position.clone()
camera.updateMatrixWorld()
camera.getCam().updateMatrixWorld()
}
xrf.frag.pos.get = function(precision,randomize){

View File

@ -1,6 +1,9 @@
xrf.frag.rot = function(v, opts){
let { frag, mesh, model, camera, scene, renderer, THREE} = opts
if( xrf.debug ) console.log("#rot.js: setting camera rotation to "+v.string)
if( !camera || !scene ) return
if( !model.isSRC ){
camera.rotation.set(
v.x * Math.PI / 180,

View File

@ -17,6 +17,7 @@ let loadAudio = (mimetype) => function(url,opts){
let sound = isPositionalAudio ? new THREE.PositionalAudio( camera.listener)
: new THREE.Audio( camera.listener )
sound.isXRF = true
mesh.media = mesh.media || {}
mesh.media.audio = { set: (mediafragment,v) => mesh.media.audio[mediafragment] = v }
@ -108,6 +109,7 @@ xrf.addEventListener('reset', () => {
if( n.media && n.media.audio ){
if( n.media.audio.stop ) n.media.audio.stop()
if( n.media.audio.remove ) n.media.audio.remove()
n.remove()
}
})
})

View File

@ -2,7 +2,6 @@
xrf.frag.t = function(v, opts){
let { frag, mesh, model, camera, scene, renderer, THREE} = opts
// handle object media players
if( mesh && mesh.media ){
for( let i in mesh.media ) mesh.media[i].set("t",v)
@ -52,13 +51,14 @@ xrf.addEventListener('parseModel', (opts) => {
model.animations.map( (a) => mixer.duration = ( a.duration > mixer.duration ) ? a.duration : mixer.duration )
}
model.animations.map( (anim) => {
anim.optimize()
if( xrf.debug ) console.log("action: "+anim.name)
model.animations.map( (anim) => {
console.log("animation action: "+anim.name)
mixer.actions.push( mixer.clipAction( anim, model.scene ) )
})
mixer.play = (t) => {
let msg = `media fragment: ${t.x}-${t.y} seconds`
if( t.x > 49 ) msg += ", not frames (!)"
console.log(msg)
mixer.isPlaying = t.x !== undefined && t.x != t.y
mixer.updateLoop(t)
xrf.emit( mixer.isPlaying === false ? 'stop' : 'play',{isPlaying: mixer.isPlaying})