@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,120 +1,157 @@
1
- import { Entity, LAYERID_IMMEDIATE, MeshInstance, Quat, Script, Vec3, XRTYPE_VR } from "playcanvas";
2
- import { HOTSPOT_HALF_EXTENT, createAnnotationCandidateBuffer } from "./xr-annotation-candidates.js";
3
- import { nearestAnnotationHit } from "./xr-annotation-targeting.js";
4
- import { INITIAL_DWELL_STATE, advanceDwell } from "./xr-gaze-dwell-state.js";
5
- import { pieShaderProgress } from "./xr-progress-pie.js";
6
- import { createProgressPieMaterial, emptyMaskTexture, progressPieQuadMesh } from "./xr-progress-pie-material.js";
1
+ import { Vec3, Script, Quat, XRTYPE_VR, MeshInstance, Entity, LAYERID_IMMEDIATE } from 'playcanvas';
2
+ import { createAnnotationCandidateBuffer } from './xr-annotation-candidates.js';
3
+ import { nearestAnnotationHit } from './xr-annotation-targeting.js';
4
+ import { INITIAL_DWELL_STATE, advanceDwell } from './xr-gaze-dwell-state.js';
5
+ import { pieShaderProgress } from './xr-progress-pie.js';
6
+ import { emptyMaskTexture, createProgressPieMaterial, progressPieQuadMesh } from './xr-progress-pie-material.js';
7
+ import { HOTSPOT_HALF_EXTENT } from './xr-annotation-candidate-buffer.js';
8
+
9
+ /** Multiplier on the hotspot's world radius for gaze targeting. */
7
10
  const GAZE_HIT_RADIUS_PAD = 5;
11
+
12
+ /** Seconds of continuous gaze required to open an annotation. */
8
13
  const DWELL_SECONDS = 1.25;
14
+
15
+ /** Progress-pie quad radius as a multiple of the hotspot's rendered half-extent (covers the hotspot). */
9
16
  const PIE_RADIUS_SCALE = 1.25;
10
17
  const LOCAL_FORWARD = new Vec3(0, 0, -1);
11
18
  class XrGazeDwell extends Script {
12
- static scriptName = 'xrGazeDwell';
13
- activeIndex = -1;
14
- _dwell = INITIAL_DWELL_STATE;
15
- _candidates = createAnnotationCandidateBuffer();
16
- _pieEntity;
17
- _pieMaterial;
18
- _pieMesh;
19
- _pieMask;
20
- _headPos = new Vec3();
21
- _headRot = new Quat();
22
- _viewOffset = new Vec3();
23
- _forward = new Vec3();
24
- _scratchScale = new Vec3();
25
- _isVrActive = ()=>this.app.xr?.active === true && this.app.xr?.type === XRTYPE_VR;
26
- _onXrStart = ()=>{
27
- this._dwell = INITIAL_DWELL_STATE;
28
- this._setPieEnabled(this._isVrActive());
29
- };
30
- _onXrEnd = ()=>{
31
- this._dwell = INITIAL_DWELL_STATE;
32
- this._pieMaterial?.setParameter('uProgress', 0);
33
- this._setPieEnabled(false);
34
- };
35
- _setPieEnabled(enabled) {
36
- if (this._pieEntity) this._pieEntity.enabled = enabled;
19
+ static scriptName = 'xrGazeDwell';
20
+
21
+ /** Index of the annotation whose panel is currently open, or -1. Excluded from gaze targets so
22
+ * dwell opens OTHER annotations while one is open (and never re-dwells the open one). */
23
+ activeIndex = -1;
24
+ _dwell = INITIAL_DWELL_STATE;
25
+ _candidates = createAnnotationCandidateBuffer();
26
+ _headPos = new Vec3();
27
+ _headRot = new Quat();
28
+ _viewOffset = new Vec3();
29
+ _forward = new Vec3();
30
+ _scratchScale = new Vec3();
31
+ _isVrActive = () => this.app.xr?.active === true && this.app.xr?.type === XRTYPE_VR;
32
+
33
+ /**
34
+ * A render component hands its mesh instances to a layer only when it flips from disabled to
35
+ * enabled, and hands over nothing if the layer cannot be resolved yet. The session events are the
36
+ * points where the layers are known to exist, so the pie flips there: start registers it, and end
37
+ * restores the transition for the next session.
38
+ */
39
+ _onXrStart = () => {
40
+ this._dwell = INITIAL_DWELL_STATE;
41
+ this._setPieEnabled(this._isVrActive());
42
+ };
43
+ _onXrEnd = () => {
44
+ this._dwell = INITIAL_DWELL_STATE;
45
+ this._pieMaterial?.setParameter('uProgress', 0);
46
+ this._setPieEnabled(false);
47
+ };
48
+ _setPieEnabled(enabled) {
49
+ if (this._pieEntity) {
50
+ this._pieEntity.enabled = enabled;
37
51
  }
38
- initialize() {
39
- this._pieMask = emptyMaskTexture(this.app.graphicsDevice);
40
- this._pieMaterial = createProgressPieMaterial({
41
- uniqueName: 'xr-gaze-dwell-pie',
42
- maskMap: this._pieMask
43
- });
44
- this._pieMesh = progressPieQuadMesh(this.app.graphicsDevice, HOTSPOT_HALF_EXTENT);
45
- const meshInstance = new MeshInstance(this._pieMesh, this._pieMaterial);
46
- meshInstance.cull = false;
47
- this._pieEntity = new Entity('xr-gaze-dwell-pie');
48
- this._pieEntity.addComponent('render', {
49
- meshInstances: [
50
- meshInstance
51
- ],
52
- layers: [
53
- LAYERID_IMMEDIATE
54
- ]
55
- });
56
- this._pieEntity.enabled = false;
57
- this.entity.addChild(this._pieEntity);
58
- this._setPieEnabled(this._isVrActive());
59
- this.app.xr?.on('start', this._onXrStart);
60
- this.app.xr?.on('end', this._onXrEnd);
52
+ }
53
+ initialize() {
54
+ this._pieMask = emptyMaskTexture(this.app.graphicsDevice);
55
+ this._pieMaterial = createProgressPieMaterial({
56
+ uniqueName: 'xr-gaze-dwell-pie',
57
+ maskMap: this._pieMask
58
+ });
59
+ this._pieMesh = progressPieQuadMesh(this.app.graphicsDevice, HOTSPOT_HALF_EXTENT);
60
+ const meshInstance = new MeshInstance(this._pieMesh, this._pieMaterial);
61
+
62
+ // Never frustum-culled. The quad only takes a position once a sweep starts, and until then sits
63
+ // at the origin where culling would drop it from the render list - taking the shader compile it
64
+ // stays enabled for along with it. An always-submitted quad that discards every fragment costs
65
+ // less than that compile landing mid-gaze.
66
+ meshInstance.cull = false;
67
+ this._pieEntity = new Entity('xr-gaze-dwell-pie');
68
+ this._pieEntity.addComponent('render', {
69
+ meshInstances: [meshInstance],
70
+ layers: [LAYERID_IMMEDIATE]
71
+ });
72
+ this._pieEntity.enabled = false;
73
+ this.entity.addChild(this._pieEntity);
74
+
75
+ // Both orders happen: the walkthrough can mount into a session that has already started (as it
76
+ // does when VR is entered from outside the canvas), or mount first and wait for one.
77
+ this._setPieEnabled(this._isVrActive());
78
+ this.app.xr?.on('start', this._onXrStart);
79
+ this.app.xr?.on('end', this._onXrEnd);
80
+ }
81
+ update(dt) {
82
+ if (!this._pieEntity || !this._pieMaterial) {
83
+ return;
61
84
  }
62
- update(dt) {
63
- if (!this._pieEntity || !this._pieMaterial) return;
64
- if (!this._isVrActive() || !this._trackHead()) {
65
- this._dwell = INITIAL_DWELL_STATE;
66
- this._pieMaterial.setParameter('uProgress', 0);
67
- return;
68
- }
69
- const candidates = this._candidates.collect(this.app, GAZE_HIT_RADIUS_PAD, `annotation-${this.activeIndex}`);
70
- this._headRot.transformVector(LOCAL_FORWARD, this._forward);
71
- const target = nearestAnnotationHit(this._headPos, this._forward, candidates);
72
- const result = advanceDwell(this._dwell, target, dt, DWELL_SECONDS);
73
- this._dwell = result.state;
74
- const progress = pieShaderProgress(result.progress);
75
- this._pieMaterial.setParameter('uProgress', progress);
76
- if (progress > 0 && null !== target) this._placePie(candidates[target].entity);
77
- if (result.justOpened && null !== target) {
78
- const { entity, script } = candidates[target];
79
- const camera = this.app.root.findComponent('camera');
80
- const screen = camera?.worldToScreen(entity.getPosition());
81
- script.onVrOpenCallback(screen?.x ?? 0, screen?.y ?? 0);
82
- }
85
+ if (!this._isVrActive() || !this._trackHead()) {
86
+ this._dwell = INITIAL_DWELL_STATE;
87
+ this._pieMaterial.setParameter('uProgress', 0);
88
+ return;
83
89
  }
84
- _trackHead() {
85
- const views = this.app.xr?.views?.list;
86
- if (!views || 0 === views.length) return false;
87
- this._headPos.set(0, 0, 0);
88
- for (const view of views){
89
- view.viewInvOffMat.getTranslation(this._viewOffset);
90
- this._headPos.add(this._viewOffset);
91
- }
92
- this._headPos.mulScalar(1 / views.length);
93
- this._headRot.setFromMat4(views[0].viewInvOffMat);
94
- return true;
90
+ const candidates = this._candidates.collect(this.app, GAZE_HIT_RADIUS_PAD, `annotation-${this.activeIndex}`);
91
+ this._headRot.transformVector(LOCAL_FORWARD, this._forward);
92
+ const target = nearestAnnotationHit(this._headPos, this._forward, candidates);
93
+ const result = advanceDwell(this._dwell, target, dt, DWELL_SECONDS);
94
+ this._dwell = result.state;
95
+ const progress = pieShaderProgress(result.progress);
96
+ this._pieMaterial.setParameter('uProgress', progress);
97
+ if (progress > 0 && target !== null) {
98
+ this._placePie(candidates[target].entity);
95
99
  }
96
- _placePie(entity) {
97
- if (!this._pieEntity) return;
98
- entity.getWorldTransform().getScale(this._scratchScale);
99
- const scale = this._scratchScale.x * PIE_RADIUS_SCALE;
100
- this._pieEntity.setLocalScale(scale, scale, scale);
101
- this._pieEntity.setPosition(entity.getPosition());
102
- this._pieEntity.setRotation(this._headRot);
100
+ if (result.justOpened && target !== null) {
101
+ const {
102
+ entity,
103
+ script
104
+ } = candidates[target];
105
+ const camera = this.app.root.findComponent('camera');
106
+ const screen = camera?.worldToScreen(entity.getPosition());
107
+ script.onVrOpenCallback(screen?.x ?? 0, screen?.y ?? 0);
103
108
  }
104
- destroy() {
105
- this.app.xr?.off('start', this._onXrStart);
106
- this.app.xr?.off('end', this._onXrEnd);
107
- if (this._pieEntity) {
108
- if (this._pieEntity.render) this._pieEntity.render.meshInstances = [];
109
- this._pieEntity.destroy();
110
- this._pieEntity = void 0;
111
- }
112
- this._pieMesh?.destroy();
113
- this._pieMaterial?.destroy();
114
- this._pieMask?.destroy();
115
- this._pieMesh = void 0;
116
- this._pieMaterial = void 0;
117
- this._pieMask = void 0;
109
+ }
110
+ _trackHead() {
111
+ const views = this.app.xr?.views?.list;
112
+ if (!views || views.length === 0) {
113
+ return false;
118
114
  }
115
+ this._headPos.set(0, 0, 0);
116
+ for (const view of views) {
117
+ view.viewInvOffMat.getTranslation(this._viewOffset);
118
+ this._headPos.add(this._viewOffset);
119
+ }
120
+ this._headPos.mulScalar(1 / views.length);
121
+ this._headRot.setFromMat4(views[0].viewInvOffMat);
122
+ return true;
123
+ }
124
+
125
+ /** Sits the pie on the hotspot, square to the head. */
126
+ _placePie(entity) {
127
+ if (!this._pieEntity) {
128
+ return;
129
+ }
130
+
131
+ // The unit quad has half-extent HOTSPOT_HALF_EXTENT, so scale it to reach the desired world size.
132
+ entity.getWorldTransform().getScale(this._scratchScale);
133
+ const scale = this._scratchScale.x * PIE_RADIUS_SCALE;
134
+ this._pieEntity.setLocalScale(scale, scale, scale);
135
+ this._pieEntity.setPosition(entity.getPosition());
136
+ this._pieEntity.setRotation(this._headRot);
137
+ }
138
+ destroy() {
139
+ this.app.xr?.off('start', this._onXrStart);
140
+ this.app.xr?.off('end', this._onXrEnd);
141
+ if (this._pieEntity) {
142
+ if (this._pieEntity.render) {
143
+ this._pieEntity.render.meshInstances = [];
144
+ }
145
+ this._pieEntity.destroy();
146
+ this._pieEntity = undefined;
147
+ }
148
+ this._pieMesh?.destroy();
149
+ this._pieMaterial?.destroy();
150
+ this._pieMask?.destroy();
151
+ this._pieMesh = undefined;
152
+ this._pieMaterial = undefined;
153
+ this._pieMask = undefined;
154
+ }
119
155
  }
156
+
120
157
  export { XrGazeDwell };
@@ -1,15 +1,27 @@
1
- import { VrAnnotationPanel } from "./vr-annotation-panel.js";
2
- import { HIT_RADIUS_PAD, collectAnnotationHitCandidates } from "./xr-annotation-candidates.js";
3
- import { nearestAnnotationHit } from "./xr-annotation-targeting.js";
4
- import { XrExitButton } from "./xr-exit-button.js";
5
- const rayHitsAnnotationHotspot = (app, origin, direction)=>null !== nearestAnnotationHit(origin, direction, collectAnnotationHitCandidates(app, HIT_RADIUS_PAD));
6
- const rayHitsAnnotationPanel = (app, origin, direction)=>{
7
- const script = app.root.findByName('vr-annotation-panel')?.script?.get(VrAnnotationPanel.scriptName);
8
- return 'function' == typeof script?.rayHitsPanel && true === script.rayHitsPanel(origin, direction);
1
+ import { VrAnnotationPanel } from './vr-annotation-panel.js';
2
+ import { collectAnnotationHitCandidates, HIT_RADIUS_PAD } from './xr-annotation-candidates.js';
3
+ import { nearestAnnotationHit } from './xr-annotation-targeting.js';
4
+ import { XrExitButton } from './xr-exit-button.js';
5
+
6
+ /** True when the ray points at an openable annotation hotspot. */
7
+ const rayHitsAnnotationHotspot = (app, origin, direction) => nearestAnnotationHit(origin, direction, collectAnnotationHitCandidates(app, HIT_RADIUS_PAD)) !== null;
8
+
9
+ /** True when the ray points at the open VR annotation panel. False when no panel is mounted. */
10
+ const rayHitsAnnotationPanel = (app, origin, direction) => {
11
+ const script = app.root.findByName('vr-annotation-panel')?.script?.get(VrAnnotationPanel.scriptName);
12
+ return typeof script?.rayHitsPanel === 'function' && script.rayHitsPanel(origin, direction) === true;
9
13
  };
10
- const rayHitsExitButton = (app, origin, direction)=>{
11
- const script = app.root.findByName('xr-exit-button')?.script?.get(XrExitButton.scriptName);
12
- return 'function' == typeof script?.rayHitsButton && true === script.rayHitsButton(origin, direction);
14
+
15
+ /** True when the ray points at the VR exit button. */
16
+ const rayHitsExitButton = (app, origin, direction) => {
17
+ const script = app.root.findByName('xr-exit-button')?.script?.get(XrExitButton.scriptName);
18
+ return typeof script?.rayHitsButton === 'function' && script.rayHitsButton(origin, direction) === true;
13
19
  };
14
- const rayHitsInteractiveUi = (app, origin, direction)=>rayHitsAnnotationHotspot(app, origin, direction) || rayHitsAnnotationPanel(app, origin, direction) || rayHitsExitButton(app, origin, direction);
20
+
21
+ /**
22
+ * True when the ray points at anything the trigger is meant to operate. Teleport consults this so a
23
+ * select that lands on UI doesn't also move the rig.
24
+ */
25
+ const rayHitsInteractiveUi = (app, origin, direction) => rayHitsAnnotationHotspot(app, origin, direction) || rayHitsAnnotationPanel(app, origin, direction) || rayHitsExitButton(app, origin, direction);
26
+
15
27
  export { rayHitsAnnotationHotspot, rayHitsAnnotationPanel, rayHitsExitButton, rayHitsInteractiveUi };
@@ -1,19 +1,26 @@
1
- import { Color, Script, Vec3 } from "playcanvas";
1
+ import { Color, Script, Vec3 } from 'playcanvas';
2
+
3
+ /** Length of the drawn pointer ray, in world meters. */
2
4
  const RAY_LENGTH = 5;
5
+
6
+ /** Default ray colour (cyan) — bright enough to read against the scene. */
3
7
  const DEFAULT_RAY_COLOR = new Color(0.25, 0.8, 1);
4
8
  class XrPointerRay extends Script {
5
- static scriptName = 'xrPointerRay';
6
- color = DEFAULT_RAY_COLOR.clone();
7
- _end = new Vec3();
8
- update() {
9
- if (this.app.xr?.active !== true) return;
10
- const sources = this.app.xr?.input?.inputSources ?? [];
11
- for (const source of sources){
12
- const origin = source.getOrigin();
13
- const direction = source.getDirection();
14
- this._end.copy(direction).mulScalar(RAY_LENGTH).add(origin);
15
- this.app.drawLine(origin, this._end, this.color, false);
16
- }
9
+ static scriptName = 'xrPointerRay';
10
+ color = DEFAULT_RAY_COLOR.clone();
11
+ _end = new Vec3();
12
+ update() {
13
+ if (this.app.xr?.active !== true) {
14
+ return;
17
15
  }
16
+ const sources = this.app.xr?.input?.inputSources ?? [];
17
+ for (const source of sources) {
18
+ const origin = source.getOrigin();
19
+ const direction = source.getDirection();
20
+ this._end.copy(direction).mulScalar(RAY_LENGTH).add(origin);
21
+ this.app.drawLine(origin, this._end, this.color, false);
22
+ }
23
+ }
18
24
  }
25
+
19
26
  export { DEFAULT_RAY_COLOR, XrPointerRay };
@@ -1,6 +1,11 @@
1
- import { ADDRESS_CLAMP_TO_EDGE, BLEND_NORMAL, CULLFACE_NONE, FILTER_NEAREST, Mesh, PIXELFORMAT_RGBA8, SEMANTIC_POSITION, SEMANTIC_TEXCOORD0, ShaderMaterial, Texture } from "playcanvas";
1
+ import { ShaderMaterial, SEMANTIC_TEXCOORD0, SEMANTIC_POSITION, BLEND_NORMAL, CULLFACE_NONE, Texture, ADDRESS_CLAMP_TO_EDGE, FILTER_NEAREST, PIXELFORMAT_RGBA8, Mesh } from 'playcanvas';
2
+
3
+ /** Pie radius as a fraction of the quad's half-extent. */
2
4
  const PIE_RADIUS = 0.92;
3
- const pieVertexGLSL = `
5
+
6
+ // GLSL only, for the same reason as BoundaryWallScript: @playcanvas/react's Application defaults to
7
+ // deviceTypes [DEVICETYPE_WEBGL2], so WGSL variants would never be compiled here.
8
+ const pieVertexGLSL = /* glsl */`
4
9
  attribute vec3 vertex_position;
5
10
  attribute vec2 aUv0;
6
11
 
@@ -14,7 +19,16 @@ const pieVertexGLSL = `
14
19
  gl_Position = matrix_viewProjection * matrix_model * vec4(vertex_position, 1.0);
15
20
  }
16
21
  `;
17
- const pieFragmentGLSL = `
22
+
23
+ /**
24
+ * Draws the progress sweep analytically from `uProgress`, so animating it costs one uniform write
25
+ * per frame.
26
+ *
27
+ * `uMaskMap` carries whatever static artwork sits around the pie, baked once by the caller: red is
28
+ * a mask for a backing disc drawn under the sweep, green a mask for a glyph drawn over it. Callers
29
+ * with no such artwork bind a transparent pixel and get the bare pie.
30
+ */
31
+ const pieFragmentGLSL = /* glsl */`
18
32
  uniform float uProgress;
19
33
  uniform vec3 uTrackColor;
20
34
  uniform float uTrackAlpha;
@@ -83,121 +97,78 @@ const pieFragmentGLSL = `
83
97
  gl_FragColor = color;
84
98
  }
85
99
  `;
86
- const progressPieQuadMesh = (device, halfExtent)=>{
87
- const h = halfExtent;
88
- const mesh = new Mesh(device);
89
- mesh.setPositions([
90
- -h,
91
- -h,
92
- 0,
93
- h,
94
- -h,
95
- 0,
96
- h,
97
- h,
98
- 0,
99
- -h,
100
- h,
101
- 0
102
- ]);
103
- mesh.setNormals([
104
- 0,
105
- 0,
106
- 1,
107
- 0,
108
- 0,
109
- 1,
110
- 0,
111
- 0,
112
- 1,
113
- 0,
114
- 0,
115
- 1
116
- ]);
117
- mesh.setUvs(0, [
118
- 0,
119
- 1,
120
- 1,
121
- 1,
122
- 1,
123
- 0,
124
- 0,
125
- 0
126
- ]);
127
- mesh.setIndices([
128
- 0,
129
- 1,
130
- 2,
131
- 0,
132
- 2,
133
- 3
134
- ]);
135
- mesh.update();
136
- return mesh;
100
+
101
+ /** A camera-facing unit quad with y-down UVs, sized to `halfExtent` in local units. */
102
+ const progressPieQuadMesh = (device, halfExtent) => {
103
+ const h = halfExtent;
104
+ const mesh = new Mesh(device);
105
+ mesh.setPositions([-h, -h, 0, h, -h, 0, h, h, 0, -h, h, 0]);
106
+ mesh.setNormals([0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1]);
107
+ mesh.setUvs(0, [0, 1, 1, 1, 1, 0, 0, 0]);
108
+ mesh.setIndices([0, 1, 2, 0, 2, 3]);
109
+ mesh.update();
110
+ return mesh;
137
111
  };
138
- const emptyMaskTexture = (device)=>{
139
- const texture = new Texture(device, {
140
- name: 'xr-progress-pie-empty-mask',
141
- width: 1,
142
- height: 1,
143
- format: PIXELFORMAT_RGBA8,
144
- mipmaps: false,
145
- minFilter: FILTER_NEAREST,
146
- magFilter: FILTER_NEAREST,
147
- addressU: ADDRESS_CLAMP_TO_EDGE,
148
- addressV: ADDRESS_CLAMP_TO_EDGE
149
- });
150
- const pixels = texture.lock();
151
- pixels.set([
152
- 0,
153
- 0,
154
- 0,
155
- 0
156
- ]);
157
- texture.unlock();
158
- return texture;
112
+
113
+ /**
114
+ * A single transparent texel, for pies that carry no static artwork of their own.
115
+ *
116
+ * The filters are set explicitly because the default minification filter samples a mipmap chain,
117
+ * which a single-level texture does not have. That combination is incomplete in WebGL, and what a
118
+ * shader reads from an incomplete texture is not something to rely on.
119
+ */
120
+ const emptyMaskTexture = device => {
121
+ const texture = new Texture(device, {
122
+ name: 'xr-progress-pie-empty-mask',
123
+ width: 1,
124
+ height: 1,
125
+ format: PIXELFORMAT_RGBA8,
126
+ mipmaps: false,
127
+ minFilter: FILTER_NEAREST,
128
+ magFilter: FILTER_NEAREST,
129
+ addressU: ADDRESS_CLAMP_TO_EDGE,
130
+ addressV: ADDRESS_CLAMP_TO_EDGE
131
+ });
132
+ const pixels = texture.lock();
133
+ pixels.set([0, 0, 0, 0]);
134
+ texture.unlock();
135
+ return texture;
159
136
  };
160
- const createProgressPieMaterial = ({ uniqueName, maskMap, discColor = [
161
- 0.08,
162
- 0.08,
163
- 0.08
164
- ], discAlpha = 0 })=>{
165
- const material = new ShaderMaterial({
166
- uniqueName,
167
- vertexGLSL: pieVertexGLSL,
168
- fragmentGLSL: pieFragmentGLSL,
169
- attributes: {
170
- vertex_position: SEMANTIC_POSITION,
171
- aUv0: SEMANTIC_TEXCOORD0
172
- }
173
- });
174
- material.blendType = BLEND_NORMAL;
175
- material.cull = CULLFACE_NONE;
176
- material.depthTest = false;
177
- material.depthWrite = false;
178
- material.setParameter('uProgress', 0);
179
- material.setParameter('uTrackColor', [
180
- 1,
181
- 1,
182
- 1
183
- ]);
184
- material.setParameter('uTrackAlpha', 0.18);
185
- material.setParameter('uWedgeColor', [
186
- 64 / 255,
187
- 200 / 255,
188
- 1
189
- ]);
190
- material.setParameter('uWedgeAlpha', 0.85);
191
- material.setParameter('uDiscColor', discColor);
192
- material.setParameter('uDiscAlpha', discAlpha);
193
- material.setParameter('uGlyphColor', [
194
- 1,
195
- 1,
196
- 1
197
- ]);
198
- material.setParameter('uOpacity', 1);
199
- material.setParameter('uMaskMap', maskMap);
200
- material.update();
201
- return material;
137
+ /**
138
+ * Material for a progress pie. Everything that animates rides on the `uProgress` uniform, so a
139
+ * running sweep costs one uniform write per frame and no texture work at all.
140
+ */
141
+ const createProgressPieMaterial = ({
142
+ uniqueName,
143
+ maskMap,
144
+ discColor = [0.08, 0.08, 0.08],
145
+ discAlpha = 0
146
+ }) => {
147
+ const material = new ShaderMaterial({
148
+ uniqueName,
149
+ vertexGLSL: pieVertexGLSL,
150
+ fragmentGLSL: pieFragmentGLSL,
151
+ attributes: {
152
+ vertex_position: SEMANTIC_POSITION,
153
+ aUv0: SEMANTIC_TEXCOORD0
154
+ }
155
+ });
156
+ material.blendType = BLEND_NORMAL;
157
+ material.cull = CULLFACE_NONE;
158
+ material.depthTest = false;
159
+ material.depthWrite = false;
160
+ material.setParameter('uProgress', 0);
161
+ material.setParameter('uTrackColor', [1, 1, 1]);
162
+ material.setParameter('uTrackAlpha', 0.18);
163
+ material.setParameter('uWedgeColor', [64 / 255, 200 / 255, 1]);
164
+ material.setParameter('uWedgeAlpha', 0.85);
165
+ material.setParameter('uDiscColor', discColor);
166
+ material.setParameter('uDiscAlpha', discAlpha);
167
+ material.setParameter('uGlyphColor', [1, 1, 1]);
168
+ material.setParameter('uOpacity', 1);
169
+ material.setParameter('uMaskMap', maskMap);
170
+ material.update();
171
+ return material;
202
172
  };
173
+
203
174
  export { createProgressPieMaterial, emptyMaskTexture, progressPieQuadMesh };
@@ -1,2 +1,11 @@
1
- const pieShaderProgress = (progress)=>progress > 0 && progress < 1 ? progress : 0;
1
+ /**
2
+ * Progress to hand the pie shader, where 0 draws nothing at all: an untouched target wears no faint
3
+ * track, and a completed one has already opened its annotation or ended the session.
4
+ *
5
+ * Visibility rides on this value so the quad can stay enabled for the whole session. A mesh
6
+ * instance compiles its shader variant the first time it is drawn, and that compile has to land
7
+ * somewhere other than the frame a pie first appears.
8
+ */
9
+ const pieShaderProgress = progress => progress > 0 && progress < 1 ? progress : 0;
10
+
2
11
  export { pieShaderProgress };
@@ -1,18 +1,32 @@
1
- import { Vec3 } from "playcanvas";
2
- const rayQuadHit = (origin, direction, center, right, up, halfWidth, halfHeight)=>{
3
- const dir = direction.clone().normalize();
4
- const normal = new Vec3().cross(right, up);
5
- const denom = dir.dot(normal);
6
- if (Math.abs(denom) < 1e-6) return null;
7
- const t = new Vec3().sub2(center, origin).dot(normal) / denom;
8
- if (t < 0) return null;
9
- const offset = new Vec3().copy(dir).mulScalar(t).add(origin).sub(center);
10
- const u = offset.dot(right) / halfWidth;
11
- const v = offset.dot(up) / halfHeight;
12
- if (u < -1 || u > 1 || v < -1 || v > 1) return null;
13
- return {
14
- u,
15
- v
16
- };
1
+ import { Vec3 } from 'playcanvas';
2
+
3
+ /**
4
+ * Intersects the ray with the rectangle centred at `center` and spanned by unit axes `right`/`up`,
5
+ * returning the hit in normalized quad coordinates (u,v in [-1,1]) or null. `u`/`v` divide the
6
+ * in-plane offset by `halfWidth`/`halfHeight`. Null when the ray is parallel to the plane, the
7
+ * plane is behind the origin, or the hit lands outside the rectangle.
8
+ */
9
+ const rayQuadHit = (origin, direction, center, right, up, halfWidth, halfHeight) => {
10
+ const dir = direction.clone().normalize();
11
+ const normal = new Vec3().cross(right, up);
12
+ const denom = dir.dot(normal);
13
+ if (Math.abs(denom) < 1e-6) {
14
+ return null;
15
+ }
16
+ const t = new Vec3().sub2(center, origin).dot(normal) / denom;
17
+ if (t < 0) {
18
+ return null;
19
+ }
20
+ const offset = new Vec3().copy(dir).mulScalar(t).add(origin).sub(center);
21
+ const u = offset.dot(right) / halfWidth;
22
+ const v = offset.dot(up) / halfHeight;
23
+ if (u < -1 || u > 1 || v < -1 || v > 1) {
24
+ return null;
25
+ }
26
+ return {
27
+ u,
28
+ v
29
+ };
17
30
  };
31
+
18
32
  export { rayQuadHit };