2025-01-15 10:53:56 +01:00
/ *
* 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 Wed Jan 15 10 : 25 : 57 AM CET 2025
* https : //xrfragment.org
* SPDX - License - Identifier : AGPL - 3.0 - or - later
* /
/ *
* 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
* /
2024-12-16 14:19:12 +01:00
/ *
2025-01-14 15:36:36 +01:00
* v0 . 5.1 generated at Tue Jan 14 03 : 35 : 01 PM CET 2025
2024-12-16 14:19:12 +01:00
* https : //xrfragment.org
* SPDX - License - Identifier : AGPL - 3.0 - or - later
* /
2024-12-16 14:17:12 +01:00
/ *
2025-01-14 15:36:36 +01:00
* v0 . 5.1 generated at Tue Jan 14 03 : 33 : 48 PM CET 2025
2024-12-16 14:17:12 +01:00
* https : //xrfragment.org
* SPDX - License - Identifier : AGPL - 3.0 - or - later
* /
2024-12-16 13:49:18 +01:00
/ *
2025-01-14 15:36:36 +01:00
* v0 . 5.1 generated at Tue Jan 14 03 : 33 : 30 PM CET 2025
2024-12-16 13:49:18 +01:00
* https : //xrfragment.org
* SPDX - License - Identifier : AGPL - 3.0 - or - later
* /
/ *
2025-01-14 15:36:36 +01:00
* v0 . 5.1 generated at Tue Jan 14 03 : 33 : 24 PM CET 2025
2024-12-16 13:49:18 +01:00
* https : //xrfragment.org
* SPDX - License - Identifier : AGPL - 3.0 - or - later
* /
/ *
2025-01-14 15:36:36 +01:00
* v0 . 5.1 generated at Tue Jan 14 03 : 32 : 59 PM CET 2025
2024-12-16 13:49:18 +01:00
* https : //xrfragment.org
* SPDX - License - Identifier : AGPL - 3.0 - or - later
* /
/ *
2025-01-14 15:36:36 +01:00
* v0 . 5.1 generated at Tue Jan 14 03 : 26 : 30 PM CET 2025
2024-12-16 13:49:18 +01:00
* https : //xrfragment.org
* SPDX - License - Identifier : AGPL - 3.0 - or - later
* /
2024-12-11 10:52:27 +01:00
/ *
2025-01-14 15:36:36 +01:00
* v0 . 5.1 generated at Tue Jan 14 03 : 26 : 19 PM CET 2025
2024-12-11 10:52:27 +01:00
* https : //xrfragment.org
* SPDX - License - Identifier : AGPL - 3.0 - or - later
* /
/ *
2025-01-14 15:36:36 +01:00
* v0 . 5.1 generated at Tue Jan 14 03 : 26 : 05 PM CET 2025
2024-12-11 10:52:27 +01:00
* https : //xrfragment.org
* SPDX - License - Identifier : AGPL - 3.0 - or - later
* /
/ *
2025-01-14 15:36:36 +01:00
* v0 . 5.1 generated at Tue Jan 14 03 : 24 : 14 PM CET 2025
2024-12-11 10:52:27 +01:00
* https : //xrfragment.org
* SPDX - License - Identifier : AGPL - 3.0 - or - later
* /
/ *
2025-01-14 15:36:36 +01:00
* v0 . 5.1 generated at Tue Jan 14 03 : 23 : 45 PM CET 2025
2024-12-11 10:52:27 +01:00
* https : //xrfragment.org
* SPDX - License - Identifier : AGPL - 3.0 - or - later
* /
/ *
2025-01-14 15:36:36 +01:00
* v0 . 5.1 generated at Tue Jan 14 03 : 23 : 27 PM CET 2025
2024-12-11 10:52:27 +01:00
* https : //xrfragment.org
* SPDX - License - Identifier : AGPL - 3.0 - or - later
* /
/ *
2025-01-14 15:36:36 +01:00
* v0 . 5.1 generated at Tue Jan 14 03 : 23 : 12 PM CET 2025
2024-12-11 10:52:27 +01:00
* https : //xrfragment.org
* SPDX - License - Identifier : AGPL - 3.0 - or - later
* /
/ *
2025-01-14 15:36:36 +01:00
* v0 . 5.1 generated at Tue Jan 14 03 : 22 : 13 PM CET 2025
2024-12-11 10:52:27 +01:00
* https : //xrfragment.org
* SPDX - License - Identifier : AGPL - 3.0 - or - later
* /
/ *
2025-01-14 15:36:36 +01:00
* v0 . 5.1 generated at Tue Jan 14 03 : 21 : 51 PM CET 2025
2024-12-11 10:52:27 +01:00
* https : //xrfragment.org
* SPDX - License - Identifier : AGPL - 3.0 - or - later
* /
/ *
2025-01-14 15:36:36 +01:00
* v0 . 5.1 generated at Tue Jan 14 03 : 21 : 36 PM CET 2025
2024-12-11 10:52:27 +01:00
* https : //xrfragment.org
* SPDX - License - Identifier : AGPL - 3.0 - or - later
* /
/ *
2025-01-14 15:36:36 +01:00
* v0 . 5.1 generated at Tue Jan 14 03 : 20 : 54 PM CET 2025
2024-12-11 10:52:27 +01:00
* https : //xrfragment.org
* SPDX - License - Identifier : AGPL - 3.0 - or - later
* /
/ *
2025-01-14 15:36:36 +01:00
* v0 . 5.1 generated at Tue Jan 14 03 : 20 : 39 PM CET 2025
2024-12-11 10:52:27 +01:00
* https : //xrfragment.org
* SPDX - License - Identifier : AGPL - 3.0 - or - later
* /
/ *
2025-01-14 15:36:36 +01:00
* v0 . 5.1 generated at Tue Jan 14 03 : 20 : 13 PM CET 2025
2024-12-11 10:52:27 +01:00
* https : //xrfragment.org
* SPDX - License - Identifier : AGPL - 3.0 - or - later
* /
/ *
2025-01-14 15:36:36 +01:00
* v0 . 5.1 generated at Tue Jan 14 03 : 19 : 55 PM CET 2025
2024-12-11 10:52:27 +01:00
* https : //xrfragment.org
* SPDX - License - Identifier : AGPL - 3.0 - or - later
* /
/ *
2025-01-14 15:36:36 +01:00
* v0 . 5.1 generated at Tue Jan 14 03 : 18 : 34 PM CET 2025
2024-12-11 10:52:27 +01:00
* https : //xrfragment.org
* SPDX - License - Identifier : AGPL - 3.0 - or - later
* /
/ *
2025-01-14 15:36:36 +01:00
* v0 . 5.1 generated at Tue Jan 14 03 : 01 : 28 PM CET 2025
2024-12-11 10:52:27 +01:00
* https : //xrfragment.org
* SPDX - License - Identifier : AGPL - 3.0 - or - later
* /
2024-12-10 15:49:42 +01:00
/ *
2025-01-14 15:36:36 +01:00
* v0 . 5.1 generated at Tue Jan 14 03 : 00 : 58 PM CET 2025
2024-12-10 15:49:42 +01:00
* https : //xrfragment.org
* SPDX - License - Identifier : AGPL - 3.0 - or - later
* /
2024-12-10 15:41:07 +01:00
/ *
2025-01-14 15:36:36 +01:00
* v0 . 5.1 generated at Tue Jan 14 02 : 55 : 26 PM CET 2025
2024-12-10 15:41:07 +01:00
* https : //xrfragment.org
* SPDX - License - Identifier : AGPL - 3.0 - or - later
* /
/ *
2025-01-14 15:36:36 +01:00
* v0 . 5.1 generated at Tue Jan 14 02 : 55 : 01 PM CET 2025
2024-12-10 15:41:07 +01:00
* https : //xrfragment.org
* SPDX - License - Identifier : AGPL - 3.0 - or - later
* /
/ *
2025-01-14 15:36:36 +01:00
* v0 . 5.1 generated at Tue Jan 14 02 : 54 : 21 PM CET 2025
2024-12-10 15:41:07 +01:00
* https : //xrfragment.org
* SPDX - License - Identifier : AGPL - 3.0 - or - later
* /
/ *
2025-01-14 15:36:36 +01:00
* v0 . 5.1 generated at Tue Jan 14 02 : 54 : 04 PM CET 2025
2024-12-10 15:41:07 +01:00
* https : //xrfragment.org
* SPDX - License - Identifier : AGPL - 3.0 - or - later
* /
/ *
2025-01-14 15:36:36 +01:00
* v0 . 5.1 generated at Tue Jan 14 02 : 53 : 31 PM CET 2025
2024-12-10 15:41:07 +01:00
* https : //xrfragment.org
* SPDX - License - Identifier : AGPL - 3.0 - or - later
* /
/ *
2025-01-14 15:36:36 +01:00
* v0 . 5.1 generated at Tue Jan 14 02 : 52 : 11 PM CET 2025
2024-12-10 15:41:07 +01:00
* https : //xrfragment.org
* SPDX - License - Identifier : AGPL - 3.0 - or - later
* /
/ *
2025-01-14 15:36:36 +01:00
* v0 . 5.1 generated at Tue Jan 14 02 : 51 : 59 PM CET 2025
2024-12-10 15:41:07 +01:00
* https : //xrfragment.org
* SPDX - License - Identifier : AGPL - 3.0 - or - later
* /
/ *
2025-01-14 15:36:36 +01:00
* v0 . 5.1 generated at Tue Jan 14 02 : 51 : 44 PM CET 2025
2024-12-10 15:41:07 +01:00
* https : //xrfragment.org
* SPDX - License - Identifier : AGPL - 3.0 - or - later
* /
/ *
2025-01-14 15:36:36 +01:00
* v0 . 5.1 generated at Tue Jan 14 02 : 51 : 01 PM CET 2025
2024-12-10 15:41:07 +01:00
* https : //xrfragment.org
* SPDX - License - Identifier : AGPL - 3.0 - or - later
* /
/ *
2025-01-14 15:36:36 +01:00
* v0 . 5.1 generated at Tue Jan 14 02 : 50 : 51 PM CET 2025
2024-12-10 15:41:07 +01:00
* https : //xrfragment.org
* SPDX - License - Identifier : AGPL - 3.0 - or - later
* /
/ *
2025-01-14 15:36:36 +01:00
* v0 . 5.1 generated at Tue Jan 14 02 : 50 : 39 PM CET 2025
2024-12-10 15:41:07 +01:00
* https : //xrfragment.org
* SPDX - License - Identifier : AGPL - 3.0 - or - later
* /
/ *
2025-01-14 15:36:36 +01:00
* v0 . 5.1 generated at Tue Jan 14 02 : 49 : 27 PM CET 2025
2024-12-10 15:41:07 +01:00
* https : //xrfragment.org
* SPDX - License - Identifier : AGPL - 3.0 - or - later
* /
/ *
2025-01-14 15:36:36 +01:00
* v0 . 5.1 generated at Tue Jan 14 02 : 48 : 13 PM CET 2025
2024-12-10 15:41:07 +01:00
* https : //xrfragment.org
* SPDX - License - Identifier : AGPL - 3.0 - or - later
* /
/ *
2025-01-14 15:36:36 +01:00
* v0 . 5.1 generated at Tue Jan 14 02 : 42 : 07 PM CET 2025
2024-12-10 15:41:07 +01:00
* https : //xrfragment.org
* SPDX - License - Identifier : AGPL - 3.0 - or - later
* /
/ *
2025-01-14 15:36:36 +01:00
* v0 . 5.1 generated at Tue Jan 14 02 : 41 : 43 PM CET 2025
2024-12-10 15:41:07 +01:00
* https : //xrfragment.org
* SPDX - License - Identifier : AGPL - 3.0 - or - later
* /
/ *
2025-01-14 15:36:36 +01:00
* v0 . 5.1 generated at Tue Jan 14 02 : 41 : 25 PM CET 2025
2024-12-10 15:41:07 +01:00
* https : //xrfragment.org
* SPDX - License - Identifier : AGPL - 3.0 - or - later
* /
/ *
2025-01-14 15:36:36 +01:00
* v0 . 5.1 generated at Tue Jan 14 02 : 40 : 51 PM CET 2025
2024-12-10 15:41:07 +01:00
* https : //xrfragment.org
* SPDX - License - Identifier : AGPL - 3.0 - or - later
* /
/ *
2025-01-14 15:36:36 +01:00
* v0 . 5.1 generated at Tue Jan 14 02 : 39 : 41 PM CET 2025
2024-12-10 15:41:07 +01:00
* https : //xrfragment.org
* SPDX - License - Identifier : AGPL - 3.0 - or - later
* /
/ *
2025-01-14 15:36:36 +01:00
* v0 . 5.1 generated at Tue Jan 14 02 : 38 : 52 PM CET 2025
2024-12-10 15:41:07 +01:00
* https : //xrfragment.org
* SPDX - License - Identifier : AGPL - 3.0 - or - later
* /
/ *
2025-01-14 15:36:36 +01:00
* v0 . 5.1 generated at Tue Jan 14 02 : 38 : 19 PM CET 2025
2024-12-10 15:41:07 +01:00
* https : //xrfragment.org
* SPDX - License - Identifier : AGPL - 3.0 - or - later
* /
/ *
2025-01-14 15:36:36 +01:00
* v0 . 5.1 generated at Tue Jan 14 02 : 37 : 01 PM CET 2025
2024-12-10 15:41:07 +01:00
* https : //xrfragment.org
* SPDX - License - Identifier : AGPL - 3.0 - or - later
* /
/ *
2025-01-14 15:36:36 +01:00
* v0 . 5.1 generated at Tue Jan 14 02 : 36 : 19 PM CET 2025
2024-12-10 15:41:07 +01:00
* https : //xrfragment.org
* SPDX - License - Identifier : AGPL - 3.0 - or - later
* /
/ *
2025-01-14 15:36:36 +01:00
* v0 . 5.1 generated at Tue Jan 14 02 : 35 : 59 PM CET 2025
2024-12-10 15:41:07 +01:00
* https : //xrfragment.org
* SPDX - License - Identifier : AGPL - 3.0 - or - later
* /
/ *
2025-01-14 15:36:36 +01:00
* v0 . 5.1 generated at Tue Jan 14 02 : 35 : 44 PM CET 2025
2024-12-10 15:41:07 +01:00
* https : //xrfragment.org
* SPDX - License - Identifier : AGPL - 3.0 - or - later
* /
/ *
2025-01-14 15:36:36 +01:00
* v0 . 5.1 generated at Tue Jan 14 02 : 35 : 28 PM CET 2025
2024-12-10 15:41:07 +01:00
* https : //xrfragment.org
* SPDX - License - Identifier : AGPL - 3.0 - or - later
* /
/ *
2025-01-14 15:36:36 +01:00
* v0 . 5.1 generated at Tue Jan 14 02 : 34 : 46 PM CET 2025
2024-12-10 15:41:07 +01:00
* https : //xrfragment.org
* SPDX - License - Identifier : AGPL - 3.0 - or - later
* /
/ *
2025-01-14 15:36:36 +01:00
* v0 . 5.1 generated at Tue Jan 14 02 : 32 : 50 PM CET 2025
2024-12-10 15:41:07 +01:00
* https : //xrfragment.org
* SPDX - License - Identifier : AGPL - 3.0 - or - later
* /
/ *
2025-01-14 15:36:36 +01:00
* v0 . 5.1 generated at Tue Jan 14 02 : 31 : 07 PM CET 2025
2024-12-10 15:41:07 +01:00
* https : //xrfragment.org
* SPDX - License - Identifier : AGPL - 3.0 - or - later
* /
/ *
2025-01-14 15:36:36 +01:00
* v0 . 5.1 generated at Tue Jan 14 02 : 30 : 27 PM CET 2025
2024-12-10 15:41:07 +01:00
* https : //xrfragment.org
* SPDX - License - Identifier : AGPL - 3.0 - or - later
* /
/ *
2025-01-14 15:36:36 +01:00
* v0 . 5.1 generated at Tue Jan 14 02 : 24 : 55 PM CET 2025
2024-12-10 15:41:07 +01:00
* https : //xrfragment.org
* SPDX - License - Identifier : AGPL - 3.0 - or - later
* /
/ *
2025-01-14 15:36:36 +01:00
* v0 . 5.1 generated at Fri Jan 10 07 : 51 : 18 PM CET 2025
2024-12-10 15:41:07 +01:00
* https : //xrfragment.org
* SPDX - License - Identifier : AGPL - 3.0 - or - later
* /
2025-01-14 15:36:36 +01:00
// Generated by Haxe 4.3.6
2023-05-04 16:24:54 +02:00
var $hx _exports = typeof exports != "undefined" ? exports : typeof window != "undefined" ? window : typeof self != "undefined" ? self : this ;
( function ( $global ) { "use strict" ;
$hx _exports [ "xrfragment" ] = $hx _exports [ "xrfragment" ] || { } ;
2024-02-16 17:36:27 +01:00
var $estr = function ( ) { return js _Boot . _ _string _rec ( this , '' ) ; } , $hxEnums = $hxEnums || { } , $ _ ;
function $extend ( from , fields ) {
var proto = Object . create ( from ) ;
for ( var name in fields ) proto [ name ] = fields [ name ] ;
if ( fields . toString !== Object . prototype . toString ) proto . toString = fields . toString ;
return proto ;
}
2023-05-04 16:24:54 +02:00
var EReg = function ( r , opt ) {
this . r = new RegExp ( r , opt . split ( "u" ) . join ( "" ) ) ;
} ;
EReg . _ _name _ _ = true ;
EReg . prototype = {
match : function ( s ) {
if ( this . r . global ) {
this . r . lastIndex = 0 ;
}
this . r . m = this . r . exec ( s ) ;
this . r . s = s ;
return this . r . m != null ;
}
2024-02-16 17:36:27 +01:00
, matched : function ( n ) {
if ( this . r . m != null && n >= 0 && n < this . r . m . length ) {
return this . r . m [ n ] ;
} else {
throw haxe _Exception . thrown ( "EReg::matched" ) ;
}
}
, matchedRight : function ( ) {
if ( this . r . m == null ) {
throw haxe _Exception . thrown ( "No string matched" ) ;
}
var sz = this . r . m . index + this . r . m [ 0 ] . length ;
return HxOverrides . substr ( this . r . s , sz , this . r . s . length - sz ) ;
}
, matchedPos : function ( ) {
if ( this . r . m == null ) {
throw haxe _Exception . thrown ( "No string matched" ) ;
}
return { pos : this . r . m . index , len : this . r . m [ 0 ] . length } ;
}
2023-05-04 16:24:54 +02:00
, split : function ( s ) {
var d = "#__delim__#" ;
return s . replace ( this . r , d ) . split ( d ) ;
}
} ;
var HxOverrides = function ( ) { } ;
HxOverrides . _ _name _ _ = true ;
HxOverrides . cca = function ( s , index ) {
var x = s . charCodeAt ( index ) ;
if ( x != x ) {
return undefined ;
}
return x ;
} ;
HxOverrides . substr = function ( s , pos , len ) {
if ( len == null ) {
len = s . length ;
} else if ( len < 0 ) {
if ( pos == 0 ) {
len = s . length + len ;
} else {
return "" ;
}
}
return s . substr ( pos , len ) ;
} ;
HxOverrides . now = function ( ) {
return Date . now ( ) ;
} ;
Math . _ _name _ _ = true ;
var Reflect = function ( ) { } ;
Reflect . _ _name _ _ = true ;
Reflect . field = function ( o , field ) {
try {
return o [ field ] ;
} catch ( _g ) {
return null ;
}
} ;
2024-02-16 17:36:27 +01:00
Reflect . getProperty = function ( o , field ) {
var tmp ;
if ( o == null ) {
return null ;
} else {
var tmp1 ;
if ( o . _ _properties _ _ ) {
tmp = o . _ _properties _ _ [ "get_" + field ] ;
tmp1 = tmp ;
} else {
tmp1 = false ;
}
if ( tmp1 ) {
return o [ tmp ] ( ) ;
} else {
return o [ field ] ;
}
}
} ;
2023-05-04 16:24:54 +02:00
Reflect . fields = function ( o ) {
var a = [ ] ;
if ( o != null ) {
var hasOwnProperty = Object . prototype . hasOwnProperty ;
for ( var f in o ) {
if ( f != "__id__" && f != "hx__closures__" && hasOwnProperty . call ( o , f ) ) {
a . push ( f ) ;
}
}
}
return a ;
} ;
2024-02-16 17:36:27 +01:00
Reflect . isObject = function ( v ) {
if ( v == null ) {
return false ;
}
var t = typeof ( v ) ;
if ( ! ( t == "string" || t == "object" && v . _ _enum _ _ == null ) ) {
if ( t == "function" ) {
return ( v . _ _name _ _ || v . _ _ename _ _ ) != null ;
} else {
return false ;
}
} else {
return true ;
}
} ;
2023-05-04 16:24:54 +02:00
Reflect . deleteField = function ( o , field ) {
if ( ! Object . prototype . hasOwnProperty . call ( o , field ) ) {
return false ;
}
delete ( o [ field ] ) ;
return true ;
} ;
2023-11-28 17:39:33 +01:00
Reflect . copy = function ( o ) {
if ( o == null ) {
return null ;
}
var o2 = { } ;
var _g = 0 ;
var _g1 = Reflect . fields ( o ) ;
while ( _g < _g1 . length ) {
var f = _g1 [ _g ] ;
++ _g ;
o2 [ f ] = Reflect . field ( o , f ) ;
}
return o2 ;
} ;
2023-05-04 16:24:54 +02:00
var Std = function ( ) { } ;
Std . _ _name _ _ = true ;
Std . string = function ( s ) {
return js _Boot . _ _string _rec ( s , "" ) ;
} ;
Std . parseInt = function ( x ) {
2024-07-09 19:27:21 +02:00
var v = parseInt ( x ) ;
if ( isNaN ( v ) ) {
return null ;
2023-05-04 16:24:54 +02:00
}
2024-07-09 19:27:21 +02:00
return v ;
2023-05-04 16:24:54 +02:00
} ;
2024-02-16 17:36:27 +01:00
var StringBuf = function ( ) {
this . b = "" ;
} ;
StringBuf . _ _name _ _ = true ;
2023-05-04 16:24:54 +02:00
var StringTools = function ( ) { } ;
StringTools . _ _name _ _ = true ;
StringTools . isSpace = function ( s , pos ) {
var c = HxOverrides . cca ( s , pos ) ;
if ( ! ( c > 8 && c < 14 ) ) {
return c == 32 ;
} else {
return true ;
}
} ;
StringTools . ltrim = function ( s ) {
var l = s . length ;
var r = 0 ;
while ( r < l && StringTools . isSpace ( s , r ) ) ++ r ;
if ( r > 0 ) {
return HxOverrides . substr ( s , r , l - r ) ;
} else {
return s ;
}
} ;
StringTools . rtrim = function ( s ) {
var l = s . length ;
var r = 0 ;
while ( r < l && StringTools . isSpace ( s , l - r - 1 ) ) ++ r ;
if ( r > 0 ) {
return HxOverrides . substr ( s , 0 , l - r ) ;
} else {
return s ;
}
} ;
StringTools . trim = function ( s ) {
return StringTools . ltrim ( StringTools . rtrim ( s ) ) ;
} ;
2024-02-16 17:36:27 +01:00
StringTools . replace = function ( s , sub , by ) {
return s . split ( sub ) . join ( by ) ;
} ;
var haxe _Exception = function ( message , previous , native ) {
Error . call ( this , message ) ;
this . message = message ;
this . _ _previousException = previous ;
this . _ _nativeException = native != null ? native : this ;
} ;
haxe _Exception . _ _name _ _ = true ;
haxe _Exception . caught = function ( value ) {
if ( ( ( value ) instanceof haxe _Exception ) ) {
return value ;
} else if ( ( ( value ) instanceof Error ) ) {
return new haxe _Exception ( value . message , null , value ) ;
} else {
return new haxe _ValueException ( value , null , value ) ;
}
} ;
haxe _Exception . thrown = function ( value ) {
if ( ( ( value ) instanceof haxe _Exception ) ) {
return value . get _native ( ) ;
} else if ( ( ( value ) instanceof Error ) ) {
return value ;
} else {
var e = new haxe _ValueException ( value ) ;
return e ;
}
} ;
haxe _Exception . _ _super _ _ = Error ;
haxe _Exception . prototype = $extend ( Error . prototype , {
unwrap : function ( ) {
return this . _ _nativeException ;
}
, get _native : function ( ) {
return this . _ _nativeException ;
}
, _ _properties _ _ : { get _native : "get_native" }
} ) ;
var haxe _ _$Template _TemplateExpr = $hxEnums [ "haxe._Template.TemplateExpr" ] = { _ _ename _ _ : true , _ _constructs _ _ : null
, OpVar : ( $ _ = function ( v ) { return { _hx _index : 0 , v : v , _ _enum _ _ : "haxe._Template.TemplateExpr" , toString : $estr } ; } , $ _ . _hx _name = "OpVar" , $ _ . _ _params _ _ = [ "v" ] , $ _ )
, OpExpr : ( $ _ = function ( expr ) { return { _hx _index : 1 , expr : expr , _ _enum _ _ : "haxe._Template.TemplateExpr" , toString : $estr } ; } , $ _ . _hx _name = "OpExpr" , $ _ . _ _params _ _ = [ "expr" ] , $ _ )
, OpIf : ( $ _ = function ( expr , eif , eelse ) { return { _hx _index : 2 , expr : expr , eif : eif , eelse : eelse , _ _enum _ _ : "haxe._Template.TemplateExpr" , toString : $estr } ; } , $ _ . _hx _name = "OpIf" , $ _ . _ _params _ _ = [ "expr" , "eif" , "eelse" ] , $ _ )
, OpStr : ( $ _ = function ( str ) { return { _hx _index : 3 , str : str , _ _enum _ _ : "haxe._Template.TemplateExpr" , toString : $estr } ; } , $ _ . _hx _name = "OpStr" , $ _ . _ _params _ _ = [ "str" ] , $ _ )
, OpBlock : ( $ _ = function ( l ) { return { _hx _index : 4 , l : l , _ _enum _ _ : "haxe._Template.TemplateExpr" , toString : $estr } ; } , $ _ . _hx _name = "OpBlock" , $ _ . _ _params _ _ = [ "l" ] , $ _ )
, OpForeach : ( $ _ = function ( expr , loop ) { return { _hx _index : 5 , expr : expr , loop : loop , _ _enum _ _ : "haxe._Template.TemplateExpr" , toString : $estr } ; } , $ _ . _hx _name = "OpForeach" , $ _ . _ _params _ _ = [ "expr" , "loop" ] , $ _ )
, OpMacro : ( $ _ = function ( name , params ) { return { _hx _index : 6 , name : name , params : params , _ _enum _ _ : "haxe._Template.TemplateExpr" , toString : $estr } ; } , $ _ . _hx _name = "OpMacro" , $ _ . _ _params _ _ = [ "name" , "params" ] , $ _ )
} ;
haxe _ _$Template _TemplateExpr . _ _constructs _ _ = [ haxe _ _$Template _TemplateExpr . OpVar , haxe _ _$Template _TemplateExpr . OpExpr , haxe _ _$Template _TemplateExpr . OpIf , haxe _ _$Template _TemplateExpr . OpStr , haxe _ _$Template _TemplateExpr . OpBlock , haxe _ _$Template _TemplateExpr . OpForeach , haxe _ _$Template _TemplateExpr . OpMacro ] ;
2023-05-04 16:24:54 +02:00
var haxe _iterators _ArrayIterator = function ( array ) {
this . current = 0 ;
this . array = array ;
} ;
haxe _iterators _ArrayIterator . _ _name _ _ = true ;
haxe _iterators _ArrayIterator . prototype = {
hasNext : function ( ) {
return this . current < this . array . length ;
}
, next : function ( ) {
return this . array [ this . current ++ ] ;
}
} ;
2024-02-16 17:36:27 +01:00
var haxe _Template = function ( str ) {
var tokens = this . parseTokens ( str ) ;
this . expr = this . parseBlock ( tokens ) ;
if ( ! tokens . isEmpty ( ) ) {
throw haxe _Exception . thrown ( "Unexpected '" + Std . string ( tokens . first ( ) . s ) + "'" ) ;
}
} ;
haxe _Template . _ _name _ _ = true ;
haxe _Template . prototype = {
execute : function ( context , macros ) {
this . macros = macros == null ? { } : macros ;
this . context = context ;
this . stack = new haxe _ds _List ( ) ;
this . buf = new StringBuf ( ) ;
this . run ( this . expr ) ;
return this . buf . b ;
}
, resolve : function ( v ) {
if ( v == "__current__" ) {
return this . context ;
}
if ( Reflect . isObject ( this . context ) ) {
var value = Reflect . getProperty ( this . context , v ) ;
if ( value != null || Object . prototype . hasOwnProperty . call ( this . context , v ) ) {
return value ;
}
}
var _g _head = this . stack . h ;
while ( _g _head != null ) {
var val = _g _head . item ;
_g _head = _g _head . next ;
var ctx = val ;
var value = Reflect . getProperty ( ctx , v ) ;
if ( value != null || Object . prototype . hasOwnProperty . call ( ctx , v ) ) {
return value ;
}
}
return Reflect . field ( haxe _Template . globals , v ) ;
}
, parseTokens : function ( data ) {
var tokens = new haxe _ds _List ( ) ;
while ( haxe _Template . splitter . match ( data ) ) {
var p = haxe _Template . splitter . matchedPos ( ) ;
if ( p . pos > 0 ) {
tokens . add ( { p : HxOverrides . substr ( data , 0 , p . pos ) , s : true , l : null } ) ;
}
if ( HxOverrides . cca ( data , p . pos ) == 58 ) {
tokens . add ( { p : HxOverrides . substr ( data , p . pos + 2 , p . len - 4 ) , s : false , l : null } ) ;
data = haxe _Template . splitter . matchedRight ( ) ;
continue ;
}
var parp = p . pos + p . len ;
var npar = 1 ;
var params = [ ] ;
var part = "" ;
while ( true ) {
var c = HxOverrides . cca ( data , parp ) ;
++ parp ;
if ( c == 40 ) {
++ npar ;
} else if ( c == 41 ) {
-- npar ;
if ( npar <= 0 ) {
break ;
}
} else if ( c == null ) {
throw haxe _Exception . thrown ( "Unclosed macro parenthesis" ) ;
}
if ( c == 44 && npar == 1 ) {
params . push ( part ) ;
part = "" ;
} else {
part += String . fromCodePoint ( c ) ;
}
}
params . push ( part ) ;
tokens . add ( { p : haxe _Template . splitter . matched ( 2 ) , s : false , l : params } ) ;
data = HxOverrides . substr ( data , parp , data . length - parp ) ;
}
if ( data . length > 0 ) {
tokens . add ( { p : data , s : true , l : null } ) ;
}
return tokens ;
}
, parseBlock : function ( tokens ) {
var l = new haxe _ds _List ( ) ;
while ( true ) {
var t = tokens . first ( ) ;
if ( t == null ) {
break ;
}
if ( ! t . s && ( t . p == "end" || t . p == "else" || HxOverrides . substr ( t . p , 0 , 7 ) == "elseif " ) ) {
break ;
}
l . add ( this . parse ( tokens ) ) ;
}
if ( l . length == 1 ) {
return l . first ( ) ;
}
return haxe _ _$Template _TemplateExpr . OpBlock ( l ) ;
}
, parse : function ( tokens ) {
var t = tokens . pop ( ) ;
var p = t . p ;
if ( t . s ) {
return haxe _ _$Template _TemplateExpr . OpStr ( p ) ;
}
if ( t . l != null ) {
var pe = new haxe _ds _List ( ) ;
var _g = 0 ;
var _g1 = t . l ;
while ( _g < _g1 . length ) {
var p1 = _g1 [ _g ] ;
++ _g ;
pe . add ( this . parseBlock ( this . parseTokens ( p1 ) ) ) ;
}
return haxe _ _$Template _TemplateExpr . OpMacro ( p , pe ) ;
}
var kwdEnd = function ( kwd ) {
var pos = - 1 ;
var length = kwd . length ;
if ( HxOverrides . substr ( p , 0 , length ) == kwd ) {
pos = length ;
var _g _offset = 0 ;
var _g _s = HxOverrides . substr ( p , length , null ) ;
while ( _g _offset < _g _s . length ) {
var c = _g _s . charCodeAt ( _g _offset ++ ) ;
if ( c == 32 ) {
++ pos ;
} else {
break ;
}
}
}
return pos ;
} ;
var pos = kwdEnd ( "if" ) ;
if ( pos > 0 ) {
p = HxOverrides . substr ( p , pos , p . length - pos ) ;
var e = this . parseExpr ( p ) ;
var eif = this . parseBlock ( tokens ) ;
var t = tokens . first ( ) ;
var eelse ;
if ( t == null ) {
throw haxe _Exception . thrown ( "Unclosed 'if'" ) ;
}
if ( t . p == "end" ) {
tokens . pop ( ) ;
eelse = null ;
} else if ( t . p == "else" ) {
tokens . pop ( ) ;
eelse = this . parseBlock ( tokens ) ;
t = tokens . pop ( ) ;
if ( t == null || t . p != "end" ) {
throw haxe _Exception . thrown ( "Unclosed 'else'" ) ;
}
} else {
t . p = HxOverrides . substr ( t . p , 4 , t . p . length - 4 ) ;
eelse = this . parse ( tokens ) ;
}
return haxe _ _$Template _TemplateExpr . OpIf ( e , eif , eelse ) ;
}
var pos = kwdEnd ( "foreach" ) ;
if ( pos >= 0 ) {
p = HxOverrides . substr ( p , pos , p . length - pos ) ;
var e = this . parseExpr ( p ) ;
var efor = this . parseBlock ( tokens ) ;
var t = tokens . pop ( ) ;
if ( t == null || t . p != "end" ) {
throw haxe _Exception . thrown ( "Unclosed 'foreach'" ) ;
}
return haxe _ _$Template _TemplateExpr . OpForeach ( e , efor ) ;
}
if ( haxe _Template . expr _splitter . match ( p ) ) {
return haxe _ _$Template _TemplateExpr . OpExpr ( this . parseExpr ( p ) ) ;
}
return haxe _ _$Template _TemplateExpr . OpVar ( p ) ;
}
, parseExpr : function ( data ) {
var l = new haxe _ds _List ( ) ;
var expr = data ;
while ( haxe _Template . expr _splitter . match ( data ) ) {
var p = haxe _Template . expr _splitter . matchedPos ( ) ;
var k = p . pos + p . len ;
if ( p . pos != 0 ) {
l . add ( { p : HxOverrides . substr ( data , 0 , p . pos ) , s : true } ) ;
}
var p1 = haxe _Template . expr _splitter . matched ( 0 ) ;
l . add ( { p : p1 , s : p1 . indexOf ( "\"" ) >= 0 } ) ;
data = haxe _Template . expr _splitter . matchedRight ( ) ;
}
if ( data . length != 0 ) {
var _g _offset = 0 ;
var _g _s = data ;
while ( _g _offset < _g _s . length ) {
2024-07-09 19:27:21 +02:00
var _g _key = _g _offset ;
var _g _value = _g _s . charCodeAt ( _g _offset ++ ) ;
var i = _g _key ;
var c = _g _value ;
2024-02-16 17:36:27 +01:00
if ( c != 32 ) {
l . add ( { p : HxOverrides . substr ( data , i , null ) , s : true } ) ;
break ;
}
}
}
var e ;
try {
e = this . makeExpr ( l ) ;
if ( ! l . isEmpty ( ) ) {
throw haxe _Exception . thrown ( l . first ( ) . p ) ;
}
} catch ( _g ) {
var _g1 = haxe _Exception . caught ( _g ) . unwrap ( ) ;
if ( typeof ( _g1 ) == "string" ) {
var s = _g1 ;
throw haxe _Exception . thrown ( "Unexpected '" + s + "' in " + expr ) ;
} else {
throw _g ;
}
}
return function ( ) {
try {
return e ( ) ;
} catch ( _g ) {
var exc = haxe _Exception . caught ( _g ) . unwrap ( ) ;
throw haxe _Exception . thrown ( "Error : " + Std . string ( exc ) + " in " + expr ) ;
}
} ;
}
, makeConst : function ( v ) {
haxe _Template . expr _trim . match ( v ) ;
v = haxe _Template . expr _trim . matched ( 1 ) ;
if ( HxOverrides . cca ( v , 0 ) == 34 ) {
var str = HxOverrides . substr ( v , 1 , v . length - 2 ) ;
return function ( ) {
return str ;
} ;
}
if ( haxe _Template . expr _int . match ( v ) ) {
var i = Std . parseInt ( v ) ;
return function ( ) {
return i ;
} ;
}
if ( haxe _Template . expr _float . match ( v ) ) {
var f = parseFloat ( v ) ;
return function ( ) {
return f ;
} ;
}
var me = this ;
return function ( ) {
return me . resolve ( v ) ;
} ;
}
, makePath : function ( e , l ) {
var p = l . first ( ) ;
if ( p == null || p . p != "." ) {
return e ;
}
l . pop ( ) ;
var field = l . pop ( ) ;
if ( field == null || ! field . s ) {
throw haxe _Exception . thrown ( field . p ) ;
}
var f = field . p ;
haxe _Template . expr _trim . match ( f ) ;
f = haxe _Template . expr _trim . matched ( 1 ) ;
return this . makePath ( function ( ) {
return Reflect . field ( e ( ) , f ) ;
} , l ) ;
}
, makeExpr : function ( l ) {
return this . makePath ( this . makeExpr2 ( l ) , l ) ;
}
, skipSpaces : function ( l ) {
var p = l . first ( ) ;
while ( p != null ) {
var _g _offset = 0 ;
var _g _s = p . p ;
while ( _g _offset < _g _s . length ) {
var c = _g _s . charCodeAt ( _g _offset ++ ) ;
if ( c != 32 ) {
return ;
}
}
l . pop ( ) ;
p = l . first ( ) ;
}
}
, makeExpr2 : function ( l ) {
this . skipSpaces ( l ) ;
var p = l . pop ( ) ;
this . skipSpaces ( l ) ;
if ( p == null ) {
throw haxe _Exception . thrown ( "<eof>" ) ;
}
if ( p . s ) {
return this . makeConst ( p . p ) ;
}
switch ( p . p ) {
case "!" :
var e = this . makeExpr ( l ) ;
return function ( ) {
var v = e ( ) ;
if ( v != null ) {
return v == false ;
} else {
return true ;
}
} ;
case "(" :
this . skipSpaces ( l ) ;
var e1 = this . makeExpr ( l ) ;
this . skipSpaces ( l ) ;
var p1 = l . pop ( ) ;
if ( p1 == null || p1 . s ) {
throw haxe _Exception . thrown ( p1 ) ;
}
if ( p1 . p == ")" ) {
return e1 ;
}
this . skipSpaces ( l ) ;
var e2 = this . makeExpr ( l ) ;
this . skipSpaces ( l ) ;
var p2 = l . pop ( ) ;
this . skipSpaces ( l ) ;
if ( p2 == null || p2 . p != ")" ) {
throw haxe _Exception . thrown ( p2 ) ;
}
switch ( p1 . p ) {
case "!=" :
return function ( ) {
return e1 ( ) != e2 ( ) ;
} ;
case "&&" :
return function ( ) {
return e1 ( ) && e2 ( ) ;
} ;
case "*" :
return function ( ) {
return e1 ( ) * e2 ( ) ;
} ;
case "+" :
return function ( ) {
return e1 ( ) + e2 ( ) ;
} ;
case "-" :
return function ( ) {
return e1 ( ) - e2 ( ) ;
} ;
case "/" :
return function ( ) {
return e1 ( ) / e2 ( ) ;
} ;
case "<" :
return function ( ) {
return e1 ( ) < e2 ( ) ;
} ;
case "<=" :
return function ( ) {
return e1 ( ) <= e2 ( ) ;
} ;
case "==" :
return function ( ) {
return e1 ( ) == e2 ( ) ;
} ;
case ">" :
return function ( ) {
return e1 ( ) > e2 ( ) ;
} ;
case ">=" :
return function ( ) {
return e1 ( ) >= e2 ( ) ;
} ;
case "||" :
return function ( ) {
return e1 ( ) || e2 ( ) ;
} ;
default :
throw haxe _Exception . thrown ( "Unknown operation " + p1 . p ) ;
}
break ;
case "-" :
var e3 = this . makeExpr ( l ) ;
return function ( ) {
return - e3 ( ) ;
} ;
}
throw haxe _Exception . thrown ( p . p ) ;
}
, run : function ( e ) {
switch ( e . _hx _index ) {
case 0 :
var v = e . v ;
var _this = this . buf ;
var x = Std . string ( this . resolve ( v ) ) ;
_this . b += Std . string ( x ) ;
break ;
case 1 :
var e1 = e . expr ;
var _this = this . buf ;
var x = Std . string ( e1 ( ) ) ;
_this . b += Std . string ( x ) ;
break ;
case 2 :
var e1 = e . expr ;
var eif = e . eif ;
var eelse = e . eelse ;
var v = e1 ( ) ;
if ( v == null || v == false ) {
if ( eelse != null ) {
this . run ( eelse ) ;
}
} else {
this . run ( eif ) ;
}
break ;
case 3 :
var str = e . str ;
this . buf . b += str == null ? "null" : "" + str ;
break ;
case 4 :
var l = e . l ;
var _g _head = l . h ;
while ( _g _head != null ) {
var val = _g _head . item ;
_g _head = _g _head . next ;
var e1 = val ;
this . run ( e1 ) ;
}
break ;
case 5 :
var e1 = e . expr ;
var loop = e . loop ;
var v = e1 ( ) ;
try {
var x = $getIterator ( v ) ;
if ( x . hasNext == null ) {
throw haxe _Exception . thrown ( null ) ;
}
v = x ;
} catch ( _g ) {
try {
if ( v . hasNext == null ) {
throw haxe _Exception . thrown ( null ) ;
}
} catch ( _g1 ) {
throw haxe _Exception . thrown ( "Cannot iter on " + Std . string ( v ) ) ;
}
}
this . stack . push ( this . context ) ;
var v1 = v ;
var ctx = v1 ;
while ( ctx . hasNext ( ) ) {
var ctx1 = ctx . next ( ) ;
this . context = ctx1 ;
this . run ( loop ) ;
}
this . context = this . stack . pop ( ) ;
break ;
case 6 :
var m = e . name ;
var params = e . params ;
var v = Reflect . field ( this . macros , m ) ;
var pl = [ ] ;
var old = this . buf ;
pl . push ( $bind ( this , this . resolve ) ) ;
var _g _head = params . h ;
while ( _g _head != null ) {
var val = _g _head . item ;
_g _head = _g _head . next ;
var p = val ;
if ( p . _hx _index == 0 ) {
var v1 = p . v ;
pl . push ( this . resolve ( v1 ) ) ;
} else {
this . buf = new StringBuf ( ) ;
this . run ( p ) ;
pl . push ( this . buf . b ) ;
}
}
this . buf = old ;
try {
var _this = this . buf ;
var x = Std . string ( v . apply ( this . macros , pl ) ) ;
_this . b += Std . string ( x ) ;
} catch ( _g ) {
var e = haxe _Exception . caught ( _g ) . unwrap ( ) ;
var plstr ;
try {
plstr = pl . join ( "," ) ;
} catch ( _g1 ) {
plstr = "???" ;
}
var msg = "Macro call " + m + "(" + plstr + ") failed (" + Std . string ( e ) + ")" ;
throw haxe _Exception . thrown ( msg ) ;
}
break ;
}
}
} ;
var haxe _ValueException = function ( value , previous , native ) {
haxe _Exception . call ( this , String ( value ) , previous , native ) ;
this . value = value ;
} ;
haxe _ValueException . _ _name _ _ = true ;
haxe _ValueException . _ _super _ _ = haxe _Exception ;
haxe _ValueException . prototype = $extend ( haxe _Exception . prototype , {
unwrap : function ( ) {
return this . value ;
}
} ) ;
var haxe _ds _List = function ( ) {
this . length = 0 ;
} ;
haxe _ds _List . _ _name _ _ = true ;
haxe _ds _List . prototype = {
add : function ( item ) {
var x = new haxe _ds _ _$List _ListNode ( item , null ) ;
if ( this . h == null ) {
this . h = x ;
} else {
this . q . next = x ;
}
this . q = x ;
this . length ++ ;
}
, push : function ( item ) {
var x = new haxe _ds _ _$List _ListNode ( item , this . h ) ;
this . h = x ;
if ( this . q == null ) {
this . q = x ;
}
this . length ++ ;
}
, first : function ( ) {
if ( this . h == null ) {
return null ;
} else {
return this . h . item ;
}
}
, pop : function ( ) {
if ( this . h == null ) {
return null ;
}
var x = this . h . item ;
this . h = this . h . next ;
if ( this . h == null ) {
this . q = null ;
}
this . length -- ;
return x ;
}
, isEmpty : function ( ) {
return this . h == null ;
}
, toString : function ( ) {
var s _b = "" ;
var first = true ;
var l = this . h ;
s _b += "{" ;
while ( l != null ) {
if ( first ) {
first = false ;
} else {
s _b += ", " ;
}
s _b += Std . string ( Std . string ( l . item ) ) ;
l = l . next ;
}
s _b += "}" ;
return s _b ;
}
} ;
var haxe _ds _ _$List _ListNode = function ( item , next ) {
this . item = item ;
this . next = next ;
} ;
haxe _ds _ _$List _ListNode . _ _name _ _ = true ;
2023-05-04 16:24:54 +02:00
var js _Boot = function ( ) { } ;
js _Boot . _ _name _ _ = true ;
js _Boot . _ _string _rec = function ( o , s ) {
if ( o == null ) {
return "null" ;
}
if ( s . length >= 5 ) {
return "<...>" ;
}
var t = typeof ( o ) ;
if ( t == "function" && ( o . _ _name _ _ || o . _ _ename _ _ ) ) {
t = "object" ;
}
switch ( t ) {
case "function" :
return "<function>" ;
case "object" :
2024-02-16 17:36:27 +01:00
if ( o . _ _enum _ _ ) {
var e = $hxEnums [ o . _ _enum _ _ ] ;
var con = e . _ _constructs _ _ [ o . _hx _index ] ;
var n = con . _hx _name ;
if ( con . _ _params _ _ ) {
s = s + "\t" ;
return n + "(" + ( ( function ( $this ) {
var $r ;
var _g = [ ] ;
{
var _g1 = 0 ;
var _g2 = con . _ _params _ _ ;
while ( true ) {
if ( ! ( _g1 < _g2 . length ) ) {
break ;
}
var p = _g2 [ _g1 ] ;
_g1 = _g1 + 1 ;
_g . push ( js _Boot . _ _string _rec ( o [ p ] , s ) ) ;
}
}
$r = _g ;
return $r ;
} ( this ) ) ) . join ( "," ) + ")" ;
} else {
return n ;
}
}
2023-05-04 16:24:54 +02:00
if ( ( ( o ) instanceof Array ) ) {
var str = "[" ;
s += "\t" ;
var _g = 0 ;
var _g1 = o . length ;
while ( _g < _g1 ) {
var i = _g ++ ;
str += ( i > 0 ? "," : "" ) + js _Boot . _ _string _rec ( o [ i ] , s ) ;
}
str += "]" ;
return str ;
}
var tostr ;
try {
tostr = o . toString ;
} catch ( _g ) {
return "???" ;
}
if ( tostr != null && tostr != Object . toString && typeof ( tostr ) == "function" ) {
var s2 = o . toString ( ) ;
if ( s2 != "[object Object]" ) {
return s2 ;
}
}
var str = "{\n" ;
s += "\t" ;
var hasp = o . hasOwnProperty != null ;
var k = null ;
for ( k in o ) {
if ( hasp && ! o . hasOwnProperty ( k ) ) {
continue ;
}
if ( k == "prototype" || k == "__class__" || k == "__super__" || k == "__interfaces__" || k == "__properties__" ) {
continue ;
}
if ( str . length != 2 ) {
str += ", \n" ;
}
str += s + k + " : " + js _Boot . _ _string _rec ( o [ k ] , s ) ;
}
s = s . substring ( 1 ) ;
str += "\n" + s + "}" ;
return str ;
case "string" :
return o ;
default :
return String ( o ) ;
}
} ;
2023-11-28 17:39:33 +01:00
var xrfragment _Filter = $hx _exports [ "xrfragment" ] [ "Filter" ] = function ( str ) {
2023-05-04 16:24:54 +02:00
this . q = { } ;
this . str = "" ;
if ( str != null ) {
this . parse ( str ) ;
}
} ;
2023-11-28 17:39:33 +01:00
xrfragment _Filter . _ _name _ _ = true ;
xrfragment _Filter . prototype = {
2023-05-04 16:24:54 +02:00
toObject : function ( ) {
2023-11-28 17:39:33 +01:00
return Reflect . copy ( this . q ) ;
2023-05-04 16:24:54 +02:00
}
, get : function ( ) {
2023-11-28 17:39:33 +01:00
return Reflect . copy ( this . q ) ;
2023-05-04 16:24:54 +02:00
}
2023-06-27 09:43:10 +02:00
, parse : function ( str ) {
2023-05-04 16:24:54 +02:00
var token = str . split ( " " ) ;
var q = { } ;
var process = function ( str , prefix ) {
if ( prefix == null ) {
prefix = "" ;
}
str = StringTools . trim ( str ) ;
2023-11-28 17:39:33 +01:00
var k = str . split ( "=" ) [ 0 ] ;
var v = str . split ( "=" ) [ 1 ] ;
2023-05-04 16:24:54 +02:00
var filter = { } ;
if ( q [ prefix + k ] ) {
filter = q [ prefix + k ] ;
}
2023-11-28 17:39:33 +01:00
if ( xrfragment _XRF . isProp . match ( str ) ) {
2023-05-04 16:24:54 +02:00
var oper = "" ;
if ( str . indexOf ( ">" ) != - 1 ) {
oper = ">" ;
}
if ( str . indexOf ( "<" ) != - 1 ) {
oper = "<" ;
}
2023-11-28 17:39:33 +01:00
if ( xrfragment _XRF . isExclude . match ( k ) ) {
2023-05-04 16:24:54 +02:00
k = HxOverrides . substr ( k , 1 , null ) ;
}
2023-11-28 17:39:33 +01:00
v = HxOverrides . substr ( v , oper . length , null ) ;
2023-05-04 16:24:54 +02:00
if ( oper . length == 0 ) {
oper = "=" ;
}
2023-09-15 19:43:11 +02:00
var rule = { } ;
2023-11-28 17:39:33 +01:00
if ( xrfragment _XRF . isNumber . match ( v ) ) {
2023-09-15 19:43:11 +02:00
rule [ oper ] = parseFloat ( v ) ;
2023-05-04 16:24:54 +02:00
} else {
2023-09-15 19:43:11 +02:00
rule [ oper ] = v ;
2023-05-04 16:24:54 +02:00
}
2023-11-28 17:39:33 +01:00
q [ "expr" ] = rule ;
2023-05-04 16:24:54 +02:00
}
2023-11-28 17:39:33 +01:00
var value = xrfragment _XRF . isDeep . match ( str ) ? k . split ( "*" ) . length - 1 : 0 ;
q [ "deep" ] = value ;
var value = xrfragment _XRF . isExclude . match ( str ) ? false : true ;
q [ "show" ] = value ;
var value = k . replace ( xrfragment _XRF . operators . r , "" ) ;
q [ "key" ] = value ;
q [ "value" ] = v ;
2023-05-04 16:24:54 +02:00
} ;
var _g = 0 ;
var _g1 = token . length ;
while ( _g < _g1 ) {
var i = _g ++ ;
2023-09-15 19:43:11 +02:00
process ( token [ i ] ) ;
2023-05-04 16:24:54 +02:00
}
return this . q = q ;
}
, test : function ( obj ) {
var qualify = false ;
var _g = 0 ;
var _g1 = Reflect . fields ( obj ) ;
while ( _g < _g1 . length ) {
var k = _g1 [ _g ] ;
++ _g ;
var v = Std . string ( Reflect . field ( obj , k ) ) ;
if ( this . testProperty ( k , v ) ) {
qualify = true ;
}
}
var _g = 0 ;
var _g1 = Reflect . fields ( obj ) ;
while ( _g < _g1 . length ) {
var k = _g1 [ _g ] ;
++ _g ;
var v = Std . string ( Reflect . field ( obj , k ) ) ;
if ( this . testProperty ( k , v , true ) ) {
qualify = false ;
}
}
return qualify ;
}
, testProperty : function ( property , value , exclude ) {
var conds = 0 ;
var fails = 0 ;
var qualify = 0 ;
var testprop = function ( expr ) {
conds += 1 ;
fails += expr ? 0 : 1 ;
return expr ;
} ;
if ( this . q [ value ] != null ) {
var v = this . q [ value ] ;
if ( v [ property ] != null ) {
return v [ property ] ;
}
}
2023-11-28 17:39:33 +01:00
if ( Reflect . field ( this . q , "expr" ) ) {
var f = Reflect . field ( this . q , "expr" ) ;
if ( ! Reflect . field ( this . q , "show" ) ) {
if ( Reflect . field ( f , "!=" ) != null && testprop ( ( value == null ? "null" : "" + value ) == Std . string ( Reflect . field ( f , "!=" ) ) ) && exclude ) {
++ qualify ;
}
} else {
if ( Reflect . field ( f , "*" ) != null && testprop ( parseFloat ( value ) != null ) ) {
++ qualify ;
}
if ( Reflect . field ( f , ">" ) != null && testprop ( parseFloat ( value ) >= parseFloat ( Reflect . field ( f , ">" ) ) ) ) {
++ qualify ;
}
if ( Reflect . field ( f , "<" ) != null && testprop ( parseFloat ( value ) <= parseFloat ( Reflect . field ( f , "<" ) ) ) ) {
++ qualify ;
}
if ( Reflect . field ( f , "=" ) != null && ( testprop ( value == Reflect . field ( f , "=" ) ) || testprop ( parseFloat ( value ) == parseFloat ( Reflect . field ( f , "=" ) ) ) ) ) {
++ qualify ;
2023-05-04 16:24:54 +02:00
}
}
}
return qualify > 0 ;
}
} ;
2023-11-28 17:39:33 +01:00
var xrfragment _Parser = $hx _exports [ "xrfragment" ] [ "Parser" ] = function ( ) { } ;
xrfragment _Parser . _ _name _ _ = true ;
xrfragment _Parser . parse = function ( key , value , store , index ) {
var Frag _h = Object . create ( null ) ;
2024-02-16 17:36:27 +01:00
Frag _h [ "#" ] = xrfragment _XRF . IMMUTABLE | xrfragment _XRF . T _PREDEFINED _VIEW | xrfragment _XRF . PV _EXECUTE ;
Frag _h [ "src" ] = xrfragment _XRF . T _URL ;
Frag _h [ "href" ] = xrfragment _XRF . T _URL | xrfragment _XRF . T _PREDEFINED _VIEW ;
Frag _h [ "tag" ] = xrfragment _XRF . IMMUTABLE | xrfragment _XRF . T _STRING ;
Frag _h [ "pos" ] = xrfragment _XRF . PV _OVERRIDE | xrfragment _XRF . T _VECTOR3 | xrfragment _XRF . T _STRING | xrfragment _XRF . METADATA | xrfragment _XRF . NAVIGATOR ;
2023-11-28 17:39:33 +01:00
Frag _h [ "rot" ] = xrfragment _XRF . QUERY _OPERATOR | xrfragment _XRF . PV _OVERRIDE | xrfragment _XRF . T _VECTOR3 | xrfragment _XRF . METADATA | xrfragment _XRF . NAVIGATOR ;
2024-02-16 17:36:27 +01:00
Frag _h [ "t" ] = xrfragment _XRF . PV _OVERRIDE | xrfragment _XRF . T _FLOAT | xrfragment _XRF . T _VECTOR2 | xrfragment _XRF . NAVIGATOR | xrfragment _XRF . METADATA ;
Frag _h [ "s" ] = xrfragment _XRF . PV _OVERRIDE | xrfragment _XRF . T _MEDIAFRAG ;
2024-03-19 11:06:08 +01:00
Frag _h [ "loop" ] = xrfragment _XRF . PV _OVERRIDE | xrfragment _XRF . T _PREDEFINED _VIEW ;
2024-02-16 17:36:27 +01:00
Frag _h [ "uv" ] = xrfragment _XRF . T _VECTOR2 | xrfragment _XRF . T _MEDIAFRAG ;
Frag _h [ "namespace" ] = xrfragment _XRF . IMMUTABLE | xrfragment _XRF . T _STRING ;
Frag _h [ "SPDX" ] = xrfragment _XRF . IMMUTABLE | xrfragment _XRF . T _STRING ;
Frag _h [ "unit" ] = xrfragment _XRF . IMMUTABLE | xrfragment _XRF . T _STRING ;
Frag _h [ "description" ] = xrfragment _XRF . IMMUTABLE | xrfragment _XRF . T _STRING ;
2023-11-28 17:39:33 +01:00
var keyStripped = key . replace ( xrfragment _XRF . operators . r , "" ) ;
var isPVDynamic = key . length > 0 && ! Object . prototype . hasOwnProperty . call ( Frag _h , key ) ;
if ( isPVDynamic ) {
var v = new xrfragment _XRF ( key , xrfragment _XRF . PV _EXECUTE | xrfragment _XRF . NAVIGATOR , index ) ;
v . validate ( value ) ;
2024-02-16 17:36:27 +01:00
v . flags = xrfragment _XRF . set ( xrfragment _XRF . T _DYNAMICKEY , v . flags ) ;
if ( ! Object . prototype . hasOwnProperty . call ( Frag _h , key ) ) {
v . flags = xrfragment _XRF . set ( xrfragment _XRF . CUSTOMFRAG , v . flags ) ;
}
if ( value . length == 0 ) {
v . flags = xrfragment _XRF . set ( xrfragment _XRF . T _DYNAMICKEYVALUE , v . flags ) ;
}
2023-11-28 17:39:33 +01:00
store [ keyStripped ] = v ;
return true ;
}
var v = new xrfragment _XRF ( key , Frag _h [ key ] , index ) ;
if ( Object . prototype . hasOwnProperty . call ( Frag _h , key ) ) {
if ( ! v . validate ( value ) ) {
2024-02-16 17:36:27 +01:00
console . log ( "src/xrfragment/Parser.hx:67:" , "⚠ fragment '" + key + "' has incompatible value (" + value + ")" ) ;
2023-11-28 17:39:33 +01:00
return false ;
}
store [ keyStripped ] = v ;
if ( xrfragment _Parser . debug ) {
2024-02-16 17:36:27 +01:00
console . log ( "src/xrfragment/Parser.hx:71:" , "✔ " + key + ": " + v . string ) ;
2023-11-28 17:39:33 +01:00
}
} else {
if ( typeof ( value ) == "string" ) {
v . guessType ( v , value ) ;
}
2024-02-16 17:36:27 +01:00
v . flags = xrfragment _XRF . set ( xrfragment _XRF . CUSTOMFRAG , v . flags ) ;
2023-11-28 17:39:33 +01:00
store [ keyStripped ] = v ;
}
return true ;
} ;
2024-02-29 14:36:00 +01:00
xrfragment _Parser . getMetaData = function ( ) {
var meta = { title : [ "title" , "og:title" , "dc.title" ] , description : [ "aria-description" , "og:description" , "dc.description" ] , author : [ "author" , "dc.creator" ] , publisher : [ "publisher" , "dc.publisher" ] , website : [ "og:site_name" , "og:url" , "dc.publisher" ] , license : [ "SPDX" , "dc.rights" ] } ;
return meta ;
} ;
2024-04-16 15:19:08 +02:00
var xrfragment _URI = $hx _exports [ "xrfragment" ] [ "URI" ] = function ( ) {
this . XRF = { } ;
this . hash = { } ;
this . fragment = "" ;
} ;
2023-05-04 16:24:54 +02:00
xrfragment _URI . _ _name _ _ = true ;
2024-04-16 15:19:08 +02:00
xrfragment _URI . parseFragment = function ( url , filter ) {
2023-05-04 16:24:54 +02:00
var store = { } ;
2023-06-10 14:43:07 +02:00
if ( url == null || url . indexOf ( "#" ) == - 1 ) {
2023-05-04 16:24:54 +02:00
return store ;
}
var fragment = url . split ( "#" ) ;
var splitArray = fragment [ 1 ] . split ( "&" ) ;
var _g = 0 ;
var _g1 = splitArray . length ;
while ( _g < _g1 ) {
var i = _g ++ ;
var splitByEqual = splitArray [ i ] . split ( "=" ) ;
var regexPlus = new EReg ( "\\+" , "g" ) ;
var key = splitByEqual [ 0 ] ;
var value = "" ;
if ( splitByEqual . length > 1 ) {
2024-02-16 17:36:27 +01:00
if ( xrfragment _XRF . isVector . match ( splitByEqual [ 1 ] ) ) {
value = splitByEqual [ 1 ] ;
} else {
var s = regexPlus . split ( splitByEqual [ 1 ] ) . join ( " " ) ;
value = decodeURIComponent ( s . split ( "+" ) . join ( " " ) ) ;
}
2023-05-04 16:24:54 +02:00
}
2023-11-28 17:39:33 +01:00
var ok = xrfragment _Parser . parse ( key , value , store , i ) ;
2023-05-04 16:24:54 +02:00
}
if ( filter != null && filter != 0 ) {
var _g = 0 ;
var _g1 = Reflect . fields ( store ) ;
while ( _g < _g1 . length ) {
var key = _g1 [ _g ] ;
++ _g ;
var xrf = store [ key ] ;
if ( ! xrf . is ( filter ) ) {
Reflect . deleteField ( store , key ) ;
}
}
}
return store ;
} ;
2024-02-16 17:36:27 +01:00
xrfragment _URI . template = function ( uri , vars ) {
var parts = uri . split ( "#" ) ;
if ( parts . length == 1 ) {
return uri ;
}
var frag = parts [ 1 ] ;
frag = StringTools . replace ( frag , "{" , "::" ) ;
frag = StringTools . replace ( frag , "}" , "::" ) ;
frag = new haxe _Template ( frag ) . execute ( vars ) ;
frag = StringTools . replace ( frag , "null" , "" ) ;
parts [ 1 ] = frag ;
return parts . join ( "#" ) ;
} ;
2024-04-16 15:19:08 +02:00
xrfragment _URI . parse = function ( stringUrl , flags ) {
var r = new EReg ( "^(?:(?![^:@]+:[^:@/]*@)([^:/?#.]+):)?(?://)?((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:/?#]*)(?::(\\d*))?)(((/(?:[^?#](?![^?#/]*\\.[^?#/.]+(?:[?#]|$)))*/?)?([^?#/]*))(?:\\?([^#]*))?(?:#(.*))?)" , "" ) ;
2024-07-17 13:07:35 +02:00
if ( stringUrl . indexOf ( "://" ) == - 1 && stringUrl . charAt ( 0 ) != "/" && stringUrl . charAt ( 0 ) != "#" ) {
2024-04-16 15:19:08 +02:00
stringUrl = "/" + stringUrl ;
}
r . match ( stringUrl ) ;
var url = new xrfragment _URI ( ) ;
var _g = 0 ;
var _g1 = xrfragment _URI . _parts . length ;
while ( _g < _g1 ) {
var i = _g ++ ;
url [ xrfragment _URI . _parts [ i ] ] = r . matched ( i ) ;
}
if ( xrfragment _URI . isRelative ( url ) == true ) {
if ( url . directory == null && url . host != null ) {
url . file = url . host ;
}
2024-04-16 18:44:55 +02:00
url . host = "" ;
2024-04-16 15:19:08 +02:00
}
url . hash = { } ;
if ( url . fragment != null && url . fragment . length > 0 ) {
url . XRF = xrfragment _URI . parseFragment ( "#" + url . fragment , flags ) ;
var key ;
var _g = 0 ;
var _g1 = Reflect . fields ( url . XRF ) ;
while ( _g < _g1 . length ) {
var key = _g1 [ _g ] ;
++ _g ;
var v = url . XRF [ key ] ;
url . hash [ key ] = v [ "string" ] ;
}
}
xrfragment _URI . computeVars ( url ) ;
return url ;
} ;
xrfragment _URI . computeVars = function ( url ) {
var r _r = new RegExp ( "//" , "g" . split ( "u" ) . join ( "" ) ) ;
if ( url . directory != null && url . directory . indexOf ( "//" ) != - 1 ) {
url . directory = url . directory . replace ( r _r , "/" ) ;
}
if ( url . path != null && url . path . indexOf ( "//" ) != - 1 ) {
url . path = url . path . replace ( r _r , "/" ) ;
}
if ( url . file != null && url . file . indexOf ( "//" ) != - 1 ) {
url . file = url . file . replace ( r _r , "/" ) ;
}
url . URN = url . scheme + "://" + url . host ;
if ( url . port != null ) {
url . URN += ":" + url . port ;
}
url . URN += url . directory ;
if ( url . file != null ) {
var parts = url . file . split ( "." ) ;
if ( parts . length > 1 ) {
url . fileExt = parts . pop ( ) ;
}
}
} ;
xrfragment _URI . toString = function ( url ) {
var result = "" ;
if ( url . scheme != null ) {
result += url . scheme + "://" ;
}
if ( url . user != null ) {
result += url . user + ":" ;
}
if ( url . password != null ) {
result += url . password + "@" ;
}
if ( url . host != null ) {
result += url . host ;
}
if ( url . port != null ) {
result += ":" + url . port ;
}
if ( url . directory != null ) {
result += url . directory ;
}
if ( url . file != null ) {
result += url . file ;
}
if ( url . query != null ) {
result += "?" + url . query ;
}
if ( url . fragment != null ) {
result += "#" + url . fragment ;
}
return result ;
} ;
xrfragment _URI . appendURI = function ( url , appendedURI ) {
if ( xrfragment _URI . isRelative ( url ) == true ) {
return xrfragment _URI . appendToRelativeURI ( url , appendedURI ) ;
} else {
return xrfragment _URI . appendToAbsoluteURI ( url , appendedURI ) ;
}
} ;
xrfragment _URI . isRelative = function ( url ) {
return url . scheme == null ;
} ;
xrfragment _URI . appendToRelativeURI = function ( url , appendedURI ) {
if ( url . directory == null || url . host == null ) {
return xrfragment _URI . cloneURI ( appendedURI ) ;
}
var resultURI = new xrfragment _URI ( ) ;
resultURI . host = url . host ;
resultURI . directory = url . directory ;
if ( appendedURI . host != null ) {
resultURI . directory += appendedURI . host ;
}
if ( appendedURI . directory != null ) {
var directory = appendedURI . directory ;
if ( appendedURI . host == null ) {
resultURI . directory += HxOverrides . substr ( directory , 1 , null ) ;
} else {
resultURI . directory += directory ;
}
}
if ( appendedURI . file != null ) {
resultURI . file = appendedURI . file ;
}
resultURI . path = resultURI . directory + resultURI . file ;
if ( appendedURI . query != null ) {
resultURI . query = appendedURI . query ;
}
if ( appendedURI . fragment != null ) {
resultURI . fragment = appendedURI . fragment ;
}
return resultURI ;
} ;
xrfragment _URI . appendToAbsoluteURI = function ( url , appendedURI ) {
var resultURI = new xrfragment _URI ( ) ;
if ( url . scheme != null ) {
resultURI . scheme = url . scheme ;
}
if ( url . host != null ) {
resultURI . host = url . host ;
}
var directory = "" ;
if ( url . directory != null ) {
directory = url . directory ;
}
if ( appendedURI . host != null ) {
appendedURI . directory += appendedURI . host ;
}
if ( appendedURI . directory != null ) {
directory += appendedURI . directory ;
}
resultURI . directory = directory ;
if ( appendedURI . file != null ) {
resultURI . file = appendedURI . file ;
}
resultURI . path = resultURI . directory + resultURI . file ;
if ( appendedURI . query != null ) {
resultURI . query = appendedURI . query ;
}
if ( appendedURI . fragment != null ) {
resultURI . fragment = appendedURI . fragment ;
}
return resultURI ;
} ;
xrfragment _URI . toAbsolute = function ( url , newUrl ) {
var newURI = xrfragment _URI . parse ( newUrl , 0 ) ;
var resultURI = new xrfragment _URI ( ) ;
resultURI . port = url . port ;
resultURI . source = newUrl ;
if ( newURI . scheme != null ) {
resultURI . scheme = newURI . scheme ;
} else {
resultURI . scheme = url . scheme ;
}
if ( newURI . host != null && newURI . host . length > 0 ) {
resultURI . host = newURI . host ;
resultURI . port = null ;
resultURI . fragment = null ;
resultURI . hash = { } ;
resultURI . XRF = { } ;
if ( newURI . port != null ) {
resultURI . port = newURI . port ;
}
} else {
resultURI . host = url . host ;
}
var directory = "" ;
if ( url . directory != null ) {
directory = url . directory ;
}
2024-07-17 13:07:35 +02:00
if ( newURI . directory != null && newURI . source . charAt ( 0 ) != "#" && newURI . directory . length > 0 ) {
2024-04-16 15:19:08 +02:00
if ( newUrl . charAt ( 0 ) != "/" && newUrl . indexOf ( "://" ) == - 1 ) {
2024-07-09 19:27:21 +02:00
var stripRelative _r = new RegExp ( "\\./.*" , "" . split ( "u" ) . join ( "" ) ) ;
directory = directory . replace ( stripRelative _r , "" ) ;
2024-04-16 15:19:08 +02:00
directory += newURI . directory ;
} else {
directory = newURI . directory ;
}
}
resultURI . directory = directory ;
2024-07-17 13:07:35 +02:00
if ( newURI . file != null && newURI . file . length > 0 ) {
2024-04-16 15:19:08 +02:00
resultURI . file = newURI . file ;
2024-04-16 18:44:55 +02:00
} else {
resultURI . file = url . file ;
2024-04-16 15:19:08 +02:00
}
resultURI . path = resultURI . directory + resultURI . file ;
if ( newURI . query != null ) {
resultURI . query = newURI . query ;
}
if ( newURI . fragment != null ) {
resultURI . fragment = newURI . fragment ;
}
resultURI . hash = newURI . hash ;
resultURI . XRF = newURI . XRF ;
xrfragment _URI . computeVars ( resultURI ) ;
return resultURI ;
} ;
xrfragment _URI . cloneURI = function ( url ) {
var clonedURI = new xrfragment _URI ( ) ;
clonedURI . url = url . url ;
clonedURI . source = url . source ;
clonedURI . scheme = url . scheme ;
clonedURI . authority = url . authority ;
clonedURI . userInfo = url . userInfo ;
clonedURI . password = url . password ;
clonedURI . host = url . host ;
clonedURI . port = url . port ;
clonedURI . relative = url . relative ;
clonedURI . path = url . path ;
clonedURI . directory = url . directory ;
clonedURI . file = url . file ;
clonedURI . query = url . query ;
clonedURI . fragment = url . fragment ;
return clonedURI ;
} ;
2023-11-28 17:39:33 +01:00
var xrfragment _XRF = $hx _exports [ "xrfragment" ] [ "XRF" ] = function ( _fragment , _flags , _index ) {
2024-02-16 17:36:27 +01:00
this . floats = [ ] ;
this . shift = [ ] ;
2023-05-04 16:24:54 +02:00
this . fragment = _fragment ;
this . flags = _flags ;
2023-11-28 17:39:33 +01:00
this . index = _index ;
2023-05-04 16:24:54 +02:00
} ;
xrfragment _XRF . _ _name _ _ = true ;
xrfragment _XRF . set = function ( flag , flags ) {
return flags | flag ;
} ;
xrfragment _XRF . unset = function ( flag , flags ) {
return flags & ~ flag ;
} ;
xrfragment _XRF . prototype = {
is : function ( flag ) {
2023-11-03 21:22:05 +01:00
var v = this . flags ;
if ( ! ( typeof ( v ) == "number" && ( ( v | 0 ) === v ) ) ) {
this . flags = 0 ;
}
2023-05-04 16:24:54 +02:00
return ( this . flags & flag ) != 0 ;
}
, validate : function ( value ) {
this . guessType ( this , value ) ;
var ok = true ;
2024-03-19 11:06:08 +01:00
if ( value . length == 0 && ! this . is ( xrfragment _XRF . T _PREDEFINED _VIEW ) ) {
2024-03-19 10:53:22 +01:00
ok = false ;
}
2023-11-03 21:22:05 +01:00
if ( ! this . is ( xrfragment _XRF . T _FLOAT ) && this . is ( xrfragment _XRF . T _VECTOR2 ) && ! ( typeof ( this . x ) == "number" && typeof ( this . y ) == "number" ) ) {
ok = false ;
}
2023-11-28 17:39:33 +01:00
if ( ! ( this . is ( xrfragment _XRF . T _VECTOR2 ) || this . is ( xrfragment _XRF . T _STRING ) ) && this . is ( xrfragment _XRF . T _VECTOR3 ) && ! ( typeof ( this . x ) == "number" && typeof ( this . y ) == "number" && typeof ( this . z ) == "number" ) ) {
2023-11-03 21:22:05 +01:00
ok = false ;
2023-05-04 16:24:54 +02:00
}
return ok ;
}
, guessType : function ( v , str ) {
v . string = str ;
2024-02-16 17:36:27 +01:00
if ( xrfragment _XRF . isReset . match ( v . fragment ) ) {
v . reset = true ;
}
if ( v . fragment == "loop" ) {
v . loop = true ;
}
2023-11-28 17:39:33 +01:00
if ( typeof ( str ) != "string" ) {
return ;
}
if ( str . length > 0 ) {
2024-02-16 17:36:27 +01:00
if ( xrfragment _XRF . isXRFScheme . match ( str ) ) {
v . xrfScheme = true ;
str = str . replace ( xrfragment _XRF . isXRFScheme . r , "" ) ;
v . string = str ;
}
2023-11-28 17:39:33 +01:00
if ( str . split ( "," ) . length > 1 ) {
2024-02-16 17:36:27 +01:00
var xyzn = str . split ( "," ) ;
if ( xyzn . length > 0 ) {
v . x = parseFloat ( xyzn [ 0 ] ) ;
2023-11-28 17:39:33 +01:00
}
2024-02-16 17:36:27 +01:00
if ( xyzn . length > 1 ) {
v . y = parseFloat ( xyzn [ 1 ] ) ;
2023-11-28 17:39:33 +01:00
}
2024-02-16 17:36:27 +01:00
if ( xyzn . length > 2 ) {
v . z = parseFloat ( xyzn [ 2 ] ) ;
2023-11-28 17:39:33 +01:00
}
2024-02-16 17:36:27 +01:00
var _g = 0 ;
var _g1 = xyzn . length ;
while ( _g < _g1 ) {
var i = _g ++ ;
v . shift . push ( xrfragment _XRF . isShift . match ( xyzn [ i ] ) ) ;
v . floats . push ( parseFloat ( xyzn [ i ] . replace ( xrfragment _XRF . isShift . r , "" ) ) ) ;
2023-11-28 17:39:33 +01:00
}
2023-11-03 21:22:05 +01:00
}
2023-11-28 17:39:33 +01:00
if ( xrfragment _XRF . isColor . match ( str ) ) {
v . color = str ;
2023-05-04 16:24:54 +02:00
}
2023-11-28 17:39:33 +01:00
if ( xrfragment _XRF . isFloat . match ( str ) ) {
v . x = parseFloat ( str ) ;
v . float = v . x ;
2023-05-04 16:24:54 +02:00
}
2023-11-28 17:39:33 +01:00
if ( xrfragment _XRF . isInt . match ( str ) ) {
v . int = Std . parseInt ( str ) ;
v . x = v . int ;
2024-02-16 17:36:27 +01:00
v . floats . push ( v . x ) ;
2023-05-04 16:24:54 +02:00
}
2023-11-28 17:39:33 +01:00
v . filter = new xrfragment _Filter ( v . fragment + "=" + v . string ) ;
} else {
v . filter = new xrfragment _Filter ( v . fragment ) ;
2023-05-04 16:24:54 +02:00
}
}
} ;
2024-02-16 17:36:27 +01:00
function $getIterator ( o ) { if ( o instanceof Array ) return new haxe _iterators _ArrayIterator ( o ) ; else return o . iterator ( ) ; }
function $bind ( o , m ) { if ( m == null ) return null ; if ( m . _ _id _ _ == null ) m . _ _id _ _ = $global . $haxeUID ++ ; var f ; if ( o . hx _ _closures _ _ == null ) o . hx _ _closures _ _ = { } ; else f = o . hx _ _closures _ _ [ m . _ _id _ _ ] ; if ( f == null ) { f = m . bind ( o ) ; o . hx _ _closures _ _ [ m . _ _id _ _ ] = f ; } return f ; }
$global . $haxeUID |= 0 ;
2023-05-04 16:24:54 +02:00
if ( typeof ( performance ) != "undefined" ? typeof ( performance . now ) == "function" : false ) {
HxOverrides . now = performance . now . bind ( performance ) ;
}
2024-02-16 17:36:27 +01:00
if ( String . fromCodePoint == null ) String . fromCodePoint = function ( c ) { return c < 0x10000 ? String . fromCharCode ( c ) : String . fromCharCode ( ( c >> 10 ) + 0xD7C0 ) + String . fromCharCode ( ( c & 0x3FF ) + 0xDC00 ) ; }
2023-05-04 16:24:54 +02:00
String . _ _name _ _ = true ;
Array . _ _name _ _ = true ;
js _Boot . _ _toStr = ( { } ) . toString ;
2024-02-16 17:36:27 +01:00
haxe _Template . splitter = new EReg ( "(::[A-Za-z0-9_ ()&|!+=/><*.\"-]+::|\\$\\$([A-Za-z0-9_-]+)\\()" , "" ) ;
haxe _Template . expr _splitter = new EReg ( "(\\(|\\)|[ \r\n\t]*\"[^\"]*\"[ \r\n\t]*|[!+=/><*.&|-]+)" , "" ) ;
haxe _Template . expr _trim = new EReg ( "^[ ]*([^ ]+)[ ]*$" , "" ) ;
haxe _Template . expr _int = new EReg ( "^[0-9]+$" , "" ) ;
haxe _Template . expr _float = new EReg ( "^([+-]?)(?=\\d|,\\d)\\d*(,\\d*)?([Ee]([+-]?\\d+))?$" , "" ) ;
haxe _Template . globals = { } ;
haxe _Template . hxKeepArrayIterator = new haxe _iterators _ArrayIterator ( [ ] ) ;
2023-05-04 16:24:54 +02:00
xrfragment _Parser . error = "" ;
xrfragment _Parser . debug = false ;
2024-02-16 17:36:27 +01:00
xrfragment _URI . _ _meta _ _ = { statics : { template : { keep : null } } } ;
2024-04-16 15:19:08 +02:00
xrfragment _URI . _parts = [ "source" , "scheme" , "authority" , "userInfo" , "user" , "password" , "host" , "port" , "relative" , "path" , "directory" , "file" , "query" , "fragment" ] ;
2024-02-16 17:36:27 +01:00
xrfragment _XRF . IMMUTABLE = 1 ;
2023-05-04 16:24:54 +02:00
xrfragment _XRF . PROP _BIND = 2 ;
xrfragment _XRF . QUERY _OPERATOR = 4 ;
xrfragment _XRF . PROMPT = 8 ;
2024-02-16 17:36:27 +01:00
xrfragment _XRF . CUSTOMFRAG = 16 ;
2023-05-05 13:26:17 +02:00
xrfragment _XRF . NAVIGATOR = 32 ;
2023-08-15 18:27:26 +02:00
xrfragment _XRF . METADATA = 64 ;
2023-05-04 16:24:54 +02:00
xrfragment _XRF . PV _OVERRIDE = 128 ;
xrfragment _XRF . PV _EXECUTE = 256 ;
xrfragment _XRF . T _COLOR = 8192 ;
xrfragment _XRF . T _INT = 16384 ;
xrfragment _XRF . T _FLOAT = 32768 ;
xrfragment _XRF . T _VECTOR2 = 65536 ;
xrfragment _XRF . T _VECTOR3 = 131072 ;
xrfragment _XRF . T _URL = 262144 ;
xrfragment _XRF . T _PREDEFINED _VIEW = 524288 ;
xrfragment _XRF . T _STRING = 1048576 ;
2024-02-16 17:36:27 +01:00
xrfragment _XRF . T _MEDIAFRAG = 2097152 ;
xrfragment _XRF . T _DYNAMICKEY = 4194304 ;
xrfragment _XRF . T _DYNAMICKEYVALUE = 8388608 ;
2023-05-04 16:24:54 +02:00
xrfragment _XRF . isColor = new EReg ( "^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$" , "" ) ;
2023-11-03 21:22:05 +01:00
xrfragment _XRF . isInt = new EReg ( "^[-0-9]+$" , "" ) ;
xrfragment _XRF . isFloat = new EReg ( "^[-0-9]+\\.[0-9]+$" , "" ) ;
2023-05-04 16:24:54 +02:00
xrfragment _XRF . isVector = new EReg ( "([,]+|\\w)" , "" ) ;
xrfragment _XRF . isUrl = new EReg ( "(://)?\\..*" , "" ) ;
xrfragment _XRF . isUrlOrPretypedView = new EReg ( "(^#|://)?\\..*" , "" ) ;
xrfragment _XRF . isString = new EReg ( ".*" , "" ) ;
2024-02-16 17:36:27 +01:00
xrfragment _XRF . operators = new EReg ( "(^[-]|^[!]|[\\*]$)" , "g" ) ;
2023-11-28 17:39:33 +01:00
xrfragment _XRF . isProp = new EReg ( "^.*=[><=]?" , "" ) ;
xrfragment _XRF . isExclude = new EReg ( "^-" , "" ) ;
xrfragment _XRF . isDeep = new EReg ( "\\*" , "" ) ;
xrfragment _XRF . isNumber = new EReg ( "^[0-9\\.]+$" , "" ) ;
2024-02-16 17:36:27 +01:00
xrfragment _XRF . isMediaFrag = new EReg ( "^([0-9\\.,\\*+-]+)$" , "" ) ;
xrfragment _XRF . isReset = new EReg ( "^!" , "" ) ;
xrfragment _XRF . isShift = new EReg ( "^(\\+|--)" , "" ) ;
xrfragment _XRF . isXRFScheme = new EReg ( "^xrf://" , "" ) ;
} ) ( typeof window != "undefined" ? window : typeof global != "undefined" ? global : typeof self != "undefined" ? self : this ) ;
2023-05-04 16:24:54 +02:00
var xrfragment = $hx _exports [ "xrfragment" ] ;
2024-01-03 15:23:34 +01:00
// the core project uses #vanillajs #proxies #clean #noframework
2024-01-29 21:19:04 +01:00
var $ = typeof $ != 'undefined' ? $ : ( s ) => document . querySelector ( s ) // respect jquery
var $$ = typeof $$ != 'undefined' ? $$ : ( s ) => [ ... document . querySelectorAll ( s ) ] // zepto etc.
2024-01-03 15:23:34 +01:00
2024-01-29 21:19:04 +01:00
var $el = ( html , tag ) => {
2024-01-03 15:23:34 +01:00
let el = document . createElement ( 'div' )
el . innerHTML = html
return el . children [ 0 ]
}
2023-06-07 17:42:21 +02:00
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) 2023 Leon van Kammen/NLNET
var xrf = { }
xrf . init = function ( opts ) {
2023-11-28 17:39:33 +01:00
opts = opts || { }
2024-02-16 17:36:27 +01:00
2024-06-04 19:00:48 +02:00
xrf . debug = document . location . hostname . match ( /^(localhost|[0-9]\.[0-9])/ ) || document . location . port == '8080' ? 0 : false
2024-06-11 19:30:32 +02:00
if ( document . location . hash . match ( /debug=([0-9])/ ) ) {
2024-02-16 17:36:27 +01:00
xrf . debug = parseInt ( ( document . location . hash . match ( /debug=([0-9])/ ) || [ 0 , '0' ] ) [ 1 ] )
}
2024-06-11 19:30:32 +02:00
if ( xrf . debug === false ) console . log ( "add #debug=[0-9] to URL to see XR Fragment debuglog" )
2024-02-16 17:36:27 +01:00
2023-06-07 17:42:21 +02:00
xrf . Parser . debug = xrf . debug
2023-11-03 21:22:05 +01:00
xrf . detectCameraRig ( opts )
2024-06-04 19:00:48 +02:00
for ( let i in opts ) xrf [ i ] = opts [ i ]
2023-06-07 17:42:21 +02:00
xrf . emit ( 'init' , opts )
2023-10-13 11:45:17 +02:00
return xrf
2023-06-07 17:42:21 +02:00
}
xrf . query = function ( ) {
// framework implementations can override this function, see src/3rd/js/three/index.sj
alert ( "queries are not implemented (yet) for this particular framework" )
}
2023-11-03 21:22:05 +01:00
xrf . detectCameraRig = function ( opts ) {
if ( opts . camera ) { // detect rig (if any)
2024-10-07 21:02:52 +02:00
const getCam = ( ( cam ) => ( ) => cam ) ( opts . camera )
2023-11-03 21:22:05 +01:00
let offsetY = 0
while ( opts . camera . parent . type != "Scene" ) {
offsetY += opts . camera . position . y
opts . camera = opts . camera . parent
opts . camera . getCam = getCam
opts . camera . updateProjectionMatrix = ( ) => opts . camera . getCam ( ) . updateProjectionMatrix ( )
}
2024-10-07 21:02:52 +02:00
if ( ! opts . camera . getCam ) opts . camera . getCam = getCam // always attach function
2023-11-03 21:22:05 +01:00
opts . camera . offsetY = offsetY
}
}
2023-09-15 19:43:11 +02:00
xrf . hasTag = ( tag , tags ) => String ( tags ) . match ( new RegExp ( ` (^| ) ${ tag } ( | $ ) ` , ` g ` ) )
2023-06-09 16:40:08 +02:00
2023-06-07 17:42:21 +02:00
// map library functions to xrf
for ( let i in xrfragment ) xrf [ i ] = xrfragment [ i ]
/ *
2023-11-28 17:39:33 +01:00
* ( promise - able ) EVENTS ( optionally continue after listeners are finished using . then )
2023-06-07 17:42:21 +02:00
*
* example :
*
* xrf . addEventListener ( 'foo' , ( e ) => {
* // let promise = e.promise()
* console . log ( "navigating to: " + e . detail . destination . url )
* // promise.resolve()
* // promise.reject("not going to happen")
* } )
*
* xrf . emit ( 'foo' , 123 )
* xrf . emit ( 'foo' , 123 ) . then ( ... ) . catch ( ... ) . finally ( ... )
* /
2024-01-29 21:19:04 +01:00
xrf . addEventListener = function ( eventName , callback , opts ) {
2023-11-03 21:22:05 +01:00
if ( ! this . _listeners ) this . _listeners = [ ]
2024-01-29 21:19:04 +01:00
callback . opts = opts || { weight : this . _listeners . length }
2023-11-03 21:22:05 +01:00
if ( ! this . _listeners [ eventName ] ) {
// create a new array for this event name if it doesn't exist yet
this . _listeners [ eventName ] = [ ] ;
}
// add the callback to the listeners array for this event name
this . _listeners [ eventName ] . push ( callback ) ;
2024-01-29 21:19:04 +01:00
// sort
this . _listeners [ eventName ] = this . _listeners [ eventName ] . sort ( ( a , b ) => a . opts . weight > b . opts . weight )
2024-02-16 17:36:27 +01:00
callback . unlisten = ( ) => {
2023-11-03 21:22:05 +01:00
this . _listeners [ eventName ] = this . _listeners [ eventName ] . filter ( ( c ) => c != callback )
}
2024-02-16 17:36:27 +01:00
return callback . unlisten
2023-06-07 17:42:21 +02:00
} ;
xrf . emit = function ( eventName , data ) {
2023-06-22 08:48:52 +02:00
if ( typeof data != 'object' ) throw 'emit() requires passing objects'
2024-02-16 17:36:27 +01:00
if ( xrf . debug && xrf . debug > 1 && ( ! eventName . match ( /^render/ ) || xrf . debug == eventName ) ) {
2023-11-28 17:39:33 +01:00
let label = String ( ` xrf.emit(' ${ eventName } ') ` ) . padEnd ( 35 , " " ) ;
label += data . mesh && data . mesh . name ? '#' + data . mesh . name : ''
console . groupCollapsed ( label )
console . info ( data )
console . groupEnd ( label )
2024-07-09 19:27:21 +02:00
if ( eventName == 'reset' ) debugger
2024-06-11 19:30:32 +02:00
if ( xrf . debug > 2 ) debugger
2023-11-28 17:39:33 +01:00
}
2023-06-07 17:42:21 +02:00
return xrf . emit . promise ( eventName , data )
}
2024-03-19 10:53:22 +01:00
xrf . emit . normal = function ( eventName , opts ) {
2023-06-07 17:42:21 +02:00
if ( ! xrf . _listeners ) xrf . _listeners = [ ]
var callbacks = xrf . _listeners [ eventName ]
if ( callbacks ) {
2024-03-19 10:53:22 +01:00
for ( var i = 0 ; i < callbacks . length && ! opts . halt ; i ++ ) {
2024-01-03 15:23:34 +01:00
try {
2024-03-19 10:53:22 +01:00
callbacks [ i ] ( opts ) ;
2024-01-03 15:23:34 +01:00
} catch ( e ) { console . error ( e ) }
2023-06-07 17:42:21 +02:00
}
}
} ;
xrf . emit . promise = function ( e , opts ) {
return new Promise ( ( resolve , reject ) => {
opts . promise = ( ) => {
2023-11-28 17:39:33 +01:00
opts . promises = opts . promises || [ ]
opts . promises . push ( 0 )
return {
resolve : ( ( index ) => ( ) => {
opts . promises [ index ] = 1
let succesful = opts . promises . reduce ( ( a , b ) => a + b )
if ( succesful == opts . promises . length ) resolve ( opts )
} ) ( opts . promises . length - 1 ) ,
2024-03-19 10:53:22 +01:00
reject : ( reason ) => {
opts . halt = true
console . warn ( ` ' ${ e } ' event rejected: ${ reason } ` )
}
2023-11-28 17:39:33 +01:00
}
2023-06-07 17:42:21 +02:00
}
xrf . emit . normal ( e , opts )
2023-11-28 17:39:33 +01:00
if ( ! opts . promises ) resolve ( opts )
2023-09-15 19:43:11 +02:00
delete opts . promise
2023-06-07 17:42:21 +02:00
} )
}
2023-12-06 12:55:08 +01:00
xrf . addEventListener ( 'reset' , ( ) => {
2024-02-16 17:36:27 +01:00
let events = [ 'renderPost' ]
events . map ( ( e ) => {
if ( xrf . _listeners [ e ] ) xrf . _listeners [ e ] . map ( ( r ) => r . unlisten && r . unlisten ( ) )
} )
2023-12-06 12:55:08 +01:00
} )
2023-07-06 15:27:27 +02:00
/ * ! r a s t e r i z e H T M L . j s - v 1 . 3 . 1 - 2 0 2 3 - 0 7 - 0 6
* http : //www.github.com/cburgmer/rasterizeHTML.js
* Copyright ( c ) 2023 Christoph Burgmer ; Licensed MIT * /
2023-11-28 17:39:33 +01:00
! function ( o , i ) { void 0 === o && void 0 !== window && ( o = window ) , "function" == typeof define && define . amd ? define ( [ "url" , "xmlserializer" , "sane-domparser-error" , "inlineresources" ] , function ( e , t , n , r ) { return o . rasterizeHTML = i ( e , t , n , r ) } ) : "object" == typeof module && module . exports ? module . exports = i ( require ( "url" ) , require ( "xmlserializer" ) , require ( "sane-domparser-error" ) , require ( "inlineresources" ) ) : o . rasterizeHTML = i ( o . url , o . xmlserializer , o . sanedomparsererror , o . inlineresources ) } ( this , function ( e , t , n , r ) { var o = function ( n ) { "use strict" ; var o = { } , t = [ ] ; o . joinUrl = function ( e , t ) { return e ? n . resolve ( e , t ) : t } , o . getConstantUniqueIdFor = function ( e ) { return t . indexOf ( e ) < 0 && t . push ( e ) , t . indexOf ( e ) } , o . clone = function ( e ) { var t , n = { } ; for ( t in e ) e . hasOwnProperty ( t ) && ( n [ t ] = e [ t ] ) ; return n } ; return o . parseOptionalParameters = function ( e ) { var t , n , r = { canvas : null , options : { } } ; return null == e [ 0 ] || ( t = e [ 0 ] , "object" == typeof ( n = t ) && null !== n && Object . prototype . toString . apply ( t ) . match ( /\[object (Canvas|HTMLCanvasElement)\]/i ) ) ? ( r . canvas = e [ 0 ] || null , r . options = o . clone ( e [ 1 ] ) ) : r . options = o . clone ( e [ 0 ] ) , r } , o } ( e ) , i = function ( i ) { "use strict" ; function u ( e , t , n ) { var r = e [ t ] ; return e [ t ] = function ( ) { var e = Array . prototype . slice . call ( arguments ) ; return n . apply ( this , [ e , r ] ) } , r } var e = { } ; return e . baseUrlRespectingXhr = function ( t , o ) { return function ( ) { var e = new t ; return u ( e , "open" , function ( e , t ) { var n = e . shift ( ) , r = e . shift ( ) , r = i . joinUrl ( o , r ) ; return t . apply ( this , [ n , r ] . concat ( e ) ) } ) , e } } , e . finishNotifyingXhr = function ( t ) { function e ( ) { var e = new t ; return u ( e , "send" , function ( e , t ) { return r += 1 , t . apply ( this , arguments ) } ) , e . addEventListener ( "load" , function ( ) { o += 1 , n ( ) } ) , e } var n , r = 0 , o = 0 , i = ! 1 , c = new Promise ( function ( e ) { n = function ( ) { r - o <= 0 && i && e ( { totalCount : r } ) } } ) ; return e . waitForRequestsToFinish = function ( ) { return i = ! 0 , n ( ) , c } , e } , e } ( o ) , e = function ( i ) { "use strict" ; function r ( e ) { return Array . prototype . slice . call ( e ) } var e = { } , c = { active : ! 0 , hover : ! 0 , focus : ! 1 , target : ! 1 } ; return e . fakeUserAction = function ( e , t , n ) { var r = e . querySelector ( t ) , o = ":" + n , t = "rasterizehtml" + n ; r && ( c [ n ] ? i . addClassNameRecursively ( r , t ) : i . addClassName ( r , t ) , i . rewriteCssSelectorWith ( e , o , "." + t ) ) } , e . persistInputValues = function ( e ) { function t ( e ) { return "checkbox" === e . type || "radio" === e . type } var n = e . querySelectorAll ( "input" ) , e = e . querySelectorAll ( "textarea" ) ; r ( n ) . filter ( t ) . forEach ( function ( e ) { e . checked ? e . setAttribute ( "checked" , "" ) : e . removeAttribute ( "checked" ) } ) , r ( n ) . filter ( function ( e ) { return ! t ( e ) } ) . forEach ( function ( e ) { e . setAttribute ( "value" , e . value ) } ) , r ( e ) . forEach ( function ( e ) { e . textContent = e . value } ) } , e . rewriteTagNameSelectorsToLowerCase = function ( e ) { i . lowercaseCssTypeSelectors ( e , i . findHtmlOnlyNodeNames ( e ) ) } , e } ( function ( ) { "use strict" ; function c ( e ) { return Array . prototype . slice . call ( e ) } var n = { } ; n . addClassName = function ( e , t ) { e . className += " " + t } , n . addClassNameRecursively = function ( e , t ) { n . addClassName ( e , t ) , e . parentNode !== e . ownerDocument && n . addClassNameRecursively ( e . parentNode , t ) } ; function r ( e , t , o ) { var i = "((?:^|[^.#:\\w])|(?=\\W))(" + t . join ( "|" ) + ")(?=\\W|$)" ; c ( e . querySelectorAll ( "style" ) ) . forEach ( function ( e ) { var t , n ; void 0 === e . sheet && ( t = e , n = document . implementation . createHTMLDocument ( "" ) , ( r = document . createElement ( "style" ) ) . textContent = t . textContent , n . body . appendChild ( r ) , t . sheet = r . sheet ) ; var r = c ( e . sheet . cssRules ) . filter ( function ( e ) { return e . selectorText && new RegExp ( i , "i" ) . test ( e . selectorText ) } ) ; r . length && ( r . forEach ( function ( e ) { var t , n = e . selectorText . replace ( new RegExp ( i , "gi" ) , function ( e , t , n ) { return t + o ( n ) } ) ; n !== e . selectorText && ( t = n , e = ( n = e ) . cssText . replace ( /^[^\{]+/ , "" ) , u ( n , t + " " + e ) ) } ) , e . textContent = a ( e . sheet . cssRules ) ) } ) } var u = function ( e , t ) { var n = e . parentStyleSheet , e = c ( n . cssRules ) . indexOf ( e ) ; n . insertRule ( t , e + 1 ) , n . deleteRule ( e ) } , a = function ( e ) { return c ( e ) . reduce ( function ( e , t ) { return e + t . cssText } , "" ) } ; return n . rewriteCssSelectorWith = function ( e , t , n ) { r ( e , [ t ] , function ( ) { return n } ) } , n . lowercaseCssTypeSelectors = function ( e , t ) { r ( e , t , function ( e ) { return e . toLowerCase ( ) } ) } , n . findHtmlOnlyNodeNames = function ( e ) { for ( var t , n = e . ownerDocument . createTreeWalker ( e , NodeFilter . SHOW _ELEMENT ) , r = { } , o = { } ; t = n . currentNode . tagName . toLowerCase ( ) , "http://www.w3.org/1999/xhtml" === n . currentNode . namespaceURI ? r [ t ] = ! 0 : o [ t ] = ! 0 , n . nextNode ( ) ; ) ; retu
// the XRWG (XR WordGraph)is mentioned in the spec
2023-09-15 19:43:11 +02:00
//
// it collects metadata-keys ('foo' e.g.), names and tags across 3D scene-nodes (.userData.foo e.g.)
let XRWG = xrf . XRWG = [ ]
XRWG . word = ( key ) => XRWG . find ( ( w ) => w . word == word )
XRWG . cleankey = ( word ) => String ( word ) . replace ( /[^0-9\.a-zA-Z_]/g , '' )
. toLowerCase ( )
. replace ( /.*:\/\// , '' )
XRWG . get = ( v , k ) => XRWG . find ( ( x ) => x [ k || 'word' ] == v )
XRWG . match = ( str , types , level ) => {
2023-11-28 17:39:33 +01:00
if ( XRWG . length == 0 ) XRWG . generate ( xrf )
level = level == undefined ? 1000 : level
2023-09-15 19:43:11 +02:00
types = types || [ ]
let res = XRWG . filter ( ( n ) => {
types . map ( ( type ) => n [ type ] ? n = false : false )
return n
} )
str = str . toLowerCase ( )
2024-02-16 17:36:27 +01:00
. replace ( /[!-\*]/g , '' ) // remove excludes and wildcards
2023-11-28 17:39:33 +01:00
if ( level < 10 ) res = res . filter ( ( n ) => n . key == str )
if ( level >= 10 ) res = res . filter ( ( n ) => n . word == str || n . key == str )
if ( level > 30 ) res = res . filter ( ( n ) => n . word . match ( str ) || n . key == str )
if ( level > 40 ) res = res . filter ( ( n ) => n . word . match ( str ) || n . key == str || String ( n . value || '' ) . match ( str ) )
if ( level > 999 ) res = res . filter ( ( n ) => n . word . match ( str ) != null || n . key . match ( str ) != null || String ( n . value || '' ) . match ( str ) != null )
2023-09-15 19:43:11 +02:00
return res
}
XRWG . generate = ( opts ) => {
let { scene , model } = opts
2024-08-02 14:27:51 +02:00
if ( ! scene ) return console . warn ( 'xrfragment: XRWG cannot be generated from empty (nonloaded?) scene' )
2023-09-15 19:43:11 +02:00
XRWG . slice ( 0 , 0 ) // empty
// collect words from 3d nodes
let add = ( key , spatialNode , type ) => {
if ( ! key || key . match ( /(^#$|name)/ ) ) return
let node = XRWG . get ( XRWG . cleankey ( key ) )
if ( node ) {
2024-02-16 17:36:27 +01:00
node . types . push ( type )
2023-09-15 19:43:11 +02:00
node . nodes . push ( spatialNode )
} else {
2024-02-16 17:36:27 +01:00
node = { word : XRWG . cleankey ( key ) , key , nodes : [ spatialNode ] , types : [ ] }
2023-09-15 19:43:11 +02:00
if ( spatialNode . userData [ key ] ) node . value = spatialNode . userData [ key ]
2024-02-16 17:36:27 +01:00
node . types . push ( type )
2023-11-28 17:39:33 +01:00
xrf . emit ( 'XRWGnode' , node )
2023-09-15 19:43:11 +02:00
XRWG . push ( node )
}
}
scene . traverse ( ( o ) => {
add ( ` # ${ o . name } ` , o , 'name' )
for ( let k in o . userData ) {
if ( k == 'tag' ) {
let tagArr = o . userData . tag . split ( " " )
. map ( ( t ) => t . trim ( ) )
. filter ( ( t ) => t )
. map ( ( w ) => add ( w , o , 'tag' ) )
} else if ( k . match ( /^(href|src)$/ ) ) add ( o . userData [ k ] , o , k )
else if ( k [ 0 ] == '#' ) add ( k , o , 'pv' )
else add ( k , o , 'query' )
}
} )
// sort by n
XRWG . sort ( ( a , b ) => a . nodes . length - b . nodes . length )
XRWG = XRWG . reverse ( ) // the cleankey/get functions e.g. will persist
2023-11-28 17:39:33 +01:00
xrf . emit ( 'XRWG' , XRWG )
2023-09-15 19:43:11 +02:00
}
2024-02-16 17:36:27 +01:00
XRWG . deepApplyMatch = function ( match , v , cb ) {
match . map ( ( m ) => {
for ( let i in m . types ) {
let type = m . types [ i ]
let node = m . nodes [ i ]
if ( type == 'name' || type == 'tag' ) {
cb ( match , v , node , type )
if ( v . filter . q . deep ) node . traverse ( ( c ) => cb ( match , v , c , t ) )
}
}
} )
}
2023-09-15 19:43:11 +02:00
// the hashbus (QueryString eventBus) is mentioned in the spec
//
// it allows metadata-keys ('foo' e.g.) of 3D scene-nodes (.userData.foo e.g.) to
// react by executing code
2024-02-16 17:36:27 +01:00
let pub = function ( url , node _or _model , flags ) { // evaluate fragments in url
2023-09-15 19:43:11 +02:00
if ( ! url ) return
if ( ! url . match ( /#/ ) ) url = ` # ${ url } `
let { THREE , camera } = xrf
2024-04-16 15:19:08 +02:00
let frag = xrf . URI . parse ( url , flags ) . XRF
2024-02-16 17:36:27 +01:00
let fromNode = node _or _model != xrf . model
2024-02-29 14:36:00 +01:00
let isNode = node _or _model && node _or _model . children
2024-02-16 17:36:27 +01:00
let opts = {
frag ,
mesh : fromNode ? node _or _model : xrf . camera ,
model : xrf . model ,
camera : xrf . camera ,
2024-02-29 14:36:00 +01:00
scene : isNode ? node _or _model : xrf . scene ,
2024-02-16 17:36:27 +01:00
renderer : xrf . renderer ,
THREE : xrf . THREE ,
hashbus : xrf . hashbus
}
2023-09-15 19:43:11 +02:00
xrf . emit ( 'hashbus' , opts )
. then ( ( ) => {
for ( let k in frag ) {
2024-02-16 17:36:27 +01:00
let nodeAlias = fromNode && opts . mesh && opts . mesh . userData && opts . mesh . userData [ k ] && opts . mesh . userData [ k ] [ 0 ] == '#'
if ( nodeAlias ) pub ( opts . mesh . userData [ k ] , opts . mesh ) // evaluate node alias
else pub . fragment ( k , opts )
2023-09-15 19:43:11 +02:00
}
} )
return frag
}
pub . fragment = ( k , opts ) => { // evaluate one fragment
let frag = opts . frag [ k ] ;
2023-10-12 17:04:46 +02:00
2024-02-16 17:36:27 +01:00
let isPVorMediaFrag = frag . is ( xrf . XRF . PV _EXECUTE ) || frag . is ( xrf . XRF . T _MEDIAFRAG )
if ( ! opts . skipXRWG && isPVorMediaFrag ) pub . XRWG ( k , opts )
2023-11-28 17:39:33 +01:00
2023-09-15 19:43:11 +02:00
// call native function (xrf/env.js e.g.), or pass it to user decorator
2024-07-09 19:27:21 +02:00
if ( xrf . frag [ k ] ) {
xrf . emit ( k , opts )
. then ( ( ) => {
let func = xrf . frag [ k ] || function ( ) { }
func ( frag , opts )
} )
}
2023-09-15 19:43:11 +02:00
}
2024-02-16 17:36:27 +01:00
pub . XRWG = ( word , opts ) => {
2023-11-03 21:22:05 +01:00
let { frag , scene , model , renderer } = opts
// if this query was triggered by an src-value, lets filter it
const isSRC = opts . embedded && opts . embedded . fragment == 'src'
if ( ! isSRC ) { // spec : https://xrfragment.org/#src
2024-02-16 17:36:27 +01:00
let triggeredByMesh = opts . model != opts . mesh
let v = frag [ word ]
let id = v . is ( xrf . XRF . T _DYNAMICKEY ) ? word : v . string || word
if ( id == '#' || ! id ) return
let match = xrf . XRWG . match ( id )
if ( ! triggeredByMesh && ( v . is ( xrf . XRF . PV _EXECUTE ) || v . is ( xrf . XRF . T _DYNAMIC ) ) && ! v . is ( xrf . XRF . T _DYNAMICKEYVALUE ) ) {
// evaluate global aliases or tag/objectnames
match . map ( ( w ) => {
if ( w . key == ` # ${ id } ` ) {
if ( w . value && w . value [ 0 ] == '#' ) {
// if value is alias, execute fragment value
xrf . hashbus . pub ( w . value , xrf . model , xrf . XRF . METADATA | xrf . XRF . PV _OVERRIDE | xrf . XRF . NAVIGATOR )
2023-11-03 21:22:05 +01:00
}
2024-02-16 17:36:27 +01:00
}
} )
xrf . emit ( 'dynamicKey' , { ... opts , v , frag , id , match , scene } )
} else if ( v . string ) {
// evaluate global aliases
xrf . emit ( 'dynamicKeyValue' , { ... opts , v , frag , id , match , scene } )
} else {
xrf . emit ( 'dynamicKey' , { ... opts , v , frag , id , match , scene } )
2023-11-03 21:22:05 +01:00
}
}
}
2023-09-15 19:43:11 +02:00
xrf . hashbus = { pub }
2024-02-16 17:36:27 +01:00
xrf . frag = { dynamic : { } }
2023-08-04 09:11:26 +02:00
xrf . model = { }
2023-11-03 21:22:05 +01:00
xrf . mixers = [ ]
2023-08-04 09:11:26 +02:00
xrf . init = ( ( init ) => function ( opts ) {
2024-06-04 19:00:48 +02:00
// operate in own subscene
2023-09-21 13:05:30 +02:00
let scene = new opts . THREE . Group ( )
2024-06-11 19:30:32 +02:00
xrf . clock = new opts . THREE . Clock ( )
2025-01-15 10:53:56 +01:00
// don't mess with original scene object
// but with our own sub-scene
2023-09-21 13:05:30 +02:00
opts . scene . add ( scene )
2024-06-04 19:00:48 +02:00
opts . sceneRoot = opts . scene
opts . scene = scene
2023-08-04 09:11:26 +02:00
init ( opts )
2024-02-16 17:36:27 +01:00
//if( opts.loaders ) Object.values(opts.loaders).map( xrf.patchLoader )
2023-08-04 09:11:26 +02:00
2023-11-03 21:22:05 +01:00
xrf . patchRenderer ( opts )
2023-08-04 09:11:26 +02:00
xrf . navigator . init ( )
2024-06-04 19:00:48 +02:00
xrf . interactive = xrf . interactiveGroup ( xrf . THREE , xrf . renderer , xrf . camera )
2023-08-04 09:11:26 +02:00
// return xrfragment lib as 'xrf' query functor (like jquery)
for ( let i in xrf ) xrf . query [ i ] = xrf [ i ]
2024-01-03 15:23:34 +01:00
2023-08-04 09:11:26 +02:00
return xrf . query
} ) ( xrf . init )
2023-11-03 21:22:05 +01:00
xrf . patchRenderer = function ( opts ) {
let { renderer , camera } = opts
2023-08-04 09:11:26 +02:00
renderer . xr . addEventListener ( 'sessionstart' , ( ) => xrf . baseReferenceSpace = renderer . xr . getReferenceSpace ( ) ) ;
renderer . xr . enabled = true ;
renderer . render = ( ( render ) => function ( scene , camera ) {
2023-11-03 21:22:05 +01:00
// update clock
2024-02-16 17:36:27 +01:00
let time = xrf . clock . delta = xrf . clock . getDelta ( )
2023-11-28 17:39:33 +01:00
xrf . emit ( 'render' , { scene , camera , time , render } ) // allow fragments to do something at renderframe
2023-08-04 09:11:26 +02:00
render ( scene , camera )
2023-11-28 17:39:33 +01:00
xrf . emit ( 'renderPost' , { scene , camera , time , render , renderer } ) // allow fragments to do something after renderframe
2023-08-04 09:11:26 +02:00
} ) ( renderer . render . bind ( renderer ) )
2023-11-03 21:22:05 +01:00
2023-08-04 09:11:26 +02:00
}
xrf . getFile = ( url ) => url . split ( "/" ) . pop ( ) . replace ( /#.*/ , '' )
2024-01-30 10:58:00 +01:00
// parseModel event is essential for src.js to hook into embedded loaded models
2023-08-04 09:11:26 +02:00
xrf . parseModel = function ( model , url ) {
let file = xrf . getFile ( url )
model . file = file
2024-01-30 10:58:00 +01:00
model . isXRF = true
2024-04-25 17:57:06 +02:00
model . scene . isXRFRoot = true
2024-10-13 14:03:31 +02:00
model . scene . traverse ( ( n ) => {
n . isXRF = true
} ) // mark for deletion during reset()
2024-02-29 14:36:00 +01:00
2023-11-03 21:22:05 +01:00
xrf . emit ( 'parseModel' , { model , url , file } )
2023-08-04 09:11:26 +02:00
}
2024-06-15 17:33:08 +02:00
xrf . loadModel = function ( model , url , noadd ) {
let URI = xrfragment . URI . toAbsolute ( xrf . navigator . URI , url )
let { directory , file , fragment , fileExt } = URI ;
model . file = URI . file
xrf . model = model
if ( ! model . isXRF ) xrf . parseModel ( model , url . replace ( directory , "" ) ) // this marks the model as an XRF model
if ( xrf . debug ) model . animations . map ( ( a ) => console . log ( "anim: " + a . name ) )
// spec: 1. generate the XRWG
xrf . XRWG . generate ( { model , scene : model . scene } )
// spec: 2. init metadata inside model for non-SRC data
if ( ! model . isSRC ) {
model . scene . traverse ( ( mesh ) => xrf . parseModel . metadataInMesh ( mesh , model ) )
}
// spec: 1. execute the default predefined view '#' (if exist) (https://xrfragment.org/#predefined_view)
const defaultFragment = xrf . frag . defaultPredefinedViews ( { model , scene : model . scene } )
// spec: predefined view(s) & objects-of-interest-in-XRWG from URI (https://xrfragment.org/#predefined_view)
let frag = xrf . hashbus . pub ( url , model ) // and eval URI XR fragments
2024-07-12 18:19:19 +02:00
2024-06-15 17:33:08 +02:00
if ( ! noadd ) xrf . add ( model . scene )
// only change url when loading *another* file
fragment = fragment || defaultFragment || ''
xrf . navigator . pushState ( URI . external ? URI . URN + URI . file : URI . file , fragment . replace ( /^#/ , '' ) )
//if( fragment ) xrf.navigator.updateHash(fragment)
xrf . emit ( 'navigateLoaded' , { url , model } )
}
2024-02-29 14:36:00 +01:00
xrf . parseModel . metadataInMesh = ( mesh , model ) => {
if ( mesh . userData ) {
let frag = { }
for ( let k in mesh . userData ) xrf . Parser . parse ( k , mesh . userData [ k ] , frag )
for ( let k in frag ) {
let opts = { frag , mesh , model , camera : xrf . camera , scene : model . scene , renderer : xrf . renderer , THREE : xrf . THREE , hashbus : xrf . hashbus }
mesh . userData . XRF = frag // allow fragment impl to access XRF obj already
xrf . emit ( 'frag2mesh' , opts )
. then ( ( ) => {
xrf . hashbus . pub . fragment ( k , { ... opts , skipXRWG : true } )
} )
}
}
}
2023-09-15 19:43:11 +02:00
xrf . getLastModel = ( ) => xrf . model . last
2023-08-04 09:11:26 +02:00
xrf . reset = ( ) => {
2024-02-16 17:36:27 +01:00
// allow others to reset certain events
xrf . emit ( 'reset' , { } )
2024-10-13 14:03:31 +02:00
// reattach camera to root scene
xrf . scene . attach ( xrf . camera )
xrf . camera . position . set ( 0 , 0 , 0 )
xrf . camera . updateMatrixWorld ( )
xrf . camera . getCam ( ) . updateMatrixWorld ( )
2024-01-31 19:47:02 +01:00
2023-08-04 09:11:26 +02:00
const disposeObject = ( obj ) => {
2024-10-13 14:03:31 +02:00
if ( obj . children . length > 0 ) {
obj . children . forEach ( ( child ) => disposeObject ( child ) ) ;
}
2023-08-04 09:11:26 +02:00
if ( obj . geometry ) obj . geometry . dispose ( ) ;
if ( obj . material ) {
if ( obj . material . map ) obj . material . map . dispose ( ) ;
obj . material . dispose ( ) ;
}
obj . clear ( )
obj . removeFromParent ( )
return true
} ;
2024-06-04 19:00:48 +02:00
// also remove XRF objects from global scene
2023-08-04 09:11:26 +02:00
let nodes = [ ]
2023-11-28 17:39:33 +01:00
xrf . scene . traverse ( ( child ) => child . isXRF && ( nodes . push ( child ) ) )
2024-06-04 19:00:48 +02:00
nodes . map ( disposeObject )
xrf . interactive . clear ( )
2023-09-21 13:05:30 +02:00
xrf . layers = 0
2023-08-04 09:11:26 +02:00
}
xrf . add = ( object ) => {
object . isXRF = true // mark for easy deletion when replacing scene
xrf . scene . add ( object )
}
2023-05-12 22:06:21 +02:00
2023-11-28 17:39:33 +01:00
xrf . hasNoMaterial = ( mesh ) => {
const hasTexture = mesh . material && mesh . material . map
const hasMaterialName = mesh . material && mesh . material . name . length > 0
return mesh . geometry && ! hasMaterialName && ! hasTexture
2023-05-12 22:06:21 +02:00
}
2024-07-09 19:27:21 +02:00
2024-04-16 18:44:55 +02:00
xrf . navigator = {
2024-07-24 14:43:25 +02:00
opts : {
openInNewTab : true
} ,
2024-07-09 19:27:21 +02:00
URI : xrf . URI . parse ( document . location . href )
// scheme: document.location.protocol.replace(/:$/,''),
// directory: document.location.pathname,
// host: document.location.hostname,
// port: document.location.port,
// file: 'index.glb'
// }
2024-04-16 18:44:55 +02:00
}
2023-05-12 22:06:21 +02:00
2023-07-04 17:15:23 +02:00
xrf . navigator . to = ( url , flags , loader , data ) => {
2023-05-18 17:11:11 +02:00
if ( ! url ) throw 'xrf.navigator.to(..) no url given'
2024-04-16 15:19:08 +02:00
let hashbus = xrf . hashbus
2024-10-14 11:54:39 +02:00
let 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
}
2024-07-17 13:07:35 +02:00
2024-10-14 11:54:39 +02:00
URI . last = xrf . navigator . URI
2024-04-16 15:19:08 +02:00
xrf . navigator . URI = URI
let { directory , file , fragment , fileExt } = URI ;
const evalFragment = ( ) => {
if ( URI . fragment ) {
hashbus . pub ( URI . fragment , xrf . model , flags ) // eval local URI XR fragments
xrf . navigator . updateHash ( fragment ) // which don't require
}
}
2023-09-15 19:43:11 +02:00
2023-05-12 22:06:21 +02:00
return new Promise ( ( resolve , reject ) => {
2024-01-29 21:19:04 +01:00
xrf
. emit ( 'navigate' , { url , loader , data } )
. then ( ( ) => {
2023-05-18 17:11:11 +02:00
2024-04-16 15:19:08 +02:00
const Loader = xrf . loaders [ fileExt ]
if ( fileExt && ! loader ) {
2024-01-29 21:19:04 +01:00
if ( ! Loader ) return resolve ( )
2024-04-16 15:19:08 +02:00
loader = loader || new Loader ( ) . setPath ( URI . URN )
2024-01-29 21:19:04 +01:00
}
2023-07-04 17:15:23 +02:00
2024-07-09 19:27:21 +02:00
if ( URI . duplicatePos || ( ! Object . values ( URI . XRF ) . length && ! URI . file && ! URI . fileExt ) ) {
2024-04-25 17:57:06 +02:00
return resolve ( xrf . model ) // nothing we can do here
}
if ( xrf . model && ! URI . fileChange && URI . hashChange && ! URI . hasPos ) {
2024-04-16 15:19:08 +02:00
evalFragment ( )
2024-04-25 17:57:06 +02:00
return resolve ( xrf . model ) // eval non-positional fragments (no loader needed)
2024-01-29 21:19:04 +01:00
}
xrf
. emit ( 'navigateLoading' , { url , loader , data } )
. then ( ( ) => {
2024-04-25 17:57:06 +02:00
if ( ( ! URI . fileChange || ! file ) && URI . hashChange && URI . hasPos ) { // we're already loaded
2024-04-16 15:19:08 +02:00
evalFragment ( )
2024-01-29 21:19:04 +01:00
xrf . emit ( 'navigateLoaded' , { url } )
return resolve ( xrf . model )
}
2024-04-25 17:57:06 +02:00
2024-01-29 21:19:04 +01:00
// clear xrf objects from scene
if ( xrf . model && xrf . model . scene ) xrf . model . scene . visible = false
xrf . reset ( )
// force relative path for files which dont include protocol or relative path
2024-04-16 15:19:08 +02:00
if ( directory ) directory = directory [ 0 ] == '.' || directory . match ( "://" ) ? directory : ` . ${ directory } `
2024-06-25 15:58:12 +02:00
if ( loader || Loader ) {
const onLoad = ( model ) => {
xrf . loadModel ( model , url )
resolve ( model )
2024-02-16 17:36:27 +01:00
}
2024-06-25 15:58:12 +02:00
loader = loader || new Loader ( ) . setPath ( URI . URN )
if ( data ) { // file upload
loader . parse ( data , "" , onLoad )
} else {
try {
loader . load ( file , onLoad )
} catch ( e ) {
console . error ( e )
xrf . emit ( 'navigateError' , { url } )
}
}
} else xrf . emit ( 'navigateError' , { url , URI } )
2024-01-29 21:19:04 +01:00
} )
} )
2023-05-12 22:06:21 +02:00
} )
}
2023-05-17 21:31:28 +02:00
xrf . navigator . init = ( ) => {
if ( xrf . navigator . init . inited ) return
2023-10-12 17:04:46 +02:00
2024-04-16 15:19:08 +02:00
xrf . navigator . URI = xrfragment . URI . parse ( document . location . href )
2023-05-12 22:06:21 +02:00
window . addEventListener ( 'popstate' , function ( event ) {
2024-06-26 14:42:00 +02:00
if ( xrf . navigator . updateHash . active ) { // ignore programmatic hash updates (causes infinite recursion)
2024-10-14 11:54:39 +02:00
xrf . navigator . to ( xrf . navigator . URI . last )
2024-01-29 21:19:04 +01:00
}
2023-05-12 22:06:21 +02:00
} )
2023-10-12 17:04:46 +02:00
window . addEventListener ( 'hashchange' , function ( e ) {
xrf . emit ( 'hash' , { hash : document . location . hash } )
} )
2023-09-15 19:43:11 +02:00
2024-01-29 21:19:04 +01:00
xrf . navigator . setupNavigateFallbacks ( )
2023-09-15 19:43:11 +02:00
// this allows selectionlines to be updated according to the camera (renderloop)
xrf . focusLine = new xrf . THREE . Group ( )
xrf . focusLine . material = new xrf . THREE . LineDashedMaterial ( { color : 0xFF00FF , linewidth : 3 , scale : 1 , dashSize : 0.2 , gapSize : 0.1 , opacity : 0.3 , transparent : true } )
xrf . focusLine . isXRF = true
xrf . focusLine . position . set ( 0 , 0 , - 0.5 ) ;
xrf . focusLine . points = [ ]
xrf . focusLine . lines = [ ]
xrf . camera . add ( xrf . focusLine )
2023-05-17 21:31:28 +02:00
xrf . navigator . init . inited = true
2023-05-12 22:06:21 +02:00
}
2024-01-29 21:19:04 +01:00
xrf . navigator . setupNavigateFallbacks = ( ) => {
xrf . addEventListener ( 'navigate' , ( opts ) => {
let { url } = opts
2024-04-16 15:19:08 +02:00
let { fileExt } = xrfragment . URI . parse ( url )
2024-02-16 17:36:27 +01:00
2024-01-29 21:19:04 +01:00
// handle http links
2024-06-11 19:30:32 +02:00
if ( url . match ( /^http/ ) && url != xrf . navigator . URI . URN && ! xrf . loaders [ fileExt ] ) {
2024-01-29 21:19:04 +01:00
let inIframe
try { inIframe = window . self !== window . top ; } catch ( e ) { inIframe = true ; }
2024-07-24 14:43:25 +02:00
if ( inIframe ) {
window . parent . postMessage ( { url } , '*' )
} else {
if ( xrf . navigator . opts . openInNewTab ) window . open ( url , '_blank' )
else { setTimeout ( ( ) => document . location . href = url , 1000 ) }
}
2024-01-29 21:19:04 +01:00
// in case you're running in an iframe, then use this in the parent page:
//
// window.addEventListener("message", (e) => {
// if (e.data && e.data.url){
// window.open( e.data.url, '_blank')
// }
// },
// false,
// );
}
} )
}
2023-09-15 19:43:11 +02:00
xrf . navigator . updateHash = ( hash , opts ) => {
if ( hash . replace ( /^#/ , '' ) == document . location . hash . substr ( 1 ) || hash . match ( /\|/ ) ) return // skip unnecesary pushState triggers
2024-04-16 15:19:08 +02:00
console . log ( ` URI: ${ document . location . search . substr ( 1 ) } # ${ hash } ` )
2024-07-09 19:27:21 +02:00
xrf . navigator . updateHash . active = false // important to prevent recursion
2023-06-22 08:48:52 +02:00
document . location . hash = hash
2024-07-09 19:27:21 +02:00
xrf . navigator . updateHash . active = true
2023-06-22 08:48:52 +02:00
}
2024-07-25 11:31:51 +02:00
xrf . navigator . updateHash . active = true
2023-06-22 08:48:52 +02:00
2023-05-18 17:11:11 +02:00
xrf . navigator . pushState = ( file , hash ) => {
2023-05-18 12:32:57 +02:00
if ( file == document . location . search . substr ( 1 ) ) return // page is in its default state
2024-07-09 19:27:21 +02:00
window . history . pushState ( { } , '' , document . location . pathname + ` ? ${ xrf . navigator . URI . source . replace ( /#.*/ , '' ) } # ${ hash } ` )
2023-09-15 19:43:11 +02:00
xrf . emit ( 'pushState' , { file , hash } )
2023-05-12 22:06:21 +02:00
}
2024-04-16 15:19:08 +02:00
xrf . navigator . reactifyHash = ( obj ) => {
return new Proxy ( obj , {
get ( me , k ) { return me [ k ] } ,
set ( me , k , v ) {
me [ k ] = v
2024-06-26 14:42:00 +02:00
if ( xrf . navigator . updateHash . active ) {
2024-06-11 19:30:32 +02:00
xrf . navigator . to ( "#" + this . toString ( me ) )
}
2024-06-26 14:42:00 +02:00
xrf . navigator . URI . fragment = this . toString ( me )
2024-04-16 15:19:08 +02:00
} ,
toString ( me ) {
let parts = [ ]
Object . keys ( me ) . map ( ( k ) => {
2024-04-16 18:44:55 +02:00
parts . push ( me [ k ] ? ` ${ k } = ${ me [ k ] } ` : k )
2024-04-16 15:19:08 +02:00
} )
return parts . join ( '&' )
}
} )
}
2023-05-22 15:03:23 +02:00
/ * *
2023-05-23 14:41:24 +02:00
*
2023-05-22 15:03:23 +02:00
* navigation , portals & mutations
2023-05-23 14:41:24 +02:00
*
2023-05-22 15:03:23 +02:00
* | fragment | type | scope | example value |
2023-05-23 14:41:24 +02:00
* | ` href ` | string ( uri or predefined view ) | 🔒 | ` #pos=1,1,0 ` < br > ` #pos=1,1,0&rot=90,0,0 ` < br > ` #pos=pyramid ` < br > ` #pos=lastvisit|pyramid ` < br > ` ://somefile.gltf#pos=1,1,0 ` < br > |
*
* [ [ » example implementation | https : //github.com/coderofsalvation/xrfragment/blob/main/src/3rd/three/xrf/href.js]]<br>
* [ [ » example 3 D asset | https : //github.com/coderofsalvation/xrfragment/blob/main/example/assets/href.gltf#L192]]<br>
* [ [ » discussion | https : //github.com/coderofsalvation/xrfragment/issues/1]]<br>
2023-05-22 15:03:23 +02:00
*
* [ img [ xrfragment . jpg ] ]
2023-05-23 14:41:24 +02:00
*
*
2023-05-22 15:03:23 +02:00
* ! ! ! spec 1.0
2023-05-23 14:41:24 +02:00
*
* 1. an '' external '' - or '' file URI '' fully replaces the current scene and assumes ` pos=0,0,0&rot=0,0,0 ` by default ( unless specified )
*
2023-05-22 15:03:23 +02:00
* 2. navigation should not happen when queries ( ` q= ` ) are present in local url : queries will apply ( ` pos= ` , ` rot= ` e . g . ) to the targeted object ( s ) instead .
2023-05-23 14:41:24 +02:00
*
2023-05-22 15:03:23 +02:00
* 3. navigation should not happen '' immediately '' when user is more than 2 meter away from the portal / object containing the href ( to prevent accidental navigation e . g . )
2023-05-23 14:41:24 +02:00
*
* 4. URL navigation should always be reflected in the client ( in case of javascript : see [ [ here | https : //github.com/coderofsalvation/xrfragment/blob/dev/src/3rd/three/navigator.js]] for an example navigator).
*
* 5. In XR mode , the navigator back / forward - buttons should be always visible ( using a wearable e . g . , see [ [ here | https : //github.com/coderofsalvation/xrfragment/blob/dev/example/aframe/sandbox/index.html#L26-L29]] for an example wearable)
*
* [ img [ navigation . png ] ]
*
2023-05-22 15:03:23 +02:00
* /
2023-05-12 22:06:21 +02:00
xrf . frag . href = function ( v , opts ) {
2023-09-21 13:05:30 +02:00
let { frag , mesh , model , camera , scene , renderer , THREE } = opts
2023-05-05 18:53:42 +02:00
2023-06-09 16:40:08 +02:00
if ( mesh . userData . XRF . href . exec ) return // mesh already initialized
2024-07-12 10:09:28 +02:00
// correct for relative urls
if ( v . string . charAt ( 0 ) != '#' && xrf . URI . isRelative ( xrf . URI . parse ( v . string ) ) ) {
v . string = xrf . navigator . URI . URN + v . string
}
2023-06-07 17:42:21 +02:00
let click = mesh . userData . XRF . href . exec = ( e ) => {
2023-10-11 13:46:38 +02:00
2024-12-10 13:54:26 +01:00
if ( ! mesh . material || ! ( mesh . material && mesh . material . visible ) ) return // ignore invisible nodes
2024-03-19 10:53:22 +01:00
// update our values to the latest value (might be edited)
2024-07-09 19:27:21 +02:00
let URI = xrf . URI . template ( mesh . userData . href , xrf . URI . vars . _ _object )
xrf . Parser . parse ( "href" , URI , frag )
2024-03-19 10:53:22 +01:00
const v = frag . href
2024-02-16 17:36:27 +01:00
// bubble up!
mesh . traverseAncestors ( ( n ) => n . userData && n . userData . href && n . dispatchEvent ( { type : e . type , data : { } } ) )
2024-06-11 19:30:32 +02:00
let fragValue = xrf . URI . parse ( v . string , xrf . XRF . NAVIGATOR | xrf . XRF . PV _OVERRIDE | xrf . XRF . METADATA )
2023-11-28 17:39:33 +01:00
let lastPos = ` pos= ${ camera . position . x . toFixed ( 2 ) } , ${ camera . position . y . toFixed ( 2 ) } , ${ camera . position . z . toFixed ( 2 ) } `
2023-06-08 17:45:21 +02:00
xrf
2024-06-11 19:30:32 +02:00
. emit ( 'href' , { click : true , mesh , xrf : v , value : fragValue } ) // let all listeners agree
2023-06-08 17:45:21 +02:00
. then ( ( ) => {
2024-01-29 21:19:04 +01:00
const isLocal = v . string [ 0 ] == '#'
const hasPos = isLocal && v . string . match ( /pos=/ )
const flags = isLocal ? xrf . XRF . PV _OVERRIDE : undefined
2024-02-16 17:36:27 +01:00
if ( v . xrfScheme ) {
xrf . hashbus . pub ( v . string )
} else xrf . navigator . to ( v . string ) // let's surf
2023-06-08 17:45:21 +02:00
} )
2023-10-11 13:46:38 +02:00
. catch ( console . error )
2023-05-22 14:10:44 +02:00
}
2023-10-14 20:10:06 +02:00
let selected = mesh . userData . XRF . href . selected = ( state ) => ( ) => {
2024-03-19 10:53:22 +01:00
if ( ( ! mesh . material && ! mesh . material . visible ) && ! mesh . isSRC ) return // ignore invisible nodes
2023-05-22 14:10:44 +02:00
if ( mesh . selected == state ) return // nothing changed
2024-02-16 17:36:27 +01:00
2023-10-12 17:04:46 +02:00
xrf . interactive . objects . map ( ( o ) => {
let newState = o . name == mesh . name ? state : false
if ( o . material ) {
2024-02-16 17:36:27 +01:00
if ( o . material . uniforms && o . material . uniforms . selected ) o . material . uniforms . selected . value = newState
2023-11-28 17:39:33 +01:00
//if( o.material.emissive ) o.material.emissive.r = o.material.emissive.g = o.material.emissive.b = newState ? 2.0 : 1.0
if ( o . material . emissive ) {
if ( ! o . material . emissive . original ) o . material . emissive . original = o . material . emissive . clone ( )
o . material . emissive . r = o . material . emissive . g = o . material . emissive . b =
newState ? o . material . emissive . original . r + 0.5 : o . material . emissive . original . r
}
2023-10-12 17:04:46 +02:00
}
} )
2023-05-22 14:10:44 +02:00
// update mouse cursor
if ( ! renderer . domElement . lastCursor )
renderer . domElement . lastCursor = renderer . domElement . style . cursor
renderer . domElement . style . cursor = state ? 'pointer' : renderer . domElement . lastCursor
2023-10-12 17:04:46 +02:00
2023-06-02 12:00:21 +02:00
xrf
. emit ( 'href' , { selected : state , mesh , xrf : v } ) // let all listeners agree
. then ( ( ) => mesh . selected = state )
2023-05-12 22:06:21 +02:00
}
2023-06-07 17:42:21 +02:00
mesh . addEventListener ( 'click' , click )
mesh . addEventListener ( 'mousemove' , selected ( true ) )
2023-11-28 17:39:33 +01:00
mesh . addEventListener ( 'mouseenter' , selected ( true ) )
2023-10-14 20:10:06 +02:00
mesh . addEventListener ( 'mouseleave' , selected ( false ) )
2023-05-17 21:31:28 +02:00
2023-11-28 17:39:33 +01:00
if ( mesh . material ) mesh . material = mesh . material . clone ( ) // clone, so we can individually highlight meshes
2023-05-22 15:03:23 +02:00
// lazy add mesh (because we're inside a recursive traverse)
setTimeout ( ( mesh ) => {
xrf . interactive . add ( mesh )
2023-10-11 13:46:38 +02:00
xrf . emit ( 'interactionReady' , { mesh , xrf : v , clickHandler : mesh . userData . XRF . href . exec } )
} , 0 , mesh )
2023-05-05 18:53:42 +02:00
}
2023-05-22 15:03:23 +02:00
2024-02-16 17:36:27 +01:00
xrf . addEventListener ( 'audioInited' , function ( opts ) {
let { THREE , listener } = opts
opts . audio = opts . audio || { }
2024-06-11 19:30:32 +02:00
opts . audio . click = opts . audio . click || '/dist/audio/click.wav'
opts . audio . hover = opts . audio . hover || '/dist/audio/hover.wav'
opts . audio . teleport = opts . audio . teleport || '/dist/audio/teleport.wav'
2024-02-16 17:36:27 +01:00
let audio = xrf . frag . href . audio = { }
2024-06-12 10:51:42 +02:00
const actions = [ 'click' , 'hover' , 'teleport' ]
2024-02-16 17:36:27 +01:00
actions . map ( ( action ) => {
const audioLoader = new THREE . AudioLoader ( ) ;
audio [ action ] = new THREE . Audio ( xrf . camera . listener )
audioLoader . load ( opts . audio [ action ] , function ( buffer ) {
audio [ action ] . setBuffer ( buffer ) ;
} )
} ) ;
xrf . addEventListener ( 'href' , ( opts ) => {
let v = opts . xrf
if ( opts . selected ) {
xrf . frag . href . audio . hover . stop ( )
xrf . frag . href . audio . hover . play ( )
return
}
if ( opts . click ) {
xrf . frag . href . audio . click . stop ( )
xrf . frag . href . audio . click . play ( )
return
}
} )
xrf . addEventListener ( 'navigateLoading' , ( e ) => {
xrf . frag . href . audio . click . stop ( )
xrf . frag . href . audio . teleport . stop ( )
xrf . frag . href . audio . teleport . play ( )
} )
} )
2023-05-22 15:03:23 +02:00
/ * *
2023-05-23 14:41:24 +02:00
* > above solutions were abducted from [ [ this | https : //i.imgur.com/E3En0gJ.png]] and [[this|https://i.imgur.com/lpnTz3A.png]] survey result
2023-05-22 15:03:23 +02:00
*
2023-05-23 14:41:24 +02:00
* ! ! ! Demo
*
* < $videojs controls = "controls" aspectratio = "16:9" preload = "auto" poster = "" fluid = "fluid" class = "vjs-big-play-centered" >
* < source src = "https://coderofsalvation.github.io/xrfragment.media/href.mp4" type = "video/mp4" / >
* < / $ v i d e o j s >
*
* > capture of < a href = "./example/aframe/sandbox" target = "_blank" > aframe / sandbox < / a >
2023-05-22 15:03:23 +02:00
* /
2024-02-16 17:36:27 +01:00
// this is called by navigator.js rather than by a URL e.g.
2024-07-09 19:27:21 +02:00
xrf . frag [ '#' ] = xrf . frag . defaultPredefinedViews = ( opts ) => {
2024-02-16 17:36:27 +01:00
let { scene , model } = opts ;
2024-07-09 19:27:21 +02:00
if ( ! scene ) return
2024-04-25 17:57:06 +02:00
let defaultFragment ;
2024-02-16 17:36:27 +01:00
scene . traverse ( ( n ) => {
if ( n . userData && n . userData [ '#' ] ) {
2024-04-25 17:57:06 +02:00
if ( n . isXRFRoot ) {
defaultFragment = n . userData [ '#' ]
}
xrf . hashbus . pub ( n . userData [ '#' ] , n ) // evaluate default XR fragments without affecting URL
2024-02-16 17:36:27 +01:00
}
} )
2024-04-25 17:57:06 +02:00
return defaultFragment
2024-02-16 17:36:27 +01:00
}
xrf . frag . loop = 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 ( "loop" , v )
return
}
// otherwise handle global 3D animations
xrf . mixers . map ( ( mixer ) => {
// update loop
mixer . loop . enabled = v . loop
} )
}
2023-08-15 18:27:26 +02:00
xrf . frag . pos = function ( v , opts ) {
let { frag , mesh , model , camera , scene , renderer , THREE } = opts
2023-06-27 09:43:10 +02:00
2025-01-15 10:53:56 +01:00
if ( ! scene ) return
2024-01-29 21:19:04 +01:00
let pos = v
2023-11-28 17:39:33 +01:00
// spec: indirect coordinate using objectname: https://xrfragment.org/#navigating%203D
2024-01-29 21:19:04 +01:00
if ( pos . x == undefined ) {
2023-11-28 17:39:33 +01:00
let obj = scene . getObjectByName ( v . string )
2024-10-13 14:03:31 +02:00
if ( ! obj ) return console . warn ( "#pos=" + v . string + " not found" )
2024-12-10 15:41:07 +01:00
obj . add ( camera ) // follow animation of targeted position
2025-01-15 10:53:56 +01:00
camera . position . set ( 0 , 0 , 0 ) // set playerheight
2024-12-10 15:49:42 +01:00
//let c = camera.rotation
//c.set( c.x, obj.rotation.y, c.z )
2024-10-07 21:02:52 +02:00
2023-11-28 17:39:33 +01:00
} else {
// spec: direct coordinate: https://xrfragment.org/#navigating%203D
2024-01-29 21:19:04 +01:00
camera . position . x = pos . x
camera . position . y = pos . y
camera . position . z = pos . z
2023-08-15 18:27:26 +02:00
}
2024-01-29 21:19:04 +01:00
2024-02-16 17:36:27 +01:00
if ( xrf . debug ) console . log ( ` #pos.js: setting camera to position ${ pos . x } , ${ pos . y } , ${ pos . z } ` )
2024-04-25 17:57:06 +02:00
xrf . frag . pos . last = v . string // remember
2024-06-11 19:30:32 +02:00
xrf . frag . pos . lastVector3 = camera . position . clone ( )
2024-01-29 21:19:04 +01:00
2023-12-12 18:09:30 +01:00
camera . updateMatrixWorld ( )
2024-10-13 14:03:31 +02:00
camera . getCam ( ) . updateMatrixWorld ( )
2023-05-12 22:06:21 +02:00
}
2024-02-16 17:36:27 +01:00
2024-04-16 15:19:08 +02:00
xrf . frag . pos . get = function ( precision , randomize ) {
if ( ! precision ) precision = 2 ;
if ( typeof THREE == 'undefined' ) THREE = xrf . THREE
let radToDeg = THREE . MathUtils . radToDeg
let toDeg = ( x ) => x / ( Math . PI / 180 )
let camera = xrf . camera
if ( randomize ) {
camera . position . x += Math . random ( ) / 10
camera . position . z += Math . random ( ) / 10
}
// *TODO* add camera direction
let direction = new xrf . THREE . Vector3 ( )
camera . getWorldDirection ( direction )
const pitch = Math . asin ( direction . y ) ;
const yaw = Math . atan2 ( direction . x , direction . z ) ;
const pitchInDegrees = pitch * 180 / Math . PI ;
const yawInDegrees = yaw * 180 / Math . PI ;
return {
x : String ( camera . position . x . toFixed ( 2 ) ) ,
y : String ( camera . position . y . toFixed ( 2 ) ) ,
z : String ( camera . position . z . toFixed ( 2 ) ) ,
}
}
2024-02-16 17:36:27 +01:00
xrf . addEventListener ( 'reset' , ( opts ) => {
// set the player to position 0,0,0
xrf . camera . position . set ( 0 , 0 , 0 )
} )
2023-05-12 22:06:21 +02:00
xrf . frag . rot = function ( v , opts ) {
2023-08-15 18:27:26 +02:00
let { frag , mesh , model , camera , scene , renderer , THREE } = opts
2024-02-16 17:36:27 +01:00
if ( xrf . debug ) console . log ( "#rot.js: setting camera rotation to " + v . string )
2025-01-15 10:53:56 +01:00
if ( ! camera || ! scene ) return
2023-12-12 17:25:21 +01:00
if ( ! model . isSRC ) {
camera . rotation . set (
v . x * Math . PI / 180 ,
v . y * Math . PI / 180 ,
v . z * Math . PI / 180
)
2023-12-12 18:09:30 +01:00
camera . rotation . offset = camera . rotation . clone ( ) // remember
//camera.updateProjectionMatrix()
2023-12-12 17:25:21 +01:00
} else {
obj = model . scene . isReparented ? model . scene . children [ 0 ] : model . scene
obj . rotation . set (
v . x * Math . PI / 180 ,
v . y * Math . PI / 180 ,
v . z * Math . PI / 180
)
}
2023-05-09 17:42:29 +02:00
}
2024-02-16 17:36:27 +01:00
xrf . frag . s = 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 ( "s" , v )
return
}
// otherwise handle global 3D animations
xrf . mixers . map ( ( mixer ) => {
mixer . s = v
// update speed
mixer . timeScale = v . x || 1.0
mixer . loop . speed = v . x || 1.0
mixer . loop . speedAbs = Math . abs ( v . x )
} )
}
2023-05-12 22:06:21 +02:00
// *TODO* use webgl instancing
xrf . frag . src = function ( v , opts ) {
2023-06-08 17:45:21 +02:00
opts . embedded = v // indicate embedded XR fragment
2023-10-12 17:04:46 +02:00
let { mesh , model , camera , scene , renderer , THREE , hashbus , frag } = opts
2023-07-04 18:15:08 +02:00
2024-02-29 14:36:00 +01:00
if ( mesh . isSRC ) return // only embed src once
2024-07-17 13:07:35 +02:00
opts . isLocal = v . string [ 0 ] == '#'
opts . isPortal = xrf . frag . src . renderAsPortal ( mesh )
opts . isSRC = mesh . isSRC = true
2024-07-12 10:09:28 +02:00
// correct for relative urls
2024-07-17 13:07:35 +02:00
let url = v . string
2024-07-12 10:09:28 +02:00
if ( v . string . charAt ( 0 ) != '#' && xrf . URI . isRelative ( xrf . URI . parse ( v . string ) ) ) {
2024-07-17 13:07:35 +02:00
url = xrf . navigator . URI . URN + v . string
2024-07-12 10:09:28 +02:00
}
2024-07-17 13:07:35 +02:00
url = xrf . frag . src . expandURI ( mesh , url )
2024-07-12 10:09:28 +02:00
2024-04-16 15:19:08 +02:00
let srcFrag = opts . srcFrag = xrfragment . URI . parse ( url ) . XRF
2024-07-12 10:09:28 +02:00
2024-02-16 17:36:27 +01:00
if ( xrf . debug ) console . log ( ` src.js: instancing ${ opts . isLocal ? 'local' : 'remote' } object ${ url } ` )
2023-07-04 18:15:08 +02:00
2023-11-28 17:39:33 +01:00
if ( opts . isLocal ) {
2024-02-29 14:36:00 +01:00
xrf . frag . src . localSRC ( url , srcFrag , opts ) // local
2023-11-28 17:39:33 +01:00
} else xrf . frag . src . externalSRC ( url , srcFrag , opts ) // external file
2024-02-16 17:36:27 +01:00
xrf . hashbus . pub ( url . replace ( /.*#/ , '' ) , mesh ) // eval src-url fragments
}
xrf . frag . src . expandURI = function ( mesh , uri ) {
if ( uri ) mesh . userData . srcTemplate = uri
mesh . userData . src = xrf . URI . template ( mesh . userData . srcTemplate , xrf . URI . vars . _ _object )
return mesh . userData . src
2023-11-28 17:39:33 +01:00
}
2023-10-12 17:04:46 +02:00
2023-11-28 17:39:33 +01:00
xrf . frag . src . addModel = ( model , url , frag , opts ) => {
let { mesh } = opts
let scene = model . scene
2023-12-06 12:55:08 +01:00
scene = xrf . frag . src . filterScene ( scene , { ... opts , frag } ) // get filtered scene
2024-01-29 21:19:04 +01:00
if ( mesh . material && mesh . userData . src ) mesh . material . visible = false // hide placeholder object
2024-02-16 17:36:27 +01:00
if ( opts . isPortal ) {
2023-11-28 17:39:33 +01:00
xrf . portalNonEuclidian ( { ... opts , model , scene : model . scene } )
2024-06-11 19:30:32 +02:00
// only add external objects, because
// local scene-objects are already added to scene
2024-08-01 16:25:05 +02:00
if ( ! opts . isLocal ) xrf . add ( scene )
2023-11-28 17:39:33 +01:00
} else {
xrf . frag . src . scale ( scene , opts , url ) // scale scene
mesh . add ( scene )
2023-07-04 18:15:08 +02:00
}
2024-06-12 10:51:42 +02:00
xrf . frag . src . enableSourcePortation ( { ... opts , scene , mesh , url , model } )
2023-11-28 17:39:33 +01:00
// flag everything isSRC & isXRF
2024-08-01 16:25:05 +02:00
mesh . isXRF = scene . isXRF = true
2023-11-28 17:39:33 +01:00
mesh . traverse ( ( n ) => { n . isSRC = n . isXRF = n [ opts . isLocal ? 'isSRCLocal' : 'isSRCExternal' ] = true } )
2024-06-11 19:30:32 +02:00
2024-02-29 14:36:00 +01:00
xrf . emit ( 'parseModel' , { ... opts , isSRC : true , mesh , model } ) // this will execute all embedded metadata/fragments e.g.
2023-11-28 17:39:33 +01:00
}
2023-07-04 18:15:08 +02:00
2023-11-28 17:39:33 +01:00
xrf . frag . src . renderAsPortal = ( mesh ) => {
// *TODO* should support better isFlat(mesh) check
const isPlane = mesh . geometry && mesh . geometry . attributes . uv && mesh . geometry . attributes . uv . count == 4
return xrf . hasNoMaterial ( mesh ) && isPlane
2023-07-05 16:43:07 +02:00
}
2024-02-29 14:36:00 +01:00
xrf . frag . src . enableSourcePortation = ( opts ) => {
2024-06-12 10:51:42 +02:00
let { scene , mesh , url , model , THREE } = opts
2024-02-29 14:36:00 +01:00
if ( url [ 0 ] == '#' ) return
url = url . replace ( /(&)?[-][\w-+\.]+(&)?/g , '&' ) // remove negative selectors to refer to original scene
if ( ! mesh . userData . href ) {
// show sourceportation clickable sphere for non-portals
let scale = new THREE . Vector3 ( )
let size = new THREE . Vector3 ( )
scene . getWorldScale ( scale )
new THREE . Box3 ( ) . setFromObject ( scene ) . getSize ( size )
const geo = new THREE . SphereGeometry ( Math . max ( size . x , size . y , size . z ) * scale . x * 0.33 , 10 , 10 )
const mat = new THREE . MeshBasicMaterial ( )
mat . visible = false // we just use this for collisions
const sphere = new THREE . Mesh ( geo , mat )
sphere . isXRF = true
// reparent scene to sphere
let children = mesh . children
mesh . children = [ ]
mesh . add ( sphere )
children . map ( ( c ) => sphere . add ( c ) )
// make sphere clickable/hoverable
let frag = { }
xrf . Parser . parse ( "href" , url , frag )
sphere . userData = scene . userData // allow rich href notifications/hovers
2024-03-01 14:35:36 +01:00
sphere . userData . href = url . replace ( /#.*/ , '' ) // remove fragments to refer to original scene
2024-02-29 14:36:00 +01:00
sphere . userData . XRF = frag
xrf . hashbus . pub . fragment ( "href" , { ... opts , mesh : sphere , frag , skipXRWG : true , renderer : xrf . renderer , camera : xrf . camera } )
}
for ( let i in scene . userData ) {
if ( ! mesh . userData [ i ] ) mesh . userData [ i ] = scene . userData [ i ] // allow rich href notifications/hovers
}
2023-11-28 17:39:33 +01:00
}
xrf . frag . src . externalSRC = ( url , frag , opts ) => {
fetch ( url , { method : 'HEAD' } )
. then ( ( res ) => {
let mimetype = res . headers . get ( 'Content-type' )
2024-06-11 19:30:32 +02:00
if ( xrf . debug != undefined ) console . log ( "HEAD " + url + " => " + mimetype )
2024-02-16 17:36:27 +01:00
if ( url . replace ( /#.*/ , '' ) . match ( /\.(gltf|glb)$/ ) ) mimetype = 'gltf'
if ( url . replace ( /#.*/ , '' ) . match ( /\.(frag|fs|glsl)$/ ) ) mimetype = 'x-shader/x-fragment'
if ( url . replace ( /#.*/ , '' ) . match ( /\.(vert|vs)$/ ) ) mimetype = 'x-shader/x-fragment'
2023-11-28 17:39:33 +01:00
//if( url.match(/\.(fbx|stl|obj)$/) ) mimetype =
opts = { ... opts , frag , mimetype }
return xrf . frag . src . type [ mimetype ] ? xrf . frag . src . type [ mimetype ] ( url , opts ) : xrf . frag . src . type . unknown ( url , opts )
} )
. then ( ( model ) => {
if ( model && model . scene ) xrf . frag . src . addModel ( model , url , frag , opts )
} )
. finally ( ( ) => { } )
. catch ( console . error )
return xrf . frag . src
}
xrf . frag . src . localSRC = ( url , frag , opts ) => {
2023-12-06 12:55:08 +01:00
let { model , mesh , scene } = opts
2024-02-16 17:36:27 +01:00
//setTimeout( (mesh,scene) => {
2023-12-06 12:55:08 +01:00
if ( mesh . material ) mesh . material = mesh . material . clone ( ) // clone, so we can individually highlight meshes
let _model = {
animations : model . animations ,
2024-02-16 17:36:27 +01:00
scene : scene . clone ( )
2023-12-06 12:55:08 +01:00
}
2024-08-01 16:25:05 +02:00
_model . scene . isClone = true
2024-02-29 14:36:00 +01:00
_model . scene . traverse ( ( n ) => n . isXRF = true ) // make sure they respond to xrf.reset()
2023-12-06 12:55:08 +01:00
_model . scenes = [ _model . scene ]
xrf . frag . src . addModel ( _model , url , frag , opts ) // current file
2024-02-16 17:36:27 +01:00
//},1000,mesh,scene )
2023-09-15 19:43:11 +02:00
}
// scale embedded XR fragments https://xrfragment.org/#scaling%20of%20instanced%20objects
xrf . frag . src . scale = function ( scene , opts , url ) {
let { mesh , model , camera , renderer , THREE } = opts
2023-11-28 17:39:33 +01:00
// remove invisible objects (hidden by selectors) which might corrupt boundingbox size-detection
let cleanScene = scene . clone ( )
let remove = [ ]
const notVisible = ( n ) => ! n . visible || ( n . material && ! n . material . visible )
cleanScene . traverse ( ( n ) => notVisible ( n ) && n . children . length == 0 && ( remove . push ( n ) ) )
remove . map ( ( n ) => n . removeFromParent ( ) )
2023-09-21 13:05:30 +02:00
let restrictTo3DBoundingBox = mesh . geometry
if ( restrictTo3DBoundingBox ) {
2023-08-04 09:11:26 +02:00
// spec 3 of https://xrfragment.org/#src
// spec 1 of https://xrfragment.org/#scaling%20of%20instanced%20objects
// normalize instanced objectsize to boundingbox
2023-09-21 13:05:30 +02:00
let sizeFrom = new THREE . Vector3 ( )
let sizeTo = new THREE . Vector3 ( )
let empty = new THREE . Object3D ( )
new THREE . Box3 ( ) . setFromObject ( mesh ) . getSize ( sizeTo )
2023-11-28 17:39:33 +01:00
new THREE . Box3 ( ) . setFromObject ( cleanScene ) . getSize ( sizeFrom )
2023-09-21 13:05:30 +02:00
let ratio = sizeFrom . divide ( sizeTo )
scene . scale . multiplyScalar ( 1.0 / Math . max ( ratio . x , ratio . y , ratio . z ) ) ;
2023-08-04 09:11:26 +02:00
} else {
// spec 4 of https://xrfragment.org/#src
2023-08-08 12:40:38 +02:00
// spec 2 of https://xrfragment.org/#scaling%20of%20instanced%20objects
2023-08-04 09:11:26 +02:00
scene . scale . multiply ( mesh . scale )
2023-07-05 16:43:07 +02:00
}
2023-09-21 13:05:30 +02:00
}
xrf . frag . src . filterScene = ( scene , opts ) => {
let { mesh , model , camera , renderer , THREE , hashbus , frag } = opts
2024-02-29 14:36:00 +01:00
scene = xrf . filter . scene ( { scene , frag , reparent : true } ) //,copyScene: opts.isPortal})
2023-12-06 12:55:08 +01:00
if ( ! opts . isLocal ) {
scene . traverse ( ( m ) => {
if ( m . userData && ( m . userData . src || m . userData . href ) ) return ; // prevent infinite recursion
2024-02-29 14:36:00 +01:00
xrf . parseModel . metadataInMesh ( m , { scene , recursive : true } )
2023-12-06 12:55:08 +01:00
} )
}
2023-11-28 17:39:33 +01:00
return scene
2023-07-05 16:43:07 +02:00
}
2023-07-06 16:54:12 +02:00
/ *
* replace the src - mesh with the contents of the src
* /
2023-07-05 16:43:07 +02:00
xrf . frag . src . type = { }
/ *
* mimetype : unknown
* /
xrf . frag . src . type [ 'unknown' ] = function ( url , opts ) {
return new Promise ( ( resolve , reject ) => {
2023-11-03 21:22:05 +01:00
reject ( ` ${ url } mimetype ' ${ opts . mimetype } ' not found or supported (yet) ` )
2023-07-05 16:43:07 +02:00
} )
}
2024-02-16 17:36:27 +01:00
// this ns the global #t mediafragment handler (which affects the 3D animation)
2023-10-16 16:58:56 +02:00
xrf . frag . t = function ( v , opts ) {
let { frag , mesh , model , camera , scene , renderer , THREE } = opts
2024-02-16 17:36:27 +01:00
// handle object media players
if ( mesh && mesh . media ) {
for ( let i in mesh . media ) mesh . media [ i ] . set ( "t" , v )
return
}
// otherwise handle global 3D animations
2023-11-03 21:22:05 +01:00
if ( ! model . mixer ) return
2023-12-07 22:21:45 +01:00
if ( ! model . animations || model . animations [ 0 ] == undefined ) {
console . warn ( 'no animations found in model' )
return xrf . emit ( v . x == 0 ? 'stop' : 'play' , { isPlaying : v . x != 0 } )
}
2024-02-16 17:36:27 +01:00
2023-11-03 21:22:05 +01:00
xrf . mixers . map ( ( mixer ) => {
2023-11-28 17:39:33 +01:00
2023-11-03 21:22:05 +01:00
mixer . t = v
// update speed
2024-02-16 17:36:27 +01:00
mixer . timeScale = mixer . loop . speed
mixer . loop . speedAbs = Math . abs ( mixer . timeScale )
2023-11-03 21:22:05 +01:00
2024-02-16 17:36:27 +01:00
mixer . updateLoop ( v )
2023-11-03 21:22:05 +01:00
// play animations
mixer . play ( v )
} )
}
xrf . frag . t . default = {
x : 0 , // (play from) offset (in seconds)
y : 0 // optional: (stop at) offset (in seconds)
}
// setup animation mixer for global scene & src scenes
xrf . addEventListener ( 'parseModel' , ( opts ) => {
let { model } = opts
let mixer = model . mixer = new xrf . THREE . AnimationMixer ( model . scene )
2024-07-09 19:27:21 +02:00
2023-11-03 21:22:05 +01:00
mixer . model = model
2024-07-19 18:10:32 +02:00
mixer . loop = { timeStart : 0 , timeStop : 0 , speed : 1.0 , enabled : false }
2023-11-03 21:22:05 +01:00
mixer . i = xrf . mixers . length
2023-11-28 17:39:33 +01:00
mixer . actions = [ ]
2024-07-09 19:27:21 +02:00
// calculate total duration/frame based on longest animation
mixer . duration = 0
if ( model . animations . length ) {
model . animations . map ( ( a ) => mixer . duration = ( a . duration > mixer . duration ) ? a . duration : mixer . duration )
}
2023-11-03 21:22:05 +01:00
2024-12-10 15:41:07 +01:00
model . animations . map ( ( anim ) => {
console . log ( "animation action: " + anim . name )
2023-11-28 17:39:33 +01:00
mixer . actions . push ( mixer . clipAction ( anim , model . scene ) )
2023-11-03 21:22:05 +01:00
} )
mixer . play = ( t ) => {
2024-12-10 15:41:07 +01:00
let msg = ` media fragment: ${ t . x } - ${ t . y } seconds `
if ( t . x > 49 ) msg += ", not frames (!)"
console . log ( msg )
2024-02-16 17:36:27 +01:00
mixer . isPlaying = t . x !== undefined && t . x != t . y
2023-11-03 21:22:05 +01:00
mixer . updateLoop ( t )
xrf . emit ( mixer . isPlaying === false ? 'stop' : 'play' , { isPlaying : mixer . isPlaying } )
}
mixer . stop = ( ) => {
mixer . play ( false )
}
mixer . updateLoop = ( t ) => {
2024-02-16 17:36:27 +01:00
if ( t ) {
mixer . loop . timeStart = t . x != undefined ? t . x : mixer . loop . timeStart
2024-07-09 19:27:21 +02:00
mixer . loop . timeStop = t . y != undefined ? t . y : mixer . loop . timeStop
2024-02-16 17:36:27 +01:00
}
2023-11-28 17:39:33 +01:00
mixer . actions . map ( ( action ) => {
2023-11-03 21:22:05 +01:00
if ( mixer . loop . timeStart != undefined ) {
2023-11-28 17:39:33 +01:00
action . time = mixer . loop . timeStart
2023-12-06 15:20:58 +01:00
action . setLoop ( xrf . THREE . LoopOnce , )
2023-11-28 17:39:33 +01:00
action . timeScale = mixer . timeScale
action . enabled = true
2024-02-16 17:36:27 +01:00
if ( t && t . x != undefined ) action . play ( )
2023-11-03 21:22:05 +01:00
}
} )
mixer . setTime ( mixer . loop . timeStart )
mixer . time = Math . abs ( mixer . loop . timeStart )
mixer . update ( 0 )
}
2023-10-16 16:58:56 +02:00
2024-02-16 17:36:27 +01:00
// monkeypatch: update loop when needed
2023-10-16 16:58:56 +02:00
if ( ! mixer . update . patched ) {
2024-02-16 17:36:27 +01:00
2023-10-16 16:58:56 +02:00
let update = mixer . update
mixer . update = function ( time ) {
mixer . time = Math . abs ( mixer . time )
2023-11-03 21:22:05 +01:00
if ( time == 0 ) return update . call ( this , time )
// loop jump
2024-02-16 17:36:27 +01:00
if ( mixer . loop . timeStop > 0 && mixer . time > mixer . loop . timeStop ) {
if ( mixer . loop . enabled ) {
setTimeout ( ( ) => mixer . updateLoop ( ) , 0 ) // prevent recursion
2024-07-19 18:10:32 +02:00
} else {
mixer . setTime ( mixer . time = mixer . loop . timeStop )
//mixer.update(mixer.time )
mixer . timeScale = 0
}
2023-10-16 16:58:56 +02:00
}
2023-11-03 21:22:05 +01:00
return update . call ( this , time )
2023-10-16 16:58:56 +02:00
}
mixer . update . patched = true
}
2023-11-03 21:22:05 +01:00
xrf . mixers . push ( mixer )
} )
if ( document . location . hash . match ( /t=/ ) ) {
let url = document . location . href
let playAfterUserGesture = ( ) => {
xrf . hashbus . pub ( url ) // re-post t fragment on the hashbus again
window . removeEventListener ( 'click' , playAfterUserGesture )
window . removeEventListener ( 'touchstart' , playAfterUserGesture )
}
window . addEventListener ( 'click' , playAfterUserGesture )
window . addEventListener ( 'touchstart' , playAfterUserGesture )
2023-10-16 16:58:56 +02:00
}
2023-11-03 21:22:05 +01:00
xrf . addEventListener ( 'render' , ( opts ) => {
let model = xrf . model
let { time } = opts
if ( ! model ) return
if ( xrf . mixers . length ) {
2023-11-28 17:39:33 +01:00
xrf . mixers . map ( ( m ) => m . isPlaying && ( m . update ( time ) ) )
2023-11-03 21:22:05 +01:00
// update active camera in case selected by dynamicKey in URI
2024-02-16 17:36:27 +01:00
if ( xrf . model . camera && xrf . model . camera . length && model . mixer . isPlaying ) {
2023-11-03 21:22:05 +01:00
let cam = xrf . camera . getCam ( )
// cam.fov = model.cameras[0].fov (why is blender not exporting radians?)
cam . far = model . cameras [ 0 ] . far
cam . near = model . cameras [ 0 ] . near
let rig = xrf . camera
rig . position . copy ( model . cameras [ 0 ] . position )
rig . position . y -= rig . offsetY // VR/AR compensate camera rig
//rig.rotation.copy( model.cameras[0].rotation )
rig . updateProjectionMatrix ( )
}
}
} )
2024-02-16 17:36:27 +01:00
// remove mixers and stop mixers when loading another scene
xrf . addEventListener ( 'reset' , ( opts ) => {
xrf . mixers . map ( ( m ) => m . stop ( ) )
xrf . mixers = [ ]
2023-11-03 21:22:05 +01:00
} )
2024-02-16 17:36:27 +01:00
xrf . frag . uv = function ( v , opts ) {
let { frag , mesh , model , camera , scene , renderer , THREE } = opts
if ( ! mesh . geometry ) return // nothing to do here
if ( v . floats . length != 4 ) return console . warn ( 'xrfragment.js: got less than 4 uv values ' )
xrf . frag . uv . init ( mesh )
mesh . uv . u = v . floats [ 0 ]
mesh . uv . v = v . floats [ 1 ]
mesh . uv . uspeed = v . floats [ 2 ] || 1.0
mesh . uv . vspeed = v . floats [ 3 ] || 1.0
mesh . uv . ushift = v . shift [ 0 ] || v . floats [ 0 ] < 0 // negative u is always relative
mesh . uv . vshift = v . shift [ 1 ] || v . floats [ 1 ] < 0 // negative v is always relative
mesh . uv . uloop = v . shift [ 2 ] || false
mesh . uv . vloop = v . shift [ 3 ] || false
mesh . onBeforeRender = xrf . frag . uv . scroll
}
xrf . frag . uv . init = function ( mesh ) {
if ( ! mesh . uv ) mesh . uv = { u : 0 , v : 0 , uspeed : 1 , vspeed : 1 , uloop : false , vloop : false , uv : false }
let uv = mesh . geometry . getAttribute ( "uv" )
if ( ! uv . old ) uv . old = mesh . geometry . getAttribute ( "uv" ) . clone ( )
}
xrf . frag . uv . scroll = function ( ) {
let diffU = 0.0 // distance to end-state (non-looping mode)
let diffV = 0.0 // distance to end-state (non-looping mode)
let uv = this . geometry . getAttribute ( "uv" )
// translate!
for ( let i = 0 ; i < uv . count ; i ++ ) {
if ( this . uv . uspeed == 1.0 ) uv . setX ( i , this . uv . ushift ? uv . getX ( i ) + this . uv . u : uv . old . getX ( i ) + this . uv . u )
if ( this . uv . vspeed == 1.0 ) uv . setY ( i , this . uv . vshift ? uv . getY ( i ) + this . uv . v : uv . old . getY ( i ) + this . uv . v )
if ( this . uv . uspeed != 1.0 || this . uv . vspeed != 1.0 ) {
let u = uv . getX ( i )
let v = uv . getY ( i )
let uTarget = this . uv . ushift ? uv . getX ( i ) + this . uv . u : uv . old . getX ( i ) + this . uv . u
let vTarget = this . uv . vshift ? uv . getY ( i ) + this . uv . v : uv . old . getY ( i ) + this . uv . v
// scroll U
if ( this . uv . uloop ) {
u += this . uv . uspeed * xrf . clock . delta
} else {
// *TODO* tween to offset
//// recover from super-high uv-values due to looped scrolling
//if( Math.abs(u-uTarget) > 10.0 ) u = uv.old.getX(i)
//u = u > uTarget ? u + (this.uv.uspeed * -xrf.clock.delta)
// : u + (this.uv.uspeed * xrf.clock.delta)
//diffU += Math.abs( u - uTarget ) // are we done yet? (non-looping mode)
}
// scroll V
if ( this . uv . vloop ) {
v += this . uv . vspeed * xrf . clock . delta
} else {
// *TODO* tween to offset
//// recover from super-high uv-values due to looped scrolling
//// recover from super-high uv-values due to looped scrolling
//if( Math.abs(v-vTarget) > 10.0 ) v = uv.old.getY(i)
//v = v > vTarget ? v + (this.uv.vspeed * -xrf.clock.delta)
// : v + (this.uv.vspeed * xrf.clock.delta)
//diffV += Math.abs( v - vTarget )
}
uv . setXY ( i , u , v )
}
}
uv . needsUpdate = true
if ( ( ! this . uv . uloop && diffU < 0.05 ) &&
( ! this . uv . vloop && diffV < 0.05 )
) { // stop animating if done
this . onBeforeRender = function ( ) { }
}
}
2023-11-28 17:39:33 +01:00
xrf . getCollisionMeshes = ( ) => {
let meshes = [ ]
xrf . scene . traverse ( ( n ) => {
2024-02-16 17:36:27 +01:00
if ( n . type == 'Mesh' && ! n . userData . href && ! n . userData . src && xrf . hasNoMaterial ( n ) ) {
2023-11-28 17:39:33 +01:00
meshes . push ( n )
}
} )
return meshes
}
2024-03-19 10:53:22 +01:00
// wrapper to collect interactive raycastable objects
2023-11-28 17:39:33 +01:00
xrf . interactiveGroup = function ( THREE , renderer , camera ) {
let {
Group ,
Matrix4 ,
Raycaster ,
Vector2
} = THREE
const _pointer = new Vector2 ( ) ;
const _event = { type : '' , data : _pointer } ;
let object = { selected : false }
class interactive extends Group {
constructor ( renderer , camera ) {
super ( ) ;
if ( ! renderer || ! camera ) return
// extract camera when camera-rig is passed
camera . traverse ( ( n ) => String ( n . type ) . match ( /Camera/ ) ? camera = n : null )
const scope = this ;
scope . objects = [ ]
2024-03-19 10:53:22 +01:00
scope . raycastAll = false
2023-11-28 17:39:33 +01:00
2024-06-11 19:30:32 +02:00
const raycaster = this . raycaster = new Raycaster ( ) ;
2023-11-28 17:39:33 +01:00
const tempMatrix = new Matrix4 ( ) ;
// Pointer Events
const element = renderer . domElement ;
2024-03-19 10:53:22 +01:00
const getAllMeshes = ( scene ) => {
let objects = [ ]
xrf . scene . traverse ( ( n ) => {
if ( ! n . material || n . type != 'Mesh' ) return
objects . push ( n )
} )
return objects
}
2023-11-28 17:39:33 +01:00
function onPointerEvent ( event ) {
//event.stopPropagation();
const rect = renderer . domElement . getBoundingClientRect ( ) ;
_pointer . x = ( event . clientX - rect . left ) / rect . width * 2 - 1 ;
_pointer . y = - ( event . clientY - rect . top ) / rect . height * 2 + 1 ;
raycaster . setFromCamera ( _pointer , camera ) ;
2024-03-19 10:53:22 +01:00
let objects = scope . raycastAll ? getAllMeshes ( xrf . scene ) : scope . objects
const intersects = raycaster . intersectObjects ( objects , false )
2023-11-28 17:39:33 +01:00
if ( intersects . length > 0 ) {
const intersection = intersects [ 0 ] ;
object = intersection . object ;
const uv = intersection . uv ;
_event . type = event . type ;
2024-03-19 10:53:22 +01:00
if ( uv ) _event . data . set ( uv . x , 1 - uv . y ) ;
2023-11-28 17:39:33 +01:00
object . dispatchEvent ( _event ) ;
} else {
if ( object . selected ) {
_event . type = 'mouseleave'
2024-02-16 17:36:27 +01:00
object . dispatchEvent ( _event )
2023-11-28 17:39:33 +01:00
}
}
}
element . addEventListener ( 'pointerdown' , onPointerEvent ) ;
element . addEventListener ( 'pointerup' , onPointerEvent ) ;
element . addEventListener ( 'pointermove' , onPointerEvent ) ;
element . addEventListener ( 'mousedown' , onPointerEvent ) ;
element . addEventListener ( 'mousemove' , onPointerEvent ) ;
element . addEventListener ( 'click' , onPointerEvent ) ;
element . addEventListener ( 'mouseup' , onPointerEvent ) ;
2024-06-15 17:33:08 +02:00
element . addEventListener ( 'touchstart' , onPointerEvent ) ;
2023-11-28 17:39:33 +01:00
// WebXR Controller Events
// TODO: Dispatch pointerevents too
2024-02-16 17:36:27 +01:00
const eventsMapper = {
2023-11-28 17:39:33 +01:00
'move' : 'mousemove' ,
'select' : 'click' ,
2024-06-15 17:33:08 +02:00
'touchstart' : 'click' ,
2023-11-28 17:39:33 +01:00
'selectstart' : 'mousedown' ,
'selectend' : 'mouseup'
} ;
function onXRControllerEvent ( event ) {
const controller = event . target ;
tempMatrix . identity ( ) . extractRotation ( controller . matrixWorld ) ;
raycaster . ray . origin . setFromMatrixPosition ( controller . matrixWorld ) ;
raycaster . ray . direction . set ( 0 , 0 , - 1 ) . applyMatrix4 ( tempMatrix ) ;
2024-06-11 19:30:32 +02:00
raycaster . far = Infinity
2023-11-28 17:39:33 +01:00
2024-03-19 10:53:22 +01:00
let objects = scope . raycastAll ? getAllMeshes ( xrf . scene ) : scope . objects
const intersects = raycaster . intersectObjects ( objects , false )
2023-11-28 17:39:33 +01:00
2024-03-19 10:53:22 +01:00
if ( intersects . length > 0 ) {
2023-11-28 17:39:33 +01:00
2024-02-16 17:36:27 +01:00
2024-03-19 10:53:22 +01:00
const intersection = intersects [ 0 ] ;
2023-11-28 17:39:33 +01:00
object = intersection . object ;
const uv = intersection . uv ;
2024-02-16 17:36:27 +01:00
_event . type = eventsMapper [ event . type ] ;
2024-06-04 19:00:48 +02:00
console . log ( ( new Date ( ) ) . getTime ( ) + " " + event . type + ":" + _event . type + " " + object . name )
2024-03-19 10:53:22 +01:00
if ( uv ) _event . data . set ( uv . x , 1 - uv . y ) ;
2023-11-28 17:39:33 +01:00
object . dispatchEvent ( _event ) ;
} else {
if ( object . selected ) {
_event . type = 'mouseleave'
object . dispatchEvent ( _event )
}
}
}
const controller1 = renderer . xr . getController ( 0 ) ;
controller1 . addEventListener ( 'move' , onXRControllerEvent ) ;
controller1 . addEventListener ( 'select' , onXRControllerEvent ) ;
controller1 . addEventListener ( 'selectstart' , onXRControllerEvent ) ;
controller1 . addEventListener ( 'selectend' , onXRControllerEvent ) ;
const controller2 = renderer . xr . getController ( 1 ) ;
controller2 . addEventListener ( 'move' , onXRControllerEvent ) ;
controller2 . addEventListener ( 'select' , onXRControllerEvent ) ;
controller2 . addEventListener ( 'selectstart' , onXRControllerEvent ) ;
controller2 . addEventListener ( 'selectend' , onXRControllerEvent ) ;
}
2024-06-11 19:30:32 +02:00
intersect ( obj , far ) {
//const mesh2Box = (mesh) => {
// let b = new THREE.Box3()
// b.expandByObject(mesh)
// return b
//}
//const objBox = obj.box || (obj.box = mesh2Box(obj))
//let objects = this.raycastAll ? getAllMeshes(xrf.scene) : this.objects
//let intersects = []
//objects.map( (objB) => {
// if( !objB.box ) objB.box = mesh2Box(objB)
// if( objB.box.intersectsBox(objBox) ) intersects.push(obj.box)
//})
//return intersects
this . raycaster . ray . origin . setFromMatrixPosition ( obj . matrixWorld ) ;
this . raycaster . ray . direction . set ( 0 , 0 , - 1 )
this . raycaster . far = far || Infinity
return this . raycaster . intersectObjects ( this . objects , true )
}
2024-03-19 10:53:22 +01:00
// we create our own add to avoid unnecessary unparenting of buffergeometries from
// their 3D model (which breaks animations)
2023-11-28 17:39:33 +01:00
add ( obj , unparent ) {
if ( unparent ) Group . prototype . add . call ( this , obj )
this . objects . push ( obj )
}
2024-06-04 19:00:48 +02:00
clear ( ) {
while ( this . children [ 0 ] != undefined ) this . children [ 0 ] . remove ( )
this . objects = [ ]
}
2023-11-28 17:39:33 +01:00
}
return new interactive ( renderer , camera )
}
2024-02-16 17:36:27 +01:00
xrf . optimize = ( opts ) => {
opts . animatedObjects = [ ]
xrf . optimize
. checkAnimations ( opts )
. freezeUnAnimatedObjects ( opts )
. disableShadows ( opts )
. disableEmbeddedLights ( opts )
. removeDuplicateLights ( )
}
2023-11-28 17:39:33 +01:00
2024-02-16 17:36:27 +01:00
// check unused animations
xrf . optimize . checkAnimations = ( opts ) => {
if ( xrf . debug ) console . log ( "TODO: fix freezeUnAnimatedObjects for SRC's" )
return xrf . optimize
let { model } = opts
model . animations . map ( ( anim ) => {
// collect zombie animations and warn user
let zombies = anim . tracks . map ( ( t ) => {
let name = t . name . replace ( /\..*/ , '' )
let obj = model . scene . getObjectByName ( name )
if ( ! model . scene . getObjectByName ( name ) ) return { anim : anim . name , obj : name }
else opts . animatedObjects . push ( name )
return undefined
} )
if ( zombies . length > 0 ) { // only warn for zombies in main scene (because src-scenes might be filtered anyways)
zombies
. filter ( ( z ) => z ) // filter out undefined
. map ( ( z ) => console . warn ( ` gltf: object ' ${ z . obj } ' not found (anim: ' ${ z . anim } ' ` ) )
console . warn ( ` TIP: remove dots in objectnames in blender (which adds dots when duplicating) ` )
}
} )
return xrf . optimize
}
xrf . optimize . freezeUnAnimatedObjects = ( opts ) => {
if ( xrf . todo ) console . log ( "TODO: fix freezeUnAnimatedObjects for SRC's" )
return xrf . optimize
let { model } = opts
let scene = model . scene
// increase performance by freezing all objects
scene . traverse ( ( n ) => n . matrixAutoUpdate = false )
// except animated objects and children
scene . traverse ( ( n ) => {
if ( ~ opts . animatedObjects . indexOf ( n . name ) ) {
n . matrixAutoUpdate = true
n . traverse ( ( m ) => m . matrixAutoUpdate = true )
}
} )
return xrf . optimize
}
xrf . optimize . disableShadows = ( opts ) => {
opts . model . scene . traverse ( ( n ) => {
if ( n . castShadow !== undefined ) n . castShadow = false
} )
return xrf . optimize
}
xrf . optimize . disableEmbeddedLights = ( opts ) => {
if ( ! opts . isSRC ) return xrf . optimize
// remove lights from SRC's
opts . model . scene . traverse ( ( n ) => {
if ( n . type . match ( /light/i ) ) n . remove ( )
} )
return xrf . optimize
}
xrf . optimize . removeDuplicateLights = ( ) => {
// local/extern src's can cause duplicate lights which tax performance
let lights = { }
xrf . scene . traverse ( ( n ) => {
if ( n . type . match ( /light/i ) ) {
if ( ! lights [ n . name ] ) lights [ n . name ] = true
else n . remove ( )
}
} )
return xrf . optimize
}
xrf . addEventListener ( 'parseModel' , ( opts ) => {
xrf . optimize ( opts )
} )
2024-04-25 17:57:06 +02:00
xrf . sceneToTranscript = ( scene , ignoreMesh ) => {
let transcript = ''
scene . traverse ( ( n ) => {
let isSRC = false
n . traverseAncestors ( ( m ) => m . userData . src ? isSRC = true : false )
if ( ! isSRC && n . userData [ 'aria-description' ] && ( ! ignoreMesh || n . uuid != ignoreMesh . uuid ) ) {
transcript += ` <b># ${ n . name } </b> ${ n . userData [ 'aria-description' ] } . `
}
} )
return transcript
}
2024-02-16 17:36:27 +01:00
// switch camera when multiple cameras for url #mycameraname
2023-11-28 17:39:33 +01:00
2024-07-12 18:19:19 +02:00
xrf . addEventListener ( 'navigateLoaded' , ( opts ) => {
// select active camera if any
2024-07-17 13:07:35 +02:00
let { id , match , v , THREE } = opts
2024-07-12 18:19:19 +02:00
let envmap = { }
let current = ''
// Recursive function to traverse the graph
function traverseAndSetEnvMap ( node , closestAncestorMaterialMap = null ) {
// Check if the current node has a material
2024-10-14 11:54:39 +02:00
if ( node . isMesh && node . material ) {
2024-07-12 18:19:19 +02:00
if ( node . material . map && closestAncestorMaterialMap ) {
// If the node has a material map, set the closest ancestor material map
2024-10-14 11:54:39 +02:00
node . material = node . material . clone ( ) // dont affect objects which share same material
2024-07-12 18:19:19 +02:00
node . material . envMap = closestAncestorMaterialMap ;
}
}
// Update the closest ancestor's material map
if ( node . isMesh && node . material && node . material . map ) {
closestAncestorMaterialMap = node . material . map . clone ( ) ;
2024-07-17 13:07:35 +02:00
closestAncestorMaterialMap . mapping = xrf . THREE . EquirectangularReflectionMapping ;
2024-07-12 18:19:19 +02:00
closestAncestorMaterialMap . needsUpdate = true
}
// Recursively traverse all children
node . children . forEach ( child => traverseAndSetEnvMap ( child , closestAncestorMaterialMap ) ) ;
}
// Start traversal from the root node
traverseAndSetEnvMap ( xrf . scene ) ;
} )
2024-02-16 17:36:27 +01:00
const doFilter = ( opts ) => {
2023-11-28 17:39:33 +01:00
let { scene , id , match , v } = opts
if ( v . filter ) {
let frags = { }
frags [ v . filter . key ] = v
xrf . filter . scene ( { frag : frags , scene } )
}
2024-02-16 17:36:27 +01:00
}
xrf . addEventListener ( 'dynamicKey' , doFilter )
xrf . addEventListener ( 'dynamicKeyValue' , ( opts ) => {
if ( xrf . debug ) console . log ( "*TODO* filter integers only" )
// doFilter(opts)
2023-11-28 17:39:33 +01:00
} )
// spec: https://xrfragment.org/#filters
xrf . filter = function ( query , cb ) {
let result = [ ]
if ( ! query ) return result
if ( query [ 0 ] != '#' ) query = '#' + query
// *TODO* jquery like utility func
return result
}
xrf . filter . scene = function ( opts ) {
let { scene , frag } = opts
2023-12-06 12:55:08 +01:00
scene = xrf . filter
2023-11-28 17:39:33 +01:00
. sort ( frag ) // get (sorted) filters from XR Fragments
. process ( frag , scene , opts ) // show/hide things
2025-01-14 15:36:36 +01:00
if ( ! scene ) return
2023-11-28 17:39:33 +01:00
scene . visible = true // always enable scene
return scene
}
xrf . filter . sort = function ( frag ) {
// get all filters from XR Fragments
frag . filters = Object . values ( frag )
. filter ( ( v ) => v . filter ? v : null )
. sort ( ( a , b ) => a . index > b . index )
return xrf . filter
}
2023-12-06 12:55:08 +01:00
// opts = {copyScene:true} in case you want a copy of the scene (not filter the current scene inplace)
2023-11-28 17:39:33 +01:00
xrf . filter . process = function ( frag , scene , opts ) {
2024-12-16 13:49:18 +01:00
if ( ! scene || scene . children . length == 0 ) return
2023-11-28 17:39:33 +01:00
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
const hasNameOrTag = ( m , name _or _tag , filter ) => hasName ( m , name _or _tag ) ||
String ( m . userData [ 'tag' ] ) . match ( new RegExp ( "(^| )" + name _or _tag ) )
2023-12-06 12:55:08 +01:00
2023-11-28 17:39:33 +01:00
// utility functions
const getOrCloneMaterial = ( o ) => {
if ( o . material ) {
if ( o . material . isXRF ) return o . material
o . material = o . material . clone ( )
o . material . isXRF = true
return o . material
}
return { }
}
const setVisible = ( n , visible , filter , processed ) => {
if ( processed && processed [ n . uuid ] ) return
getOrCloneMaterial ( n ) . visible = visible
if ( filter . deep ) n . traverse ( ( m ) => getOrCloneMaterial ( m ) . visible = visible )
if ( processed ) processed [ n . uuid ] == true
}
2024-06-04 19:00:48 +02:00
// spec 3 @ https://xrfragment.org/doc/RFC_XR_Macros.html#embedding-xr-content-using-src
2023-11-28 17:39:33 +01:00
// reparent scene based on objectname in case it matches a (non-negating) selector
if ( opts . reparent && firstFilter && ! firstFilter . value && firstFilter . show === true ) {
let obj
frag . target = firstFilter
scene . traverse ( ( n ) => hasName ( n , firstFilter . key , firstFilter ) && ( obj = n ) )
2024-01-30 10:58:00 +01:00
if ( xrf . debug ) console . log ( "reparent " + firstFilter . key + " " + ( ( opts . copyScene ) ? "copy" : "inplace" ) )
2023-12-06 12:55:08 +01:00
if ( obj ) {
2023-11-28 17:39:33 +01:00
obj . position . set ( 0 , 0 , 0 )
2023-12-12 17:25:21 +01:00
if ( opts . copyScene ) {
2023-12-06 12:55:08 +01:00
opts . copyScene = new xrf . THREE . Scene ( )
2023-12-12 17:25:21 +01:00
opts . copyScene . children [ 0 ] = obj // we dont use .add() as that reparents it from the original scene
2023-12-06 12:55:08 +01:00
} else {
// empty current scene and add obj
while ( scene . children . length > 0 ) scene . children [ 0 ] . removeFromParent ( )
scene . add ( obj )
}
2023-12-12 17:25:21 +01:00
} else {
console . warn ( "could not reparent scene to object " + firstFilter . key + " (not found)" )
opts . copyScene = new xrf . THREE . Scene ( ) // return empty scene
}
if ( opts . copyScene ) scene = opts . copyScene
if ( obj ) obj . isReparented = true
2023-11-28 17:39:33 +01:00
}
// then show/hide things based on secondary selectors
// we don't use the XRWG (everything) because we process only the given (sub)scene
frag . filters . map ( ( v ) => {
const filter = v . filter . get ( )
const name _or _tag = cleanupKey ( v . fragment )
let processed = { }
let extembeds = { }
2024-02-29 14:36:00 +01:00
// hide external objects temporarely (prevent them getting filtered too)
2023-11-28 17:39:33 +01:00
scene . traverse ( ( m ) => {
if ( m . isSRCExternal ) {
2023-12-06 12:55:08 +01:00
m . traverse ( ( n ) => ( extembeds [ n . uuid ] = m ) && ( m . visible = false ) )
2023-11-28 17:39:33 +01:00
}
} )
scene . traverseVisible ( ( m ) => {
// filter on value(expression) #foo=>3 e.g. *TODO* do this in XRWG
if ( filter . value && m . userData [ filter . key ] ) {
const visible = v . filter . testProperty ( filter . key , m . userData [ filter . key ] , filter . show === false )
setVisible ( m , visible , filter , processed )
return
}
if ( hasNameOrTag ( m , name _or _tag , filter ) ) {
setVisible ( m , filter . show , filter )
}
} )
// show external objects again
for ( let i in extembeds ) extembeds [ i ] . visible = true
} )
2023-12-06 12:55:08 +01:00
return scene
2023-11-28 17:39:33 +01:00
}
2024-02-16 17:36:27 +01:00
xrf . frag . dynamic . material = function ( v , opts ) {
let { match } = opts
// update material in case of <tag_or_object>[*]=<materialname>
let material
xrf . scene . traverse ( ( n ) => n . material && ( n . material . name == v . string ) && ( material = n . material ) )
if ( ! material && ! v . reset ) return // nothing to do
xrf . XRWG . deepApplyMatch ( match , v , ( match , v , node , type ) => {
if ( node . material ) xrf . frag . dynamic . material . set ( node , material , v . reset )
2023-11-03 21:22:05 +01:00
} )
}
2024-02-16 17:36:27 +01:00
xrf . frag . dynamic . material . set = function ( mesh , material , reset ) {
if ( ! mesh . materialOriginal ) mesh . materialOriginal = mesh . material
let visible = mesh . material . visible //remember
if ( reset ) {
mesh . material = mesh . materialOriginal
} else mesh . material = material
mesh . material . visible = visible
}
// for reset calls like href: xrf://!myobject e.g.
xrf . addEventListener ( 'dynamicKey' , ( opts ) => {
let { v , match } = opts
if ( v . reset ) {
xrf . XRWG . deepApplyMatch ( match , v , ( match , v , node , type ) => {
if ( node . material ) xrf . frag . dynamic . material . set ( node , null , v . reset )
} )
}
} )
// this holds all the URI Template variables (https://www.rfc-editor.org/rfc/rfc6570)
xrf . addEventListener ( 'parseModel' , ( opts ) => {
let { model , url , file } = opts
if ( model . isSRC || opts . isSRC ) return // ignore SRC models
xrf . URI . vars = new Proxy ( { } , {
set ( me , k , v ) {
2024-06-12 10:51:42 +02:00
if ( k . match ( /^(name)$/ ) ) return true
2024-02-16 17:36:27 +01:00
me [ k ] = v
2024-06-12 10:51:42 +02:00
return true
2024-02-16 17:36:27 +01:00
} ,
get ( me , k ) {
if ( k == '__object' ) {
let obj = { }
for ( let i in xrf . URI . vars ) obj [ i ] = xrf . URI . vars [ i ] ( )
return obj
2023-11-03 21:22:05 +01:00
}
2024-02-16 17:36:27 +01:00
return me [ k ]
} ,
} )
2023-11-03 21:22:05 +01:00
2024-02-16 17:36:27 +01:00
model . scene . traverse ( ( n ) => {
const variables = /{([a-zA-Z0-9-]+)}/g
if ( n . userData ) {
for ( let i in n . userData ) {
2024-07-09 19:27:21 +02:00
//if( i[0] == '#' || i.match(/^(href|tag)$/) ) continue // ignore XR Fragment aliases
if ( i . match ( /^(src|href|tag)/ ) ) {
2024-02-16 17:36:27 +01:00
// lets declare empty variables found in src-values ('https://foo.com/video.mp4#{somevar}') e.g.
if ( n . userData [ i ] . match ( variables ) ) {
let vars = [ ] . concat ( n . userData [ i ] . match ( variables ) )
const strip = ( v ) => v . replace ( /[{}]/g , '' )
vars . map ( ( v ) => xrf . URI . vars [ strip ( v ) ] = ( ) => '' )
2023-11-03 21:22:05 +01:00
}
2024-02-16 17:36:27 +01:00
} else xrf . URI . vars [ i ] = ( ) => n . userData [ i ] // declare variables with values
2023-11-03 21:22:05 +01:00
}
}
} )
2024-02-16 17:36:27 +01:00
} )
xrf . addEventListener ( 'dynamicKeyValue' , ( opts ) => {
// select active camera if any
let { id , match , v } = opts
if ( ! v . is ( xrf . XRF . CUSTOMFRAG ) ) return // only process custom frags from here
if ( v . string . match ( /(<|>)/ ) ) return // ignore filter values
if ( match . length > 0 ) {
xrf . frag . dynamic . material ( v , opts ) // check if fragment is an objectname
}
2024-10-13 14:08:45 +02:00
if ( ! xrf . URI . vars [ v . string ] ) return // ignore non-template URI fragments
2024-02-16 17:36:27 +01:00
//if( xrf.URI.vars[ id ] && !match.length ) return console.error(`'${id}' object/tag/metadata-key not found in scene`)
2024-07-09 19:27:21 +02:00
if ( xrf . debug ) console . log ( ` URI.vars[ ${ id } ] => ' ${ v . string } ' ` )
2024-02-16 17:36:27 +01:00
if ( xrf . URI . vars [ id ] ) {
xrf . URI . vars [ id ] = xrf . URI . vars [ v . string ] // update var
2024-07-09 19:27:21 +02:00
if ( xrf . debug ) console . log ( ` URI.vars[ ${ id } ] => ' ${ xrf . URI . vars [ v . string ] ( ) } ' ` )
2024-02-16 17:36:27 +01:00
xrf . scene . traverse ( ( n ) => {
// re-expand src-values which use the updated URI Template var
if ( n . userData && n . userData . src && n . userData . srcTemplate && n . userData . srcTemplate . match ( ` { ${ id } } ` ) ) {
let srcNewFragments = xrf . frag . src . expandURI ( n ) . replace ( /.*#/ , '' )
console . log ( ` URI.vars[ ${ id } ] => updating ${ n . name } => ${ srcNewFragments } ` )
let frag = xrf . hashbus . pub ( srcNewFragments , n )
}
} )
} else {
xrf . XRWG . deepApplyMatch ( match , v , ( match , v , node , type ) => {
console . log ( v . string )
if ( node . geometry ) xrf . hashbus . pub ( xrf . URI . vars [ v . string ] ( ) , node ) // apply fragment mesh(es)
} )
}
2023-11-03 21:22:05 +01:00
} )
xrf . addEventListener ( 'dynamicKey' , ( opts ) => {
let { scene , id , match , v } = opts
if ( ! scene ) return
let remove = [ ]
2024-01-29 21:19:04 +01:00
2023-11-03 21:22:05 +01:00
// erase previous lines
2023-11-28 17:39:33 +01:00
xrf . focusLine . lines . map ( ( line ) => line . parent && ( line . parent . remove ( line ) ) )
2023-11-03 21:22:05 +01:00
xrf . focusLine . points = [ ]
xrf . focusLine . lines = [ ]
// drawlines
match . map ( ( w ) => {
w . nodes . map ( ( mesh ) => xrf . drawLineToMesh ( { ... opts , mesh } ) )
} )
} )
xrf . drawLineToMesh = ( opts ) => {
let { scene , mesh , frag , id } = opts
2023-11-28 17:39:33 +01:00
const THREE = xrf . THREE
2023-11-03 21:22:05 +01:00
let oldSelection
// Selection of Interest if predefined_view matches object name
if ( mesh . visible && mesh . material ) {
xrf . emit ( 'focus' , { ... opts , frag } )
. then ( ( ) => {
const color = new THREE . Color ( ) ;
const colors = [ ]
let from = new THREE . Vector3 ( )
let getCenterPoint = ( mesh ) => {
var geometry = mesh . geometry ;
geometry . computeBoundingBox ( ) ;
var center = new THREE . Vector3 ( ) ;
geometry . boundingBox . getCenter ( center ) ;
mesh . localToWorld ( center ) ;
return center ;
}
2023-11-28 17:39:33 +01:00
let cam = xrf . camera . getCam ? xrf . camera . getCam ( ) : xrf . camera // *FIXME* camerarig/rig are conflicting
cam . updateMatrixWorld ( true ) ; // always keeps me diving into the docs :]
cam . getWorldPosition ( from )
2023-11-03 21:22:05 +01:00
from . y = 0.5 // originate from the heart chakra! :p
const points = [ from , getCenterPoint ( mesh ) ]
const geometry = new THREE . BufferGeometry ( ) . setFromPoints ( points ) ;
let line = new THREE . Line ( geometry , xrf . focusLine . material ) ;
line . isXRF = true
line . computeLineDistances ( ) ;
xrf . focusLine . lines . push ( line )
xrf . focusLine . points . push ( from )
xrf . focusLine . opacity = 1
2024-08-01 16:25:05 +02:00
xrf . add ( line )
2023-11-03 21:22:05 +01:00
} )
}
}
xrf . addEventListener ( 'render' , ( opts ) => {
// update focusline
let { time , model } = opts
2025-01-15 10:53:56 +01:00
if ( ! xrf . clock || ! xrf . focusLine ) return
2023-11-03 21:22:05 +01:00
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 )
xrf . focusLine . material . opacity = ( 0.25 + 0.15 * Math . sin ( xrf . clock . getElapsedTime ( ) * 3 ) ) * xrf . focusLine . opacity ;
if ( xrf . focusLine . opacity > 0.0 ) xrf . focusLine . opacity -= time * 0.2
if ( xrf . focusLine . opacity < 0.0 ) xrf . focusLine . opacity = 0
} )
2023-10-16 16:58:56 +02:00
/ *
* mimetype : audio / aac
* mimetype : audio / mpeg
* mimetype : audio / ogg
* mimetype : audio / weba
* mimetype : audio / wav
* /
let loadAudio = ( mimetype ) => function ( url , opts ) {
2023-11-28 17:39:33 +01:00
let { mesh , src , camera , THREE } = opts
2024-04-16 15:19:08 +02:00
let URL = xrfragment . URI . toAbsolute ( xrf . navigator . URI , url )
let frag = URL . XRF
2023-10-16 16:58:56 +02:00
2024-02-16 17:36:27 +01:00
xrf . init . audio ( )
2023-11-03 21:22:05 +01:00
let isPositionalAudio = ! ( mesh . position . x == 0 && mesh . position . y == 0 && mesh . position . z == 0 )
const audioLoader = new THREE . AudioLoader ( ) ;
let sound = isPositionalAudio ? new THREE . PositionalAudio ( camera . listener )
: new THREE . Audio ( camera . listener )
2024-10-13 14:03:31 +02:00
sound . isXRF = true
2024-02-16 17:36:27 +01:00
mesh . media = mesh . media || { }
mesh . media . audio = { set : ( mediafragment , v ) => mesh . media . audio [ mediafragment ] = v }
2024-01-31 19:47:02 +01:00
2024-04-16 15:19:08 +02:00
let finalUrl = URL . URN + URL . file
if ( xrf . debug > 0 ) console . log ( "GET " + finalUrl )
2024-02-29 14:36:00 +01:00
audioLoader . load ( finalUrl , function ( buffer ) {
2023-11-03 21:22:05 +01:00
sound . setBuffer ( buffer ) ;
sound . setLoop ( false ) ;
2024-02-16 17:36:27 +01:00
sound . setVolume ( 1.0 )
2023-11-03 21:22:05 +01:00
if ( isPositionalAudio ) {
sound . setRefDistance ( mesh . scale . x ) ;
sound . setRolloffFactor ( 20.0 )
//sound.setDirectionalCone( 360, 360, 0.01 );
2024-02-16 17:36:27 +01:00
} else sound . setVolume ( mesh . scale . x )
2023-11-03 21:22:05 +01:00
2024-02-16 17:36:27 +01:00
mesh . add ( sound )
sound . set = ( mediafragment , v ) => {
2023-12-06 12:55:08 +01:00
try {
2024-02-16 17:36:27 +01:00
sound [ mediafragment ] = v
if ( mediafragment == 't' ) {
if ( sound . isPlaying && v . y != undefined && v . x == v . y ) {
sound . offset = v . x * buffer . sampleRate ;
sound . pause ( )
return
} else sound . stop ( )
2023-12-06 12:55:08 +01:00
// apply embedded audio/video samplerate/fps or global mixer fps
2024-02-16 17:36:27 +01:00
sound . setLoopStart ( v . x ) ;
sound . setLoopEnd ( v . y || buffer . duration ) ;
sound . offset = v . x ;
sound . play ( )
}
if ( mediafragment == 's' ) {
// *TODO* https://stackoverflow.com/questions/12484052/how-can-i-reverse-playback-in-web-audio-api-but-keep-a-forward-version-as-well
sound . pause ( )
sound . setPlaybackRate ( Math . abs ( v . x ) ) // WebAudio does not support negative playback
sound . play ( )
}
if ( mediafragment == 'loop' ) {
sound . pause ( )
sound . setLoop ( v . loop )
2023-12-06 12:55:08 +01:00
sound . play ( )
2023-11-03 21:22:05 +01:00
}
2023-12-06 12:55:08 +01:00
} catch ( e ) { console . warn ( e ) }
2023-11-03 21:22:05 +01:00
}
2024-01-31 19:47:02 +01:00
2024-02-16 17:36:27 +01:00
let lazySet = { }
2024-02-29 14:36:00 +01:00
let mediaFragments = [ 'loop' , 's' , 't' ]
2024-02-16 17:36:27 +01:00
mediaFragments . map ( ( f ) => mesh . media . audio [ f ] && ( lazySet [ f ] = mesh . media . audio [ f ] ) )
mesh . media . audio = sound
// autoplay if user already requested play (before the sound was loaded)
mediaFragments . map ( ( f ) => {
if ( lazySet [ f ] ) mesh . media . audio . set ( f , lazySet [ f ] )
} )
2023-11-03 21:22:05 +01:00
} ) ;
2024-02-16 17:36:27 +01:00
// apply Media fragments from URL
( [ 't' , 'loop' , 's' ] ) . map ( ( f ) => {
if ( frag [ f ] ) {
mesh . media . audio . set ( f , frag [ f ] )
}
} )
2023-10-16 16:58:56 +02:00
}
2024-02-16 17:36:27 +01:00
xrf . init . audio = ( opts ) => {
let camera = xrf . camera
/* WebAudio: setup context via THREEjs */
if ( ! camera . listener ) {
2024-06-12 10:51:42 +02:00
camera . listener = new xrf . THREE . AudioListener ( ) ;
2024-02-16 17:36:27 +01:00
// *FIXME* camera vs camerarig conflict
( camera . getCam ? camera . getCam ( ) : camera ) . add ( camera . listener ) ;
xrf . emit ( 'audioInited' , { listener : camera . listener , ... opts } )
}
}
xrf . addEventListener ( 'init' , xrf . init . audio )
// stop playing audio when loading another scene
xrf . addEventListener ( 'reset' , ( ) => {
xrf . scene . traverse ( ( n ) => {
if ( n . media && n . media . audio ) {
if ( n . media . audio . stop ) n . media . audio . stop ( )
if ( n . media . audio . remove ) n . media . audio . remove ( )
2024-12-10 13:54:26 +01:00
n . remove ( )
2024-02-16 17:36:27 +01:00
}
} )
} )
2023-10-16 16:58:56 +02:00
let audioMimeTypes = [
2024-02-16 17:36:27 +01:00
'audio/x-wav' ,
2023-10-16 16:58:56 +02:00
'audio/wav' ,
'audio/mpeg' ,
2023-11-03 21:34:32 +01:00
'audio/mp3' ,
2023-10-16 16:58:56 +02:00
'audio/weba' ,
'audio/aac' ,
'application/ogg'
]
audioMimeTypes . map ( ( mimetype ) => xrf . frag . src . type [ mimetype ] = loadAudio ( mimetype ) )
2024-02-16 17:36:27 +01:00
/ *
* mimetype : model / gltf + json
* /
2023-07-05 16:43:07 +02:00
2024-02-16 17:36:27 +01:00
xrf . frag . src . type [ 'fbx' ] = function ( url , opts ) {
return new Promise ( async ( resolve , reject ) => {
let { mesh , src } = opts
2024-04-16 15:19:08 +02:00
let URL = xrfragment . URI . toAbsolute ( xrf . navigator . URI , url )
let frag = URL . XRF
2024-02-16 17:36:27 +01:00
let loader
2024-02-29 14:36:00 +01:00
let { THREE } = await import ( 'https://unpkg.com/three@0.161.0/build/three.module.js' )
let { FBXLoader } = await import ( 'three/addons/loaders/FBXLoader.js' )
debugger
2024-02-16 17:36:27 +01:00
//const Loader = xrf.loaders[ext]
//if( !Loader ) throw 'xrfragment: no loader passed to xrfragment for extension .'+ext
//if( !dir.match("://") ){ // force relative path
// dir = dir[0] == './' ? dir : `./${dir}`
// loader = new Loader().setPath( dir )
//}else loader = new Loader()
//loader.load(url, (model) => {
// model.isSRC = true
// resolve(model)
//})
2024-01-31 19:47:02 +01:00
} )
2024-02-16 17:36:27 +01:00
}
/ *
* extensions : . frag / . fs / . vs / . vert
* /
xrf . frag . src . type [ 'x-shader/x-fragment' ] = function ( url , opts ) {
let { mesh , THREE } = opts
2024-04-16 15:19:08 +02:00
let URL = xrfragment . URI . toAbsolute ( xrf . navigator . URI , url )
let frag = URL . XRF
2024-02-16 17:36:27 +01:00
let isFragmentShader = /\.(fs|frag|glsl)$/
let isVertexShader = /\.(vs|vert)$/
let shaderReqs = [ ]
let shaderCode = { }
let shader = {
2024-04-16 15:19:08 +02:00
fragment : { code : '' , url : url . match ( isFragmentShader ) ? URL . URN + URL . file : '' } ,
vertex : { code : '' , url : url . match ( isVertexShader ) ? URL . URN + URL . file : '' }
2024-02-16 17:36:27 +01:00
}
var onShaderLoaded = ( ( args ) => ( type , status , code ) => {
2024-07-12 10:09:28 +02:00
if ( ! code ) return console . error ( 'could not load shader' )
2024-02-16 17:36:27 +01:00
shader [ type ] . status = status
shader [ type ] . code = code
if ( shader . fragment . code && shader . vertex . code ) {
let oldMaterial = mesh . material
mesh . material = new THREE . RawShaderMaterial ( {
uniforms : {
time : { value : 1.0 } ,
resolution : { value : new THREE . Vector2 ( 1.0 , 1.0 ) }
} ,
// basic shaders include following common vars/funcs: https://github.com/mrdoob/three.js/blob/master/src/renderers/shaders/ShaderChunk/common.glsl.js
fragmentShader : shader . fragment . status == 200 ? shader . fragment . code : THREE . ShaderChunk . meshbasic _frag ,
vertexShader : shader . vertex . status == 200 ? shader . vertex . code : THREE . ShaderChunk . meshbasic _vert ,
} ) ;
mesh . material . needsUpdate = true
mesh . needsUpdate = true
mesh . onBeforeRender = ( ) => {
if ( ! mesh . material || ! mesh . material . uniforms ) return mesh . onBeforeRender = function ( ) { }
mesh . material . uniforms . time . value = xrf . clock . elapsedTime
}
}
} ) ( { } )
// sidecar-load vertex shader file
if ( shader . fragment . url && ! shader . vertex . url ) {
shader . vertex . url = shader . fragment . url . replace ( /\.fs$/ , '.vs' )
. replace ( /\.frag$/ , '.vert' )
}
if ( shader . fragment . url ) {
fetch ( shader . fragment . url )
. then ( ( res ) => res . text ( ) . then ( ( code ) => onShaderLoaded ( 'fragment' , res . status , code ) ) )
}
if ( shader . vertex . url ) {
fetch ( shader . vertex . url )
. then ( ( res ) => res . text ( ) . then ( ( code ) => onShaderLoaded ( 'vertex' , res . status , code ) ) )
}
}
xrf . frag . src . type [ 'x-shader/x-vertex' ] = xrf . frag . src . type [ 'x-shader/x-fragment' ]
2023-07-05 16:43:07 +02:00
/ *
* mimetype : model / gltf + json
* /
2023-09-21 13:05:30 +02:00
xrf . frag . src . type [ 'gltf' ] = function ( url , opts ) {
2023-07-05 16:43:07 +02:00
return new Promise ( ( resolve , reject ) => {
2023-09-21 13:05:30 +02:00
let { mesh , src } = opts
2024-04-16 15:19:08 +02:00
let URL = xrfragment . URI . toAbsolute ( xrf . navigator . URI , url )
let { directory , file , fileExt , URN } = URL ;
2023-07-05 16:43:07 +02:00
let loader
2024-04-16 15:19:08 +02:00
const Loader = xrf . loaders [ fileExt ]
2023-07-05 16:43:07 +02:00
if ( ! Loader ) throw 'xrfragment: no loader passed to xrfragment for extension .' + ext
2024-04-16 15:19:08 +02:00
loader = new Loader ( ) . setPath ( URN )
2023-07-05 16:43:07 +02:00
2024-04-16 15:19:08 +02:00
loader . load ( file , ( model ) => {
2023-12-12 17:25:21 +01:00
model . isSRC = true
2023-07-05 16:43:07 +02:00
resolve ( model )
2023-09-21 13:05:30 +02:00
} )
2023-07-05 16:43:07 +02:00
} )
2023-05-05 18:53:42 +02:00
}
2023-07-06 16:54:12 +02:00
2023-12-06 12:55:08 +01:00
let loadHTML = ( mimetype ) => function ( url , opts ) {
let { mesh , src , camera } = opts
let { urlObj , dir , file , hash , ext } = xrf . parseUrl ( url )
2024-04-16 15:19:08 +02:00
let frag = xrf . URI . parse ( url ) . XRF
2023-12-06 12:55:08 +01:00
console . warn ( "todo: html viewer for src not implemented" )
}
let htmlMimeTypes = [
'text/html'
]
htmlMimeTypes . map ( ( mimetype ) => xrf . frag . src . type [ mimetype ] = loadHTML ( mimetype ) )
2023-07-06 16:54:12 +02:00
/ *
* mimetype : image / png
* mimetype : image / jpg
* mimetype : image / gif
* /
xrf . frag . src . type [ 'image/png' ] = function ( url , opts ) {
2023-11-28 17:39:33 +01:00
let { mesh , THREE } = opts
2023-09-21 13:05:30 +02:00
let restrictTo3DBoundingBox = mesh . geometry
2024-04-16 15:19:08 +02:00
let URL = xrfragment . URI . toAbsolute ( xrf . navigator . URI , url )
let frag = URL . XRF
2023-07-06 16:54:12 +02:00
2023-12-06 12:55:08 +01:00
mesh . material = new xrf . THREE . MeshBasicMaterial ( {
map : null ,
2024-01-29 21:19:04 +01:00
transparent : url . match ( /\.(png|gif)/ ) ? true : false ,
2023-12-06 12:55:08 +01:00
side : THREE . DoubleSide ,
color : 0xFFFFFF ,
opacity : 1
} ) ;
2023-11-03 21:22:05 +01:00
let renderImage = ( texture ) => {
let img = { w : texture . source . data . width , h : texture . source . data . height }
// stretch image by pinning uv-coordinates to corners
if ( mesh . geometry ) {
if ( mesh . geometry . attributes . uv ) { // buffergeometries
let uv = mesh . geometry . attributes . uv ;
} else {
console . warn ( "xrfragment: uv's of ${url} might be off for non-buffer-geometries *TODO*" )
//if( geometry.faceVertexUvs ){
// *TODO* force uv's of dynamically created geometries (in threejs)
//}
}
}
2023-12-06 12:55:08 +01:00
mesh . material . map = texture
2024-01-29 21:19:04 +01:00
mesh . material . needsUpdate = true
2023-12-06 12:55:08 +01:00
mesh . needsUpdate = true
2024-02-16 17:36:27 +01:00
//// *TODO* update clones in portals or dont clone scene of portals..
//xrf.scene.traverse( (n) => {
// if( n.userData.src == mesh.userData.src && mesh.uuid != n.uuid ){
// n.material = mesh.material
// n.material.needsUpdate = true
// }
//})
2023-11-03 21:22:05 +01:00
}
let onLoad = ( texture ) => {
texture . colorSpace = THREE . SRGBColorSpace ;
texture . wrapS = THREE . RepeatWrapping ;
texture . wrapT = THREE . RepeatWrapping ;
2023-12-06 12:55:08 +01:00
renderImage ( texture )
2023-07-06 16:54:12 +02:00
}
2023-11-03 21:22:05 +01:00
2024-04-16 15:19:08 +02:00
new THREE . TextureLoader ( ) . load ( URL . URN + URL . file , onLoad , null , console . error ) ;
2023-11-03 21:22:05 +01:00
2023-07-06 16:54:12 +02:00
}
2023-11-03 21:22:05 +01:00
2023-07-06 16:54:12 +02:00
xrf . frag . src . type [ 'image/gif' ] = xrf . frag . src . type [ 'image/png' ]
2023-11-03 21:22:05 +01:00
xrf . frag . src . type [ 'image/jpeg' ] = xrf . frag . src . type [ 'image/png' ]
2023-07-06 16:54:12 +02:00
2023-11-28 17:39:33 +01:00
// spec 8: https://xrfragment.org/doc/RFC_XR_Macros.html#embedding-xr-content-using-src
xrf . portalNonEuclidian = function ( opts ) {
let { frag , mesh , model , camera , scene , renderer } = opts
mesh . portal = {
pos : mesh . position . clone ( ) ,
posWorld : new xrf . THREE . Vector3 ( ) ,
posWorldCamera : new xrf . THREE . Vector3 ( ) ,
stencilRef : xrf . portalNonEuclidian . stencilRef ,
needUpdate : false ,
stencilObject : false ,
2023-12-06 15:20:58 +01:00
cameraDirection : new xrf . THREE . Vector3 ( ) ,
cameraPosition : new xrf . THREE . Vector3 ( ) ,
raycaster : new xrf . THREE . Raycaster ( ) ,
2023-11-28 17:39:33 +01:00
isLocal : opts . isLocal ,
2023-12-06 12:55:08 +01:00
isLens : false ,
isInside : false ,
setStencil : ( stencilRef ) => mesh . portal . stencilObjects . traverse ( ( n ) => showPortal ( n , stencilRef == 0 ) && n . stencil && n . stencil ( stencilRef ) ) ,
positionObjectsIfNeeded : ( pos , scale ) => ! mesh . portal . isLens && mesh . portal . stencilObjects . traverse ( ( n ) => n . positionAtStencil && ( n . positionAtStencil ( pos , scale ) ) )
2023-11-28 17:39:33 +01:00
}
// allow objects to flip between original and stencil position (which puts them behind stencilplane)
const addStencilFeature = ( n ) => {
if ( n . stencil ) return n // run once
2023-12-06 12:55:08 +01:00
n . stencil = ( sRef ) => xrf . portalNonEuclidian . selectStencil ( n , sRef )
n . positionAtStencil = ( pos , scale ) => ( newPos , newScale ) => {
n . position . copy ( newPos || pos )
n . scale . copy ( scale )
n . updateMatrixWorld ( true )
}
// curry function
n . positionAtStencil = n . positionAtStencil ( n . position . clone ( ) , n . scale . clone ( ) )
2023-11-28 17:39:33 +01:00
return n
}
this . setupStencilObjects = ( scene , opts ) => {
// collect related objects to render inside stencilplane
let stencilObject = scene
if ( opts . srcFrag . target ) {
stencilObject = scene . getObjectByName ( opts . srcFrag . target . key )
// spec: if src-object is child of portal (then portal is lens, and should include all children )
mesh . traverse ( ( n ) => n . name == opts . srcFrag . target . key && ( stencilObject = n ) && ( mesh . portal . isLens = true ) )
}
if ( ! stencilObject ) return console . warn ( ` no objects were found (src: ${ mesh . userData . src } ) for (portal)object name ' ${ mesh . name } ' ` )
mesh . portal . stencilObject = stencilObject
// spec: if src points to child, act as lens
if ( ! mesh . portal . isLocal || mesh . portal . isLens ) stencilObject . visible = false
let stencilObjects = [ stencilObject ]
stencilObjects = stencilObjects
. filter ( ( n ) => ! n . portal ) // filter out (self)references to portals (prevent recursion)
. map ( addStencilFeature )
// put it into a scene (without .add() because it reparents objects) so we can render it separately
mesh . portal . stencilObjects = new xrf . THREE . Scene ( )
mesh . portal . stencilObjects . children = stencilObjects
2024-02-29 14:36:00 +01:00
mesh . portal . stencilObjects . isXRF = true
2023-11-28 17:39:33 +01:00
xrf . portalNonEuclidian . stencilRef += 1 // each portal has unique stencil id
2024-02-16 17:36:27 +01:00
if ( xrf . debug ) console . log ( ` enabling portal for object ' ${ mesh . name } ' (stencilRef: ${ mesh . portal . stencilRef } ) ` )
2023-11-28 17:39:33 +01:00
return this
}
// enable the stencil-material of the stencil objects to prevent stackoverflow (portal in portal rendering)
const showPortal = ( n , show ) => {
if ( n . portal ) n . visible = show
return true
}
this . setupListeners = ( ) => {
2024-02-16 17:36:27 +01:00
// below is a somewhat weird tapdance to render the portals **after** the scene
// is rendered (otherwise it messes up occlusion)
2023-11-28 17:39:33 +01:00
mesh . onAfterRender = function ( renderer , scene , camera , geometry , material , group ) {
mesh . portal . needUpdate = true
}
xrf . addEventListener ( 'renderPost' , ( opts ) => {
let { scene , camera , time , render , renderer } = opts
2023-12-06 12:55:08 +01:00
if ( mesh . portal . needUpdate && mesh . portal && mesh . portal . stencilObjects ) {
let cameraDirection = mesh . portal . cameraDirection
let cameraPosition = mesh . portal . cameraPosition
2023-11-28 17:39:33 +01:00
let stencilRef = mesh . portal . stencilRef
let newPos = mesh . portal . posWorld
let stencilObject = mesh . portal . stencilObject
let newScale = mesh . scale
let raycaster = mesh . portal . raycaster
2023-12-06 12:55:08 +01:00
2023-11-28 17:39:33 +01:00
let cam = xrf . camera . getCam ? xrf . camera . getCam ( ) : camera
cam . getWorldPosition ( cameraPosition )
cam . getWorldDirection ( cameraDirection )
2024-02-16 17:36:27 +01:00
if ( cameraPosition . distanceTo ( newPos ) > 15.0 ) return // dont render far portals
2023-11-28 17:39:33 +01:00
// init
if ( ! mesh . portal . isLocal || mesh . portal . isLens ) stencilObject . visible = true
2023-12-06 12:55:08 +01:00
mesh . portal . setStencil ( stencilRef )
2023-11-28 17:39:33 +01:00
renderer . autoClear = false
renderer . autoClearDepth = false
renderer . autoClearColor = false
renderer . autoClearStencil = false
// render
render ( mesh . portal . stencilObjects , camera )
// de-init
renderer . autoClear = true
renderer . autoClearDepth = true
renderer . autoClearColor = true
renderer . autoClearStencil = true
2023-12-06 12:55:08 +01:00
mesh . portal . setStencil ( 0 )
2023-11-28 17:39:33 +01:00
if ( ! mesh . portal . isLocal || mesh . portal . isLens ) stencilObject . visible = false
// trigger href upon camera collide
if ( mesh . userData . XRF . href ) {
raycaster . far = 0.35
raycaster . set ( cameraPosition , cameraDirection )
2024-06-15 17:33:08 +02:00
let intersects = raycaster . intersectObjects ( [ mesh ] , false )
2023-11-28 17:39:33 +01:00
if ( intersects . length > 0 && ! mesh . portal . teleporting ) {
mesh . portal . teleporting = true
mesh . userData . XRF . href . exec ( { nocommit : true } )
setTimeout ( ( ) => mesh . portal . teleporting = false , 500 ) // dont flip back and forth
}
}
}
mesh . portal . needUpdate = false
} )
2023-12-06 12:55:08 +01:00
2023-11-28 17:39:33 +01:00
return this
}
// turn mesh into stencilplane
xrf
. portalNonEuclidian
. setMaterial ( mesh )
. getWorldPosition ( mesh . portal . posWorld )
this
. setupListeners ( )
. setupStencilObjects ( scene , opts )
2023-12-06 12:55:08 +01:00
// move portal objects to portalposition
if ( mesh . portal . stencilObjects ) mesh . portal . positionObjectsIfNeeded ( mesh . portal . posWorld , mesh . scale )
2023-11-28 17:39:33 +01:00
}
xrf . portalNonEuclidian . selectStencil = ( n , stencilRef , nested ) => {
if ( n . material ) {
n . material . stencilRef = stencilRef
n . material . stencilWrite = stencilRef > 0
n . material . stencilFunc = xrf . THREE . EqualStencilFunc ;
}
if ( n . children && ! nested ) n . traverse ( ( m ) => ! m . portal && ( xrf . portalNonEuclidian . selectStencil ( m , stencilRef , true ) ) )
}
xrf . portalNonEuclidian . setMaterial = function ( mesh ) {
mesh . material = new xrf . THREE . MeshBasicMaterial ( { color : 'orange' } ) ;
mesh . material . depthWrite = false ;
mesh . material . colorWrite = false ;
mesh . material . stencilWrite = true ;
mesh . material . stencilRef = xrf . portalNonEuclidian . stencilRef ;
mesh . material . stencilFunc = xrf . THREE . AlwaysStencilFunc ;
mesh . material . stencilZPass = xrf . THREE . ReplaceStencilOp ;
mesh . material . stencilZFail = xrf . THREE . ReplaceStencilOp ;
return mesh
}
xrf . addEventListener ( 'parseModel' , ( opts ) => {
const scene = opts . model . scene
} )
2023-12-06 12:55:08 +01:00
// (re)set portalObjects when entering/leaving a portal
let updatePortals = ( opts ) => {
xrf . scene . traverse ( ( n ) => {
if ( ! n . portal ) return
// move objects back to the portal
if ( n . portal . isInside ) n . portal . positionObjectsIfNeeded ( n . portal . posWorld , n . scale )
n . portal . isInside = false
} )
if ( opts . mesh && opts . mesh . portal && opts . click ) {
opts . mesh . portal . isInside = true
opts . mesh . portal . positionObjectsIfNeeded ( ) // move objects back to original pos (since we are teleporting there)
}
}
xrf . addEventListener ( 'href' , ( opts ) => opts . click && updatePortals ( opts ) )
xrf . addEventListener ( 'navigate' , updatePortals )
2023-11-28 17:39:33 +01:00
xrf . portalNonEuclidian . stencilRef = 1
2023-11-06 11:50:11 +01:00
let loadVideo = ( mimetype ) => function ( url , opts ) {
let { mesh , src , camera } = opts
2023-11-28 17:39:33 +01:00
const THREE = xrf . THREE
2024-04-16 15:19:08 +02:00
let URL = xrfragment . URI . toAbsolute ( xrf . navigator . URI , url )
let frag = URL . XRF
2023-11-06 11:50:11 +01:00
2024-06-11 19:30:32 +02:00
// patch VideoTexture so it doesn't upload videoframes when paused
// https://github.com/mrdoob/three.js/pull/28575
THREE . VideoTexture . prototype . update = function ( ) {
const video = this . image ;
const hasVideoFrameCallback = 'requestVideoFrameCallback' in video ;
if ( hasVideoFrameCallback === false && video . readyState >= video . HAVE _CURRENT _DATA && ( ! video . paused || ! this . firstFrame ) ) {
console . log ( "updating.." )
this . needsUpdate = true ;
this . firstFrame = true
}
}
2024-02-16 17:36:27 +01:00
mesh . media = mesh . media || { }
let video = mesh . media . video = document . createElement ( 'video' )
2024-06-11 19:30:32 +02:00
video . style . display = 'none'
2023-11-06 11:50:11 +01:00
video . setAttribute ( "crossOrigin" , "anonymous" )
video . setAttribute ( "playsinline" , '' )
video . addEventListener ( 'loadedmetadata' , function ( ) {
let texture = new THREE . VideoTexture ( video ) ;
texture . colorSpace = THREE . SRGBColorSpace ;
let mat = new xrf . THREE . MeshBasicMaterial ( )
mat . map = texture
mesh . material = mat
2023-11-28 17:39:33 +01:00
// set range
2024-02-16 17:36:27 +01:00
video . addEventListener ( 'timeupdate' , function timeupdate ( ) {
if ( video . t && video . t . y !== undefined && video . t . y > video . t . x && Math . abs ( video . currentTime ) >= video . t . y ) {
if ( video . looping ) video . currentTime = video . t . x // speed means loop
else video . pause ( )
}
} , false )
2023-11-06 11:50:11 +01:00
} )
2023-11-28 17:39:33 +01:00
2024-04-16 15:19:08 +02:00
video . src = URL . URN + URL . file
2024-02-16 17:36:27 +01:00
video . speed = 1.0
video . looping = false
video . set = ( mediafragment , v ) => {
video [ mediafragment ] = v
if ( mediafragment == 't' ) {
video . pause ( )
if ( v . x !== undefined && v . x == v . y ) return // stop paused
else {
video . currentTime = v . x
video . time = v . x
video . play ( )
}
}
if ( mediafragment == 's' ) {
video . playbackRate = Math . abs ( v . x ) // html5 video does not support reverseplay :/
}
if ( mediafragment == 'loop' ) {
video . looping = true
2023-11-06 11:50:11 +01:00
}
}
}
2024-02-16 17:36:27 +01:00
// stop playing audio when loading another scene
xrf . addEventListener ( 'reset' , ( ) => {
xrf . scene . traverse ( ( n ) => n . media && n . media . video && ( n . media . video . pause ( ) ) && ( n . media . video . remove ( ) ) )
} )
2023-11-06 11:50:11 +01:00
let videoMimeTypes = [
'video/ogg' ,
'video/mp4'
]
videoMimeTypes . map ( ( mimetype ) => xrf . frag . src . type [ mimetype ] = loadVideo ( mimetype ) )