@terraware/web-components 5.0.27-rc.0 → 5.0.27

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (295) hide show
  1. package/components/AntSwitch/index.js +22 -19
  2. package/components/Autocomplete/Autocomplete.js +130 -106
  3. package/components/Badge/index.js +29 -28
  4. package/components/BusySpinner/index.js +45 -40
  5. package/components/Button/Button.js +47 -41
  6. package/components/Checkbox.js +63 -64
  7. package/components/Confirm/index.js +83 -74
  8. package/components/DatePicker/DatePicker.js +141 -138
  9. package/components/DialogBox/DialogBox.js +118 -111
  10. package/components/Divisor.js +18 -18
  11. package/components/Dropdown.js +94 -66
  12. package/components/EditableTable/index.js +271 -211
  13. package/components/ErrorBox/ErrorBox.js +49 -47
  14. package/components/FileChooser/index.js +135 -117
  15. package/components/FormBottomBar/index.js +84 -75
  16. package/components/Icon/Icon.js +20 -12
  17. package/components/Icon/icons/index.js +256 -256
  18. package/components/IconTooltip/index.js +60 -49
  19. package/components/Map/MapBox.js +452 -545
  20. package/components/Map/MapContainer.js +29 -25
  21. package/components/Map/MapDateSliderControl.js +64 -64
  22. package/components/Map/MapDrawer.js +47 -41
  23. package/components/Map/MapLegend.js +172 -176
  24. package/components/Map/MapViewStyleControl.js +63 -59
  25. package/components/Map/ReactMapboxWrapper.js +2 -3
  26. package/components/Map/index.js +180 -161
  27. package/components/Map/types.js +17 -12
  28. package/components/Map/useMaintainLayerOrder.js +26 -16
  29. package/components/Markdown/index.js +26 -25
  30. package/components/Message/Message.js +76 -73
  31. package/components/MultiSelect/index.js +124 -112
  32. package/components/Navbar/NavFooter.js +13 -9
  33. package/components/Navbar/NavItem.js +76 -70
  34. package/components/Navbar/NavSection.js +19 -17
  35. package/components/Navbar/Navbar.js +46 -39
  36. package/components/Navbar/SubNavbar.js +13 -9
  37. package/components/Note.js +22 -18
  38. package/components/OverlayModal/OverlayModal.js +63 -56
  39. package/components/PageForm/index.js +41 -33
  40. package/components/PhotoChooser/index.js +173 -165
  41. package/components/PhotosCarousel/PhotoSlide.js +35 -32
  42. package/components/PhotosCarousel/index.js +85 -80
  43. package/components/PhotosCarousel/usePhotosCarousel.js +76 -69
  44. package/components/Pill/index.js +45 -41
  45. package/components/PillList/index.js +24 -17
  46. package/components/PlacementWrapper/index.js +59 -38
  47. package/components/PopoverMenu/Popover.js +85 -68
  48. package/components/PopoverMenu/index.js +56 -51
  49. package/components/PopoverMultiSelect/index.js +59 -52
  50. package/components/ProgressCircle/ProgressCircle.js +32 -28
  51. package/components/RadioButton.js +19 -17
  52. package/components/Select/Select.js +15 -13
  53. package/components/Select/SelectT.js +250 -235
  54. package/components/Separator/index.js +17 -14
  55. package/components/Size.js +1 -0
  56. package/components/Slider/index.js +56 -43
  57. package/components/SummaryBox.js +72 -70
  58. package/components/Tabs/index.js +135 -133
  59. package/components/TextTruncated/index.js +130 -117
  60. package/components/Textfield/Textfield.js +190 -158
  61. package/components/Textfield/TruncatedTextArea.js +84 -70
  62. package/components/TimelineSlider/TimelineMarker.js +30 -25
  63. package/components/TimelineSlider/TimelineRail.js +11 -8
  64. package/components/TimelineSlider/index.js +66 -64
  65. package/components/TimelineSliderV2/ClusterBadge.js +29 -25
  66. package/components/TimelineSliderV2/TimelineDot.js +35 -31
  67. package/components/TimelineSliderV2/TimelineRail.js +11 -8
  68. package/components/TimelineSliderV2/index.js +103 -121
  69. package/components/TimelineSliderV2/layout.js +147 -124
  70. package/components/TimelineSliderV2/types.js +1 -0
  71. package/components/Tooltip/Tooltip.js +56 -47
  72. package/components/ViewPhotosDialog/index.js +35 -28
  73. package/components/VirtualWalkthrough/Annotation.js +207 -163
  74. package/components/VirtualWalkthrough/AnnotationEditPane.js +184 -198
  75. package/components/VirtualWalkthrough/AnnotationPanel.js +199 -187
  76. package/components/VirtualWalkthrough/Application.js +34 -31
  77. package/components/VirtualWalkthrough/AutoRotator.js +223 -85
  78. package/components/VirtualWalkthrough/BlockingSpinner.js +28 -26
  79. package/components/VirtualWalkthrough/BoundaryRing.js +34 -30
  80. package/components/VirtualWalkthrough/BoundaryWall.js +36 -32
  81. package/components/VirtualWalkthrough/CameraInfo.js +89 -100
  82. package/components/VirtualWalkthrough/ControlsInfoPane.js +195 -254
  83. package/components/VirtualWalkthrough/GradientSky.js +73 -72
  84. package/components/VirtualWalkthrough/SplatControls.js +221 -209
  85. package/components/VirtualWalkthrough/SplatCrop.js +20 -21
  86. package/components/VirtualWalkthrough/SplatFadeCrop.js +20 -21
  87. package/components/VirtualWalkthrough/SplatModel.js +85 -65
  88. package/components/VirtualWalkthrough/SplatRevealRain.js +46 -41
  89. package/components/VirtualWalkthrough/TfAnnotationManager.js +379 -197
  90. package/components/VirtualWalkthrough/TfXrNavigation.js +162 -73
  91. package/components/VirtualWalkthrough/VirtualWalkthroughViewer.js +431 -438
  92. package/components/VirtualWalkthrough/VrAnnotationPanel.js +22 -18
  93. package/components/VirtualWalkthrough/XrAnnotationInteraction.js +17 -14
  94. package/components/VirtualWalkthrough/XrExitButton.js +17 -14
  95. package/components/VirtualWalkthrough/XrGazeDwell.js +17 -14
  96. package/components/VirtualWalkthrough/XrPointerRay.js +17 -14
  97. package/components/VirtualWalkthrough/XrStartPose.js +121 -67
  98. package/components/VirtualWalkthrough/boundary-ring.js +117 -87
  99. package/components/VirtualWalkthrough/boundary-wall.js +258 -175
  100. package/components/VirtualWalkthrough/camera-adoption.js +64 -18
  101. package/components/VirtualWalkthrough/groundPlane.js +47 -32
  102. package/components/VirtualWalkthrough/gsplat-fade-crop-effect.js +35 -35
  103. package/components/VirtualWalkthrough/splatFormat.js +47 -13
  104. package/components/VirtualWalkthrough/useAdoptedCamera.js +44 -23
  105. package/components/VirtualWalkthrough/useRestartOnVr.js +37 -26
  106. package/components/VirtualWalkthrough/useXrRenderTuning.js +54 -33
  107. package/components/VirtualWalkthrough/vr-annotation-panel-layout.js +99 -67
  108. package/components/VirtualWalkthrough/vr-annotation-panel.js +433 -403
  109. package/components/VirtualWalkthrough/vr-annotation-scroll.js +28 -10
  110. package/components/VirtualWalkthrough/walkthrough-camera.js +329 -204
  111. package/components/VirtualWalkthrough/xr-annotation-candidate-buffer.js +80 -39
  112. package/components/VirtualWalkthrough/xr-annotation-candidates.js +18 -6
  113. package/components/VirtualWalkthrough/xr-annotation-interaction.js +56 -31
  114. package/components/VirtualWalkthrough/xr-annotation-targeting.js +38 -22
  115. package/components/VirtualWalkthrough/xr-button-arming.js +17 -7
  116. package/components/VirtualWalkthrough/xr-button-hold.js +36 -22
  117. package/components/VirtualWalkthrough/xr-exit-button-mask.js +36 -22
  118. package/components/VirtualWalkthrough/xr-exit-button.js +207 -148
  119. package/components/VirtualWalkthrough/xr-face-buttons.js +29 -19
  120. package/components/VirtualWalkthrough/xr-gaze-dwell-state.js +34 -23
  121. package/components/VirtualWalkthrough/xr-gaze-dwell.js +144 -107
  122. package/components/VirtualWalkthrough/xr-interactive-ui.js +24 -12
  123. package/components/VirtualWalkthrough/xr-pointer-ray.js +20 -13
  124. package/components/VirtualWalkthrough/xr-progress-pie-material.js +88 -117
  125. package/components/VirtualWalkthrough/xr-progress-pie.js +10 -1
  126. package/components/VirtualWalkthrough/xr-ray-quad.js +30 -16
  127. package/components/VirtualWalkthrough/xr-render-tuning.js +48 -17
  128. package/components/VirtualWalkthrough/xr-scene-bounds.js +37 -21
  129. package/components/VirtualWalkthrough/xr-start-pose.js +73 -27
  130. package/components/VirtualWalkthrough/xr-teleport-gesture.js +42 -16
  131. package/components/svg/BlobbyGrayIconImage.js +39 -41
  132. package/components/svg/BlobbyGrayIconImport.js +39 -41
  133. package/components/svg/BlobbyGrayIconUploadToTheCloud.js +39 -41
  134. package/components/svg/BlobbyIconCloudChecked.js +164 -202
  135. package/components/svg/BlobbyIconExportOrUploadPhoto.js +164 -202
  136. package/components/svg/BlobbyIconFolder.js +164 -202
  137. package/components/svg/BlobbyIconGraphReport.js +164 -202
  138. package/components/svg/BlobbyIconHappy.js +164 -202
  139. package/components/svg/BlobbyIconHeartMonitor.js +164 -202
  140. package/components/svg/BlobbyIconLeaf.js +164 -202
  141. package/components/svg/BlobbyIconLibrary.js +164 -202
  142. package/components/svg/BlobbyIconNursery.js +166 -204
  143. package/components/svg/BlobbyIconOrganization.js +164 -202
  144. package/components/svg/BlobbyIconParchment.js +164 -202
  145. package/components/svg/BlobbyIconPeople.js +164 -202
  146. package/components/svg/BlobbyIconSeedBank.js +164 -202
  147. package/components/svg/BlobbyIconSeedling.js +164 -202
  148. package/components/svg/BlobbyIconSeeds.js +164 -202
  149. package/components/svg/BlobbyIconSite.js +167 -206
  150. package/components/svg/BlobbyIconSparkles.js +164 -202
  151. package/components/svg/BlobbyIconWrench.js +164 -202
  152. package/components/svg/Bug.js +21 -19
  153. package/components/svg/Calendar.js +21 -19
  154. package/components/svg/CaretDown.js +21 -19
  155. package/components/svg/CaretLeft.js +21 -19
  156. package/components/svg/CaretRight.js +21 -19
  157. package/components/svg/CaretUp.js +21 -19
  158. package/components/svg/ChevronDown.js +21 -19
  159. package/components/svg/ChevronUp.js +21 -19
  160. package/components/svg/Close.js +21 -19
  161. package/components/svg/Critical.js +21 -19
  162. package/components/svg/Edit.js +164 -202
  163. package/components/svg/Error.js +21 -19
  164. package/components/svg/Filter.js +21 -19
  165. package/components/svg/Folder.js +21 -19
  166. package/components/svg/Help.js +21 -19
  167. package/components/svg/Home.js +21 -19
  168. package/components/svg/IconAdd.js +21 -19
  169. package/components/svg/IconArrowRight.js +23 -21
  170. package/components/svg/IconBusinessNetwork.js +21 -19
  171. package/components/svg/IconCalendar.js +21 -19
  172. package/components/svg/IconCancel.js +21 -19
  173. package/components/svg/IconChargingBattery.js +21 -19
  174. package/components/svg/IconChecklist.js +23 -21
  175. package/components/svg/IconCheckmark.js +21 -19
  176. package/components/svg/IconCoinInHand.js +21 -19
  177. package/components/svg/IconColumns.js +21 -19
  178. package/components/svg/IconComment.js +22 -20
  179. package/components/svg/IconDashboard.js +21 -19
  180. package/components/svg/IconDataMigration.js +21 -19
  181. package/components/svg/IconDocument.js +23 -21
  182. package/components/svg/IconDownloadFromTheCloud.js +23 -21
  183. package/components/svg/IconEdit.js +23 -21
  184. package/components/svg/IconExpand.js +23 -21
  185. package/components/svg/IconExport.js +21 -19
  186. package/components/svg/IconExternalLink.js +21 -19
  187. package/components/svg/IconEye.js +21 -19
  188. package/components/svg/IconEyeOff.js +21 -19
  189. package/components/svg/IconFile.js +21 -19
  190. package/components/svg/IconFolder.js +21 -19
  191. package/components/svg/IconFullScreen.js +22 -20
  192. package/components/svg/IconFutures.js +21 -19
  193. package/components/svg/IconGraphReport.js +21 -19
  194. package/components/svg/IconGrid.js +21 -19
  195. package/components/svg/IconHeartMonitor.js +21 -19
  196. package/components/svg/IconHelp.js +23 -21
  197. package/components/svg/IconHistory.js +21 -19
  198. package/components/svg/IconImport.js +21 -19
  199. package/components/svg/IconIndex.js +21 -19
  200. package/components/svg/IconLayers.js +21 -19
  201. package/components/svg/IconLibrary.js +22 -20
  202. package/components/svg/IconList.js +21 -19
  203. package/components/svg/IconLivePlant.js +21 -19
  204. package/components/svg/IconLocations.js +21 -19
  205. package/components/svg/IconMail.js +21 -19
  206. package/components/svg/IconManager.js +21 -19
  207. package/components/svg/IconMap.js +21 -19
  208. package/components/svg/IconMarker.js +21 -19
  209. package/components/svg/IconMenu.js +21 -19
  210. package/components/svg/IconMenuHorizontal.js +21 -19
  211. package/components/svg/IconModule.js +21 -19
  212. package/components/svg/IconMyLocation.js +21 -19
  213. package/components/svg/IconNote.js +23 -21
  214. package/components/svg/IconNursery.js +23 -21
  215. package/components/svg/IconOrg.js +21 -19
  216. package/components/svg/IconParchment.js +21 -19
  217. package/components/svg/IconPhoto.js +21 -19
  218. package/components/svg/IconPlantsFilled.js +21 -19
  219. package/components/svg/IconRedo.js +21 -19
  220. package/components/svg/IconRestorationSite.js +21 -19
  221. package/components/svg/IconSeedBank.js +21 -19
  222. package/components/svg/IconSeedling.js +21 -19
  223. package/components/svg/IconSettings.js +21 -19
  224. package/components/svg/IconSlice.js +21 -19
  225. package/components/svg/IconSubmit.js +21 -19
  226. package/components/svg/IconSubtract.js +23 -21
  227. package/components/svg/IconSynced.js +21 -19
  228. package/components/svg/IconTrashCan.js +21 -19
  229. package/components/svg/IconTreasureMap.js +21 -19
  230. package/components/svg/IconUnavailable.js +21 -19
  231. package/components/svg/IconUndo.js +21 -19
  232. package/components/svg/IconVariable.js +21 -19
  233. package/components/svg/IconVideo.js +31 -30
  234. package/components/svg/IconWifi.js +21 -19
  235. package/components/svg/Info.js +21 -19
  236. package/components/svg/Key.js +21 -19
  237. package/components/svg/Leaf.js +21 -19
  238. package/components/svg/Lock.js +21 -19
  239. package/components/svg/Logo.js +43 -48
  240. package/components/svg/MenuVertical.js +21 -19
  241. package/components/svg/Notification.js +21 -19
  242. package/components/svg/Person.js +21 -19
  243. package/components/svg/Plus.js +21 -19
  244. package/components/svg/RestorationSite.js +21 -19
  245. package/components/svg/Search.js +21 -19
  246. package/components/svg/Seeds.js +21 -19
  247. package/components/svg/Site.js +21 -19
  248. package/components/svg/Sparkles.js +21 -19
  249. package/components/svg/Species.js +21 -19
  250. package/components/svg/Species2.js +164 -202
  251. package/components/svg/Spinner.js +48 -54
  252. package/components/svg/Star.js +21 -19
  253. package/components/svg/Success.js +21 -19
  254. package/components/svg/SuccessFilled.js +21 -19
  255. package/components/svg/TerrawareLogoDesktop.js +49 -55
  256. package/components/svg/TerrawareLogoMobile.js +26 -25
  257. package/components/svg/Touchscreen.js +21 -19
  258. package/components/svg/UploadCloud.js +164 -202
  259. package/components/svg/Warning.js +21 -19
  260. package/components/svg/WelcomeClipboard.js +133 -156
  261. package/components/svg/index.js +129 -129
  262. package/components/table/EnhancedTableToolbar.js +74 -68
  263. package/components/table/EnhancedTableToolbarV2.js +41 -31
  264. package/components/table/TableCellRenderer.js +231 -196
  265. package/components/table/TableHeader.js +73 -71
  266. package/components/table/TableHeaderItem.js +102 -97
  267. package/components/table/density.js +19 -18
  268. package/components/table/index.js +369 -350
  269. package/components/table/sort.js +52 -27
  270. package/components/table/types.js +1 -0
  271. package/components/types/index.js +1 -0
  272. package/hooks/cameraEntityContext.js +13 -2
  273. package/hooks/useBoolean.js +13 -17
  274. package/hooks/useCameraPosition.js +56 -56
  275. package/hooks/useDevicePerformance.js +17 -17
  276. package/hooks/useXr.js +87 -85
  277. package/index.js +58 -58
  278. package/package.json +1 -1
  279. package/setupTests.js +4 -0
  280. package/strings/export.js +170 -111
  281. package/strings/index.js +54 -25
  282. package/strings/locales.js +41 -21
  283. package/strings/strings-en.js +51 -49
  284. package/strings/strings-es.js +51 -49
  285. package/strings/strings-fr.js +51 -49
  286. package/strings/strings-gx.js +51 -49
  287. package/style-dictionary-dist/TerrawareTheme.js +186 -186
  288. package/theme.js +76 -74
  289. package/utils/color.js +15 -8
  290. package/utils/date.js +108 -42
  291. package/utils/index.js +4 -4
  292. package/utils/preventDefaultEvent.js +3 -3
  293. package/utils/text.js +11 -11
  294. package/utils/useDeviceInfo.js +79 -42
  295. package/virtualWalkthrough.js +23 -23
@@ -1,47 +1,88 @@
1
- import { Vec3 } from "playcanvas";
1
+ import { Vec3 } from 'playcanvas';
2
+
3
+ /** Local half-extent of the unit-plane hotspot quad. */
2
4
  const HOTSPOT_HALF_EXTENT = 0.5;
5
+
6
+ /** Pulls the stock annotation script off a hotspot entity, or undefined if it carries none. */
7
+
3
8
  const scratchScale = new Vec3();
4
- const isInScene = (node, sceneRoot)=>{
5
- for(let current = node; current; current = current.parent)if (current === sceneRoot) return true;
6
- return false;
9
+
10
+ /**
11
+ * Whether `node` still hangs beneath `sceneRoot`. An entity that has been removed from the scene
12
+ * keeps its own `children` array - a destroyed one keeps it as an empty array - so only a walk back
13
+ * up the parent chain tells a live root apart from a detached one.
14
+ */
15
+ const isInScene = (node, sceneRoot) => {
16
+ for (let current = node; current; current = current.parent) {
17
+ if (current === sceneRoot) {
18
+ return true;
19
+ }
20
+ }
21
+ return false;
7
22
  };
23
+
24
+ /**
25
+ * Collects world-space hit candidates for the openable annotation hotspots into a buffer it owns and
26
+ * reuses, so a caller running every frame allocates nothing. The returned array and the candidate
27
+ * objects inside it are overwritten by the next `collect`, which is why each caller holds its own
28
+ * buffer rather than sharing one.
29
+ *
30
+ * Positions and radii are re-read on every call, so hotspots that move or resize stay targetable.
31
+ * That read is cheap; it is the allocation around it that a per-frame caller cannot afford.
32
+ */
8
33
  class AnnotationCandidateBuffer {
9
- _getScript;
10
- _candidates = [];
11
- _root = null;
12
- constructor(_getScript){
13
- this._getScript = _getScript;
34
+ _candidates = [];
35
+ _root = null;
36
+ constructor(_getScript) {
37
+ this._getScript = _getScript;
38
+ }
39
+
40
+ /**
41
+ * Hit candidates for every openable hotspot except `excludeName`, with hit spheres derived from
42
+ * each hotspot's rendered world size and padded by `radiusPad` for easier aiming.
43
+ */
44
+ collect(app, radiusPad, excludeName) {
45
+ // The root is looked up by a walk of the whole scene graph, so it is held onto between frames.
46
+ // It is re-resolved whenever it leaves the scene: annotations mount after the scene does, and
47
+ // the root is torn down and built afresh every time the annotation list empties and refills.
48
+ if (!isInScene(this._root, app.root)) {
49
+ this._root = app.root.findByName('annotations-root');
50
+ }
51
+ const children = this._root?.children;
52
+ if (!children) {
53
+ this._candidates.length = 0;
54
+ return this._candidates;
14
55
  }
15
- collect(app, radiusPad, excludeName) {
16
- if (!isInScene(this._root, app.root)) this._root = app.root.findByName('annotations-root');
17
- const children = this._root?.children;
18
- if (!children) {
19
- this._candidates.length = 0;
20
- return this._candidates;
21
- }
22
- let count = 0;
23
- for (const entity of children){
24
- if (void 0 !== excludeName && entity.name === excludeName) continue;
25
- const script = this._getScript(entity);
26
- if (!script || false === script.enabled || 'function' != typeof script.onVrOpenCallback) continue;
27
- entity.getWorldTransform().getScale(scratchScale);
28
- const radius = HOTSPOT_HALF_EXTENT * scratchScale.x * radiusPad;
29
- const existing = this._candidates[count];
30
- if (existing) {
31
- existing.entity = entity;
32
- existing.script = script;
33
- existing.position = entity.getPosition();
34
- existing.radius = radius;
35
- } else this._candidates.push({
36
- entity,
37
- script,
38
- position: entity.getPosition(),
39
- radius
40
- });
41
- count++;
42
- }
43
- this._candidates.length = count;
44
- return this._candidates;
56
+ let count = 0;
57
+ for (const entity of children) {
58
+ if (excludeName !== undefined && entity.name === excludeName) {
59
+ continue;
60
+ }
61
+ const script = this._getScript(entity);
62
+ if (!script || script.enabled === false || typeof script.onVrOpenCallback !== 'function') {
63
+ continue;
64
+ }
65
+ entity.getWorldTransform().getScale(scratchScale);
66
+ const radius = HOTSPOT_HALF_EXTENT * scratchScale.x * radiusPad;
67
+ const existing = this._candidates[count];
68
+ if (existing) {
69
+ existing.entity = entity;
70
+ existing.script = script;
71
+ existing.position = entity.getPosition();
72
+ existing.radius = radius;
73
+ } else {
74
+ this._candidates.push({
75
+ entity,
76
+ script,
77
+ position: entity.getPosition(),
78
+ radius
79
+ });
80
+ }
81
+ count++;
45
82
  }
83
+ this._candidates.length = count;
84
+ return this._candidates;
85
+ }
46
86
  }
87
+
47
88
  export { AnnotationCandidateBuffer, HOTSPOT_HALF_EXTENT };
@@ -1,7 +1,19 @@
1
- import { Annotation } from "playcanvas/scripts/esm/annotations.mjs";
2
- import { AnnotationCandidateBuffer, HOTSPOT_HALF_EXTENT } from "./xr-annotation-candidate-buffer.js";
1
+ import { Annotation } from 'playcanvas/scripts/esm/annotations.mjs';
2
+ import { AnnotationCandidateBuffer } from './xr-annotation-candidate-buffer.js';
3
+ export { HOTSPOT_HALF_EXTENT } from './xr-annotation-candidate-buffer.js';
4
+
5
+ /** Multiplier on the hotspot's world radius to make controller targeting forgiving. */
3
6
  const HIT_RADIUS_PAD = 2.5;
4
- const annotationScriptOf = (entity)=>entity.script?.get(Annotation.scriptName);
5
- const createAnnotationCandidateBuffer = ()=>new AnnotationCandidateBuffer(annotationScriptOf);
6
- const collectAnnotationHitCandidates = (app, radiusPad)=>createAnnotationCandidateBuffer().collect(app, radiusPad);
7
- export { HIT_RADIUS_PAD, HOTSPOT_HALF_EXTENT, collectAnnotationHitCandidates, createAnnotationCandidateBuffer };
7
+ const annotationScriptOf = entity => entity.script?.get(Annotation.scriptName);
8
+
9
+ /** A buffer wired to the stock annotation script. Callers that collect every frame hold one of
10
+ * these; its results are overwritten on the next collect, so one buffer cannot be shared. */
11
+ const createAnnotationCandidateBuffer = () => new AnnotationCandidateBuffer(annotationScriptOf);
12
+
13
+ /**
14
+ * One-shot collection for callers that run on an input event rather than every frame, and so have no
15
+ * reason to hold a buffer of their own.
16
+ */
17
+ const collectAnnotationHitCandidates = (app, radiusPad) => createAnnotationCandidateBuffer().collect(app, radiusPad);
18
+
19
+ export { HIT_RADIUS_PAD, collectAnnotationHitCandidates, createAnnotationCandidateBuffer };
@@ -1,37 +1,62 @@
1
- import { Script, XRTYPE_VR } from "playcanvas";
2
- import { HIT_RADIUS_PAD, collectAnnotationHitCandidates } from "./xr-annotation-candidates.js";
3
- import { nearestAnnotationHit } from "./xr-annotation-targeting.js";
4
- import { FaceButtonPressTracker } from "./xr-face-buttons.js";
5
- import { rayHitsAnnotationPanel } from "./xr-interactive-ui.js";
1
+ import { Script, XRTYPE_VR } from 'playcanvas';
2
+ import { collectAnnotationHitCandidates, HIT_RADIUS_PAD } from './xr-annotation-candidates.js';
3
+ import { nearestAnnotationHit } from './xr-annotation-targeting.js';
4
+ import { FaceButtonPressTracker } from './xr-face-buttons.js';
5
+ import { rayHitsAnnotationPanel } from './xr-interactive-ui.js';
6
+
6
7
  class XrAnnotationInteraction extends Script {
7
- static scriptName = 'xrAnnotationInteraction';
8
- onDismissCallback;
9
- _faceButtons = new FaceButtonPressTracker();
10
- _isVrActive = ()=>this.app.xr?.active === true && this.app.xr?.type === XRTYPE_VR;
11
- _dismiss = ()=>{
12
- if ('function' == typeof this.onDismissCallback) this.onDismissCallback();
13
- };
14
- _onSelect = (inputSource)=>{
15
- if (!this._isVrActive()) return;
16
- this._openAnnotationUnderRay(inputSource.getOrigin(), inputSource.getDirection());
17
- };
18
- _openAnnotationUnderRay(origin, direction) {
19
- if (rayHitsAnnotationPanel(this.app, origin, direction)) return;
20
- const candidates = collectAnnotationHitCandidates(this.app, HIT_RADIUS_PAD);
21
- const index = nearestAnnotationHit(origin, direction, candidates);
22
- if (null === index) return void this._dismiss();
23
- const { entity, script } = candidates[index];
24
- const camera = this.app.root.findComponent('camera');
25
- const screen = camera?.worldToScreen(entity.getPosition());
26
- script.onVrOpenCallback(screen?.x ?? 0, screen?.y ?? 0);
8
+ static scriptName = 'xrAnnotationInteraction';
9
+ _faceButtons = new FaceButtonPressTracker();
10
+ _isVrActive = () => this.app.xr?.active === true && this.app.xr?.type === XRTYPE_VR;
11
+ _dismiss = () => {
12
+ if (typeof this.onDismissCallback === 'function') {
13
+ this.onDismissCallback();
27
14
  }
28
- initialize() {
29
- this.app.xr?.input?.on('select', this._onSelect);
30
- this.once('destroy', ()=>this.app.xr?.input?.off('select', this._onSelect));
15
+ };
16
+ _onSelect = inputSource => {
17
+ if (!this._isVrActive()) {
18
+ return;
31
19
  }
32
- update() {
33
- if (!this._isVrActive()) return;
34
- for (const inputSource of this.app.xr?.input?.inputSources ?? [])if (this._faceButtons.justPressed(inputSource)) return void this._dismiss();
20
+ this._openAnnotationUnderRay(inputSource.getOrigin(), inputSource.getDirection());
21
+ };
22
+ _openAnnotationUnderRay(origin, direction) {
23
+ // Checked before the hotspots so aiming at the panel (e.g. its carousel arrows) neither
24
+ // dismisses it nor opens an annotation whose padded hit sphere sits behind it.
25
+ if (rayHitsAnnotationPanel(this.app, origin, direction)) {
26
+ return;
35
27
  }
28
+ const candidates = collectAnnotationHitCandidates(this.app, HIT_RADIUS_PAD);
29
+ const index = nearestAnnotationHit(origin, direction, candidates);
30
+ if (index === null) {
31
+ this._dismiss();
32
+ return;
33
+ }
34
+ const {
35
+ entity,
36
+ script
37
+ } = candidates[index];
38
+ const camera = this.app.root.findComponent('camera');
39
+ const screen = camera?.worldToScreen(entity.getPosition());
40
+ script.onVrOpenCallback(screen?.x ?? 0, screen?.y ?? 0);
41
+ }
42
+ initialize() {
43
+ this.app.xr?.input?.on('select', this._onSelect);
44
+ this.once('destroy', () => this.app.xr?.input?.off('select', this._onSelect));
45
+ }
46
+ update() {
47
+ if (!this._isVrActive()) {
48
+ return;
49
+ }
50
+
51
+ // A face button dismisses from any aim direction, unlike the click-out select. Harmless when
52
+ // nothing is open: the React close handler re-sets state it already holds.
53
+ for (const inputSource of this.app.xr?.input?.inputSources ?? []) {
54
+ if (this._faceButtons.justPressed(inputSource)) {
55
+ this._dismiss();
56
+ return;
57
+ }
58
+ }
59
+ }
36
60
  }
61
+
37
62
  export { XrAnnotationInteraction };
@@ -1,28 +1,44 @@
1
- import { Vec3 } from "playcanvas";
1
+ import { Vec3 } from 'playcanvas';
2
+
3
+ // Reused across calls: this runs per candidate per frame, where fresh vectors would be steady GC churn.
2
4
  const scratchDir = new Vec3();
3
5
  const scratchToCenter = new Vec3();
4
- const raySphereEntryDistance = (origin, dir, center, radius)=>{
5
- const m = scratchToCenter.sub2(origin, center);
6
- const b = m.dot(dir);
7
- const c = m.dot(m) - radius * radius;
8
- if (c > 0 && b > 0) return null;
9
- const disc = b * b - c;
10
- if (disc < 0) return null;
11
- const t = -b - Math.sqrt(disc);
12
- return t < 0 ? 0 : t;
6
+
7
+ /**
8
+ * Distance along a (normalized) ray to where it first enters the sphere, or null if it never does.
9
+ * A ray starting inside the sphere returns 0.
10
+ */
11
+ const raySphereEntryDistance = (origin, dir, center, radius) => {
12
+ const m = scratchToCenter.sub2(origin, center);
13
+ const b = m.dot(dir);
14
+ const c = m.dot(m) - radius * radius;
15
+ if (c > 0 && b > 0) {
16
+ return null;
17
+ }
18
+ const disc = b * b - c;
19
+ if (disc < 0) {
20
+ return null;
21
+ }
22
+ const t = -b - Math.sqrt(disc);
23
+ return t < 0 ? 0 : t;
13
24
  };
14
- const nearestAnnotationHit = (origin, direction, candidates)=>{
15
- const dir = scratchDir.copy(direction).normalize();
16
- let bestIndex = null;
17
- let bestDistance = 1 / 0;
18
- for(let index = 0; index < candidates.length; index++){
19
- const candidate = candidates[index];
20
- const distance = raySphereEntryDistance(origin, dir, candidate.position, candidate.radius);
21
- if (null !== distance && distance < bestDistance) {
22
- bestDistance = distance;
23
- bestIndex = index;
24
- }
25
+
26
+ /**
27
+ * Index of the closest candidate sphere the ray enters, or null if it hits none.
28
+ */
29
+ const nearestAnnotationHit = (origin, direction, candidates) => {
30
+ const dir = scratchDir.copy(direction).normalize();
31
+ let bestIndex = null;
32
+ let bestDistance = Infinity;
33
+ for (let index = 0; index < candidates.length; index++) {
34
+ const candidate = candidates[index];
35
+ const distance = raySphereEntryDistance(origin, dir, candidate.position, candidate.radius);
36
+ if (distance !== null && distance < bestDistance) {
37
+ bestDistance = distance;
38
+ bestIndex = index;
25
39
  }
26
- return bestIndex;
40
+ }
41
+ return bestIndex;
27
42
  };
43
+
28
44
  export { nearestAnnotationHit };
@@ -1,11 +1,21 @@
1
+ /**
2
+ * Latches per input source whether a press may count toward a hold. A source arms itself the
3
+ * moment it is seen released, and stays armed from then on - so a button already down when
4
+ * tracking starts is ignored until it is released once, and one controller held down can't stop
5
+ * another from arming and counting. Callers must poll every frame for every source they care
6
+ * about; a press and release between polls is missed.
7
+ */
1
8
  class ButtonArmingLatch {
2
- _armed = new WeakSet();
3
- trackPress(inputSource, pressed) {
4
- if (!pressed) {
5
- this._armed.add(inputSource);
6
- return false;
7
- }
8
- return this._armed.has(inputSource);
9
+ _armed = new WeakSet();
10
+
11
+ /** Records one frame for `inputSource` and returns whether its current press may count. */
12
+ trackPress(inputSource, pressed) {
13
+ if (!pressed) {
14
+ this._armed.add(inputSource);
15
+ return false;
9
16
  }
17
+ return this._armed.has(inputSource);
18
+ }
10
19
  }
20
+
11
21
  export { ButtonArmingLatch };
@@ -1,31 +1,45 @@
1
1
  const INITIAL_HOLD_STATE = {
2
- elapsed: 0,
3
- armed: false
2
+ elapsed: 0,
3
+ armed: false
4
4
  };
5
5
  const RELEASED_STATE = {
6
- elapsed: 0,
7
- armed: true
6
+ elapsed: 0,
7
+ armed: true
8
8
  };
9
- const advanceHold = (prev, held, dt, threshold)=>{
10
- if (!held) return {
11
- state: RELEASED_STATE,
12
- progress: 0,
13
- justFired: false
14
- };
15
- if (!prev.armed) return {
16
- state: prev,
17
- progress: 0,
18
- justFired: false
9
+
10
+ /**
11
+ * Advances a press-and-hold timer by one frame. `held` is whether the button is down right now.
12
+ *
13
+ * A hold only counts once a release has been seen, so a button that is already down when tracking
14
+ * starts does nothing until the user lets go - otherwise entering a session with a thumb on the
15
+ * button would fire `threshold` seconds later. `justFired` is true only on the frame elapsed first
16
+ * reaches `threshold`, so continuing to hold fires once.
17
+ */
18
+ const advanceHold = (prev, held, dt, threshold) => {
19
+ if (!held) {
20
+ return {
21
+ state: RELEASED_STATE,
22
+ progress: 0,
23
+ justFired: false
19
24
  };
20
- const elapsed = Math.min(prev.elapsed + dt, threshold);
21
- const justFired = prev.elapsed < threshold && elapsed >= threshold;
25
+ }
26
+ if (!prev.armed) {
22
27
  return {
23
- state: {
24
- elapsed,
25
- armed: true
26
- },
27
- progress: elapsed / threshold,
28
- justFired
28
+ state: prev,
29
+ progress: 0,
30
+ justFired: false
29
31
  };
32
+ }
33
+ const elapsed = Math.min(prev.elapsed + dt, threshold);
34
+ const justFired = prev.elapsed < threshold && elapsed >= threshold;
35
+ return {
36
+ state: {
37
+ elapsed,
38
+ armed: true
39
+ },
40
+ progress: elapsed / threshold,
41
+ justFired
42
+ };
30
43
  };
44
+
31
45
  export { INITIAL_HOLD_STATE, advanceHold };
@@ -1,27 +1,41 @@
1
+ /** Backing disc radius as a fraction of the button's half-size. Matches the shader's pie radius. */
1
2
  const DISC_RADIUS = 0.92;
3
+
4
+ /** Glyph arm reach and stroke width, as fractions of the drawn size. */
2
5
  const GLYPH_ARM = 0.24;
3
6
  const GLYPH_WIDTH = 0.09;
4
- const drawExitButtonMask = (ctx, size)=>{
5
- const c = size / 2;
6
- ctx.save();
7
- ctx.globalCompositeOperation = 'source-over';
8
- ctx.fillStyle = '#000000';
9
- ctx.fillRect(0, 0, size, size);
10
- ctx.globalCompositeOperation = 'lighter';
11
- ctx.fillStyle = '#ff0000';
12
- ctx.beginPath();
13
- ctx.arc(c, c, c * DISC_RADIUS, 0, 2 * Math.PI);
14
- ctx.fill();
15
- const arm = size * GLYPH_ARM;
16
- ctx.strokeStyle = '#00ff00';
17
- ctx.lineWidth = size * GLYPH_WIDTH;
18
- ctx.lineCap = 'round';
19
- ctx.beginPath();
20
- ctx.moveTo(c - arm, c - arm);
21
- ctx.lineTo(c + arm, c + arm);
22
- ctx.moveTo(c + arm, c - arm);
23
- ctx.lineTo(c - arm, c + arm);
24
- ctx.stroke();
25
- ctx.restore();
7
+
8
+ /**
9
+ * Bakes the exit button's static artwork into a two-channel mask: red covers the backing disc, green
10
+ * the X glyph. The shader composites the progress sweep between the two, which is what keeps the
11
+ * glyph legible over the fill.
12
+ *
13
+ * The channels have to stay independent, so the glyph is drawn additively rather than over the disc,
14
+ * and an opaque base goes down first. Uniform full alpha means the mask survives the texture upload
15
+ * whether or not alpha ends up premultiplied, and antialiased edges keep their coverage.
16
+ */
17
+ const drawExitButtonMask = (ctx, size) => {
18
+ const c = size / 2;
19
+ ctx.save();
20
+ ctx.globalCompositeOperation = 'source-over';
21
+ ctx.fillStyle = '#000000';
22
+ ctx.fillRect(0, 0, size, size);
23
+ ctx.globalCompositeOperation = 'lighter';
24
+ ctx.fillStyle = '#ff0000';
25
+ ctx.beginPath();
26
+ ctx.arc(c, c, c * DISC_RADIUS, 0, Math.PI * 2);
27
+ ctx.fill();
28
+ const arm = size * GLYPH_ARM;
29
+ ctx.strokeStyle = '#00ff00';
30
+ ctx.lineWidth = size * GLYPH_WIDTH;
31
+ ctx.lineCap = 'round';
32
+ ctx.beginPath();
33
+ ctx.moveTo(c - arm, c - arm);
34
+ ctx.lineTo(c + arm, c + arm);
35
+ ctx.moveTo(c + arm, c - arm);
36
+ ctx.lineTo(c - arm, c + arm);
37
+ ctx.stroke();
38
+ ctx.restore();
26
39
  };
40
+
27
41
  export { drawExitButtonMask };