Compare commits
70 Commits
Author | SHA1 | Date |
---|---|---|
Leon van Kammen | fb7b9c2c9c | |
Leon van Kammen | 2ce4a39ea8 | |
Leon van Kammen | 4c15b4cbf1 | |
Leon van Kammen | 3a3f8f555c | |
Leon van Kammen | 6cc43f4011 | |
Leon van Kammen | bf6efb173a | |
Leon van Kammen | a79e6414d2 | |
Leon van Kammen | 519d1a5894 | |
Leon van Kammen | 287879997d | |
Leon van Kammen | e6eb94910b | |
Leon van Kammen | 43e5f82402 | |
Leon van Kammen | 57053d40c2 | |
Leon van Kammen | 970805b5c6 | |
Leon van Kammen | 41496ea9cf | |
Leon van Kammen | ab77db680d | |
Leon van Kammen | 94346dbdff | |
Leon van Kammen | 9efaeab5b5 | |
Leon van Kammen | 1ae1a46580 | |
Leon van Kammen | f9be7a946a | |
Leon van Kammen | 659c2da2a5 | |
Leon van Kammen | 78b60210bc | |
Leon van Kammen | ba90411e6c | |
Leon van Kammen | 7abdcf1016 | |
Leon van Kammen | aacb9d13e2 | |
Leon van Kammen | 090016089c | |
Leon van Kammen | a37de3ec8d | |
Leon van Kammen | 935e0898c9 | |
Leon van Kammen | 9a13746c1c | |
Leon van Kammen | 7025074612 | |
Leon van Kammen | e7d282f268 | |
Leon van Kammen | 9c23a6e2a3 | |
Leon van Kammen | edfab56347 | |
Leon van Kammen | 9fc7d3c6f6 | |
Leon van Kammen | 186f2af440 | |
Leon van Kammen | ab69dac0ec | |
Leon van Kammen | 4bc3d1c520 | |
Leon van Kammen | c3b0636bf7 | |
Leon van Kammen | 43ca5c1cc6 | |
Leon van Kammen | 0bd37a3b30 | |
Leon van Kammen | b7f3c91b7f | |
Leon van Kammen | 28770956b4 | |
Leon van Kammen | e0ee7e10e0 | |
Leon van Kammen | 5318eed885 | |
Leon van Kammen | 89147de484 | |
Leon van Kammen | 69e801e4a5 | |
Leon van Kammen | 4d68b50ede | |
Leon van Kammen | a54ff56a8d | |
Leon van Kammen | 676b8d1fd2 | |
Leon van Kammen | 2ce60fc493 | |
Leon van Kammen | 60ed159b0c | |
Leon van Kammen | f0d97b91ab | |
Leon van Kammen | 8d776ba4b0 | |
Leon van Kammen | b288e0343d | |
Leon van Kammen | f73ddba0c0 | |
Leon van Kammen | 0a5047ec3d | |
Leon van Kammen | 79a3b7e151 | |
Leon van Kammen | b72914c49c | |
Leon van Kammen | de1acb539a | |
Leon van Kammen | 4b92836422 | |
Leon van Kammen | 457935a426 | |
Leon van Kammen | 0dc2b1f2e4 | |
Leon van Kammen | 91b08014fa | |
Leon van Kammen | f865c22694 | |
Leon van Kammen | cc49c4ad92 | |
Leon van Kammen | 82ab6e2882 | |
Leon van Kammen | f14ff60252 | |
Leon van Kammen | cffd1e1446 | |
Leon van Kammen | c813a45eb1 | |
Leon van Kammen | de5fe7002a | |
Leon van Kammen | e0b50f039b |
|
@ -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/* .
|
||||
}
|
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
|
@ -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"] || {};
|
||||
|
|
|
@ -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
File diff suppressed because it is too large
Load Diff
|
@ -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
|
||||
*/
|
||||
|
|
|
@ -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
|
@ -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
|
||||
*/
|
||||
|
|
|
@ -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
|
||||
*/
|
||||
|
|
|
@ -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
|
||||
*/
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Generated by Haxe 4.3.3
|
||||
# Generated by Haxe 4.3.6
|
||||
# coding: utf-8
|
||||
import sys
|
||||
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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 |
|
@ -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’s & <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’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 (‘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 <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&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 ‘extras’ 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>"href": "b.gltf"</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>"src": "#cube"</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>"tag": "cubes geo"</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>"#": "#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">{
|
||||
"/":{
|
||||
"#": "#-penguin",
|
||||
"aria-description": "description of scene",
|
||||
},
|
||||
"/room/chair": {
|
||||
"href": "#penguin"
|
||||
}
|
||||
}
|
||||
</code></pre>
|
||||
|
||||
<blockquote>
|
||||
<p>This would mean: hide object(s) with name or <code>tag</code>-value ‘penguin’ 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['#'] = "#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
|
||||
</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&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 & 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>"href": "b.gltf"</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>"src": "#cube"</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>"tag": "cubes geo"</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>"#": "#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>#<preset></code></td>
|
||||
<td>string</td>
|
||||
<td><code>#cubes</code></td>
|
||||
<td>evaluates preset (<code>#foo&bar</code>) defined in 3D Object metadata (<code>#cubes: #foo&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&bar</code>) when a scene contains extra (<code>#cubes: #foo&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>#<cameraname></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>#<tag_or_objectname>[*]=<materialname></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’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’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.</p>
|
||||
|
||||
<p>”“”
|
||||
+——————————–+<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
|
||||
+——————————–+</p>
|
||||
|
||||
<pre><code>
|
||||
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:
|
||||
|
||||
+--------------------------------+
|
||||
</code></pre>
|
||||
|
||||
<p>+────────────────────────────────────────────────────────+
|
||||
<p>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:</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’s ‘transclusion resolution’, there’s a soft-mechanism to harden links & 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 <——— 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 <--------- 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.<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:
|
||||
+────────────────────────────────────────────────────────+
|
||||
|
||||
</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&-topics math</code></li>
|
||||
<li><code>href: schoolB.edu/projects.gltf#math&-courses math</code></li>
|
||||
<li><code>href: university.edu/projects.gltf#math&-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&-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>”`</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’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 format’s
|
||||
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’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’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 ‘entity’-part of their entity-component-system (ECS). The ‘component’-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: '' | > 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' │
|
||||
│ | │
|
||||
+────────────────────────────────────────────────────────────────────────────────────────────────────────+
|
||||
</code></pre>
|
||||
|
||||
<ul>
|
||||
<li>key/value syntax: -<code><vendorname></code>-<code><component|version></code>.<code><key></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 ‘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.</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>
|
||||
|
||||
|
|
|
@ -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 format’s
|
||||
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
|
@ -10,13 +10,16 @@
|
|||
<workgroup>Jens & Leon Internet Engineering Task Force</workgroup>
|
||||
|
||||
<abstract>
|
||||
<t>This draft is a specification for 4D URI's & <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 <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.</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&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>"href": "b.gltf"</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>"src": "#cube"</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>"tag": "cubes geo"</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>"#": "#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&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 & 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>"href": "b.gltf"</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>"src": "#cube"</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>"tag": "cubes geo"</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>"#": "#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>#<preset></tt></td>
|
||||
<td>string</td>
|
||||
<td><tt>#cubes</tt></td>
|
||||
<td>evaluates preset (<tt>#foo&bar</tt>) defined in 3D Object metadata (<tt>#cubes: #foo&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&bar</tt>) when a scene contains extra (<tt>#cubes: #foo&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>#<cameraname></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>#<tag_or_objectname>[*]=<materialname></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><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 <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 & 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 & 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 <--------- 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&-topics math</tt></li>
|
||||
<li><tt>href: schoolB.edu/projects.gltf#math&-courses math</tt></li>
|
||||
<li><tt>href: university.edu/projects.gltf#math&-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&-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&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 format’s
|
||||
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><vendorname></tt>-<tt><component|version></tt>.<tt><key></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>
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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.
Binary file not shown.
|
@ -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();
|
||||
|
|
170
index.html
170
index.html
File diff suppressed because one or more lines are too long
2
make
2
make
|
@ -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
|
||||
|
|
|
@ -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/) ){
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
}())
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
@ -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();
|
||||
|
|
|
@ -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 )
|
||||
}
|
||||
})
|
||||
|
||||
|
|
|
@ -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}'`)
|
||||
|
|
|
@ -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 )
|
||||
|
|
|
@ -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
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 )
|
||||
|
|
|
@ -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){
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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})
|
||||
|
|
Loading…
Reference in New Issue