@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.
- package/components/AntSwitch/index.js +22 -19
- package/components/Autocomplete/Autocomplete.js +130 -106
- package/components/Badge/index.js +29 -28
- package/components/BusySpinner/index.js +45 -40
- package/components/Button/Button.js +47 -41
- package/components/Checkbox.js +63 -64
- package/components/Confirm/index.js +83 -74
- package/components/DatePicker/DatePicker.js +141 -138
- package/components/DialogBox/DialogBox.js +118 -111
- package/components/Divisor.js +18 -18
- package/components/Dropdown.js +94 -66
- package/components/EditableTable/index.js +271 -211
- package/components/ErrorBox/ErrorBox.js +49 -47
- package/components/FileChooser/index.js +135 -117
- package/components/FormBottomBar/index.js +84 -75
- package/components/Icon/Icon.js +20 -12
- package/components/Icon/icons/index.js +256 -256
- package/components/IconTooltip/index.js +60 -49
- package/components/Map/MapBox.js +452 -545
- package/components/Map/MapContainer.js +29 -25
- package/components/Map/MapDateSliderControl.js +64 -64
- package/components/Map/MapDrawer.js +47 -41
- package/components/Map/MapLegend.js +172 -176
- package/components/Map/MapViewStyleControl.js +63 -59
- package/components/Map/ReactMapboxWrapper.js +2 -3
- package/components/Map/index.js +180 -161
- package/components/Map/types.js +17 -12
- package/components/Map/useMaintainLayerOrder.js +26 -16
- package/components/Markdown/index.js +26 -25
- package/components/Message/Message.js +76 -73
- package/components/MultiSelect/index.js +124 -112
- package/components/Navbar/NavFooter.js +13 -9
- package/components/Navbar/NavItem.js +76 -70
- package/components/Navbar/NavSection.js +19 -17
- package/components/Navbar/Navbar.js +46 -39
- package/components/Navbar/SubNavbar.js +13 -9
- package/components/Note.js +22 -18
- package/components/OverlayModal/OverlayModal.js +63 -56
- package/components/PageForm/index.js +41 -33
- package/components/PhotoChooser/index.js +173 -165
- package/components/PhotosCarousel/PhotoSlide.js +35 -32
- package/components/PhotosCarousel/index.js +85 -80
- package/components/PhotosCarousel/usePhotosCarousel.js +76 -69
- package/components/Pill/index.js +45 -41
- package/components/PillList/index.js +24 -17
- package/components/PlacementWrapper/index.js +59 -38
- package/components/PopoverMenu/Popover.js +85 -68
- package/components/PopoverMenu/index.js +56 -51
- package/components/PopoverMultiSelect/index.js +59 -52
- package/components/ProgressCircle/ProgressCircle.js +32 -28
- package/components/RadioButton.js +19 -17
- package/components/Select/Select.js +15 -13
- package/components/Select/SelectT.js +250 -235
- package/components/Separator/index.js +17 -14
- package/components/Size.js +1 -0
- package/components/Slider/index.js +56 -43
- package/components/SummaryBox.js +72 -70
- package/components/Tabs/index.js +135 -133
- package/components/TextTruncated/index.js +130 -117
- package/components/Textfield/Textfield.js +190 -158
- package/components/Textfield/TruncatedTextArea.js +84 -70
- package/components/TimelineSlider/TimelineMarker.js +30 -25
- package/components/TimelineSlider/TimelineRail.js +11 -8
- package/components/TimelineSlider/index.js +66 -64
- package/components/TimelineSliderV2/ClusterBadge.js +29 -25
- package/components/TimelineSliderV2/TimelineDot.js +35 -31
- package/components/TimelineSliderV2/TimelineRail.js +11 -8
- package/components/TimelineSliderV2/index.js +103 -121
- package/components/TimelineSliderV2/layout.js +147 -124
- package/components/TimelineSliderV2/types.js +1 -0
- package/components/Tooltip/Tooltip.js +56 -47
- package/components/ViewPhotosDialog/index.js +35 -28
- package/components/VirtualWalkthrough/Annotation.js +207 -163
- package/components/VirtualWalkthrough/AnnotationEditPane.js +184 -198
- package/components/VirtualWalkthrough/AnnotationPanel.js +199 -187
- package/components/VirtualWalkthrough/Application.js +34 -31
- package/components/VirtualWalkthrough/AutoRotator.js +223 -85
- package/components/VirtualWalkthrough/BlockingSpinner.js +28 -26
- package/components/VirtualWalkthrough/BoundaryRing.js +34 -30
- package/components/VirtualWalkthrough/BoundaryWall.js +36 -32
- package/components/VirtualWalkthrough/CameraInfo.js +89 -100
- package/components/VirtualWalkthrough/ControlsInfoPane.js +195 -254
- package/components/VirtualWalkthrough/GradientSky.js +73 -72
- package/components/VirtualWalkthrough/SplatControls.js +221 -209
- package/components/VirtualWalkthrough/SplatCrop.js +20 -21
- package/components/VirtualWalkthrough/SplatFadeCrop.js +20 -21
- package/components/VirtualWalkthrough/SplatModel.js +85 -65
- package/components/VirtualWalkthrough/SplatRevealRain.js +46 -41
- package/components/VirtualWalkthrough/TfAnnotationManager.js +379 -197
- package/components/VirtualWalkthrough/TfXrNavigation.js +162 -73
- package/components/VirtualWalkthrough/VirtualWalkthroughViewer.js +431 -438
- package/components/VirtualWalkthrough/VrAnnotationPanel.js +22 -18
- package/components/VirtualWalkthrough/XrAnnotationInteraction.js +17 -14
- package/components/VirtualWalkthrough/XrExitButton.js +17 -14
- package/components/VirtualWalkthrough/XrGazeDwell.js +17 -14
- package/components/VirtualWalkthrough/XrPointerRay.js +17 -14
- package/components/VirtualWalkthrough/XrStartPose.js +121 -67
- package/components/VirtualWalkthrough/boundary-ring.js +117 -87
- package/components/VirtualWalkthrough/boundary-wall.js +258 -175
- package/components/VirtualWalkthrough/camera-adoption.js +64 -18
- package/components/VirtualWalkthrough/groundPlane.js +47 -32
- package/components/VirtualWalkthrough/gsplat-fade-crop-effect.js +35 -35
- package/components/VirtualWalkthrough/splatFormat.js +47 -13
- package/components/VirtualWalkthrough/useAdoptedCamera.js +44 -23
- package/components/VirtualWalkthrough/useRestartOnVr.js +37 -26
- package/components/VirtualWalkthrough/useXrRenderTuning.js +54 -33
- package/components/VirtualWalkthrough/vr-annotation-panel-layout.js +99 -67
- package/components/VirtualWalkthrough/vr-annotation-panel.js +433 -403
- package/components/VirtualWalkthrough/vr-annotation-scroll.js +28 -10
- package/components/VirtualWalkthrough/walkthrough-camera.js +329 -204
- package/components/VirtualWalkthrough/xr-annotation-candidate-buffer.js +80 -39
- package/components/VirtualWalkthrough/xr-annotation-candidates.js +18 -6
- package/components/VirtualWalkthrough/xr-annotation-interaction.js +56 -31
- package/components/VirtualWalkthrough/xr-annotation-targeting.js +38 -22
- package/components/VirtualWalkthrough/xr-button-arming.js +17 -7
- package/components/VirtualWalkthrough/xr-button-hold.js +36 -22
- package/components/VirtualWalkthrough/xr-exit-button-mask.js +36 -22
- package/components/VirtualWalkthrough/xr-exit-button.js +207 -148
- package/components/VirtualWalkthrough/xr-face-buttons.js +29 -19
- package/components/VirtualWalkthrough/xr-gaze-dwell-state.js +34 -23
- package/components/VirtualWalkthrough/xr-gaze-dwell.js +144 -107
- package/components/VirtualWalkthrough/xr-interactive-ui.js +24 -12
- package/components/VirtualWalkthrough/xr-pointer-ray.js +20 -13
- package/components/VirtualWalkthrough/xr-progress-pie-material.js +88 -117
- package/components/VirtualWalkthrough/xr-progress-pie.js +10 -1
- package/components/VirtualWalkthrough/xr-ray-quad.js +30 -16
- package/components/VirtualWalkthrough/xr-render-tuning.js +48 -17
- package/components/VirtualWalkthrough/xr-scene-bounds.js +37 -21
- package/components/VirtualWalkthrough/xr-start-pose.js +73 -27
- package/components/VirtualWalkthrough/xr-teleport-gesture.js +42 -16
- package/components/svg/BlobbyGrayIconImage.js +39 -41
- package/components/svg/BlobbyGrayIconImport.js +39 -41
- package/components/svg/BlobbyGrayIconUploadToTheCloud.js +39 -41
- package/components/svg/BlobbyIconCloudChecked.js +164 -202
- package/components/svg/BlobbyIconExportOrUploadPhoto.js +164 -202
- package/components/svg/BlobbyIconFolder.js +164 -202
- package/components/svg/BlobbyIconGraphReport.js +164 -202
- package/components/svg/BlobbyIconHappy.js +164 -202
- package/components/svg/BlobbyIconHeartMonitor.js +164 -202
- package/components/svg/BlobbyIconLeaf.js +164 -202
- package/components/svg/BlobbyIconLibrary.js +164 -202
- package/components/svg/BlobbyIconNursery.js +166 -204
- package/components/svg/BlobbyIconOrganization.js +164 -202
- package/components/svg/BlobbyIconParchment.js +164 -202
- package/components/svg/BlobbyIconPeople.js +164 -202
- package/components/svg/BlobbyIconSeedBank.js +164 -202
- package/components/svg/BlobbyIconSeedling.js +164 -202
- package/components/svg/BlobbyIconSeeds.js +164 -202
- package/components/svg/BlobbyIconSite.js +167 -206
- package/components/svg/BlobbyIconSparkles.js +164 -202
- package/components/svg/BlobbyIconWrench.js +164 -202
- package/components/svg/Bug.js +21 -19
- package/components/svg/Calendar.js +21 -19
- package/components/svg/CaretDown.js +21 -19
- package/components/svg/CaretLeft.js +21 -19
- package/components/svg/CaretRight.js +21 -19
- package/components/svg/CaretUp.js +21 -19
- package/components/svg/ChevronDown.js +21 -19
- package/components/svg/ChevronUp.js +21 -19
- package/components/svg/Close.js +21 -19
- package/components/svg/Critical.js +21 -19
- package/components/svg/Edit.js +164 -202
- package/components/svg/Error.js +21 -19
- package/components/svg/Filter.js +21 -19
- package/components/svg/Folder.js +21 -19
- package/components/svg/Help.js +21 -19
- package/components/svg/Home.js +21 -19
- package/components/svg/IconAdd.js +21 -19
- package/components/svg/IconArrowRight.js +23 -21
- package/components/svg/IconBusinessNetwork.js +21 -19
- package/components/svg/IconCalendar.js +21 -19
- package/components/svg/IconCancel.js +21 -19
- package/components/svg/IconChargingBattery.js +21 -19
- package/components/svg/IconChecklist.js +23 -21
- package/components/svg/IconCheckmark.js +21 -19
- package/components/svg/IconCoinInHand.js +21 -19
- package/components/svg/IconColumns.js +21 -19
- package/components/svg/IconComment.js +22 -20
- package/components/svg/IconDashboard.js +21 -19
- package/components/svg/IconDataMigration.js +21 -19
- package/components/svg/IconDocument.js +23 -21
- package/components/svg/IconDownloadFromTheCloud.js +23 -21
- package/components/svg/IconEdit.js +23 -21
- package/components/svg/IconExpand.js +23 -21
- package/components/svg/IconExport.js +21 -19
- package/components/svg/IconExternalLink.js +21 -19
- package/components/svg/IconEye.js +21 -19
- package/components/svg/IconEyeOff.js +21 -19
- package/components/svg/IconFile.js +21 -19
- package/components/svg/IconFolder.js +21 -19
- package/components/svg/IconFullScreen.js +22 -20
- package/components/svg/IconFutures.js +21 -19
- package/components/svg/IconGraphReport.js +21 -19
- package/components/svg/IconGrid.js +21 -19
- package/components/svg/IconHeartMonitor.js +21 -19
- package/components/svg/IconHelp.js +23 -21
- package/components/svg/IconHistory.js +21 -19
- package/components/svg/IconImport.js +21 -19
- package/components/svg/IconIndex.js +21 -19
- package/components/svg/IconLayers.js +21 -19
- package/components/svg/IconLibrary.js +22 -20
- package/components/svg/IconList.js +21 -19
- package/components/svg/IconLivePlant.js +21 -19
- package/components/svg/IconLocations.js +21 -19
- package/components/svg/IconMail.js +21 -19
- package/components/svg/IconManager.js +21 -19
- package/components/svg/IconMap.js +21 -19
- package/components/svg/IconMarker.js +21 -19
- package/components/svg/IconMenu.js +21 -19
- package/components/svg/IconMenuHorizontal.js +21 -19
- package/components/svg/IconModule.js +21 -19
- package/components/svg/IconMyLocation.js +21 -19
- package/components/svg/IconNote.js +23 -21
- package/components/svg/IconNursery.js +23 -21
- package/components/svg/IconOrg.js +21 -19
- package/components/svg/IconParchment.js +21 -19
- package/components/svg/IconPhoto.js +21 -19
- package/components/svg/IconPlantsFilled.js +21 -19
- package/components/svg/IconRedo.js +21 -19
- package/components/svg/IconRestorationSite.js +21 -19
- package/components/svg/IconSeedBank.js +21 -19
- package/components/svg/IconSeedling.js +21 -19
- package/components/svg/IconSettings.js +21 -19
- package/components/svg/IconSlice.js +21 -19
- package/components/svg/IconSubmit.js +21 -19
- package/components/svg/IconSubtract.js +23 -21
- package/components/svg/IconSynced.js +21 -19
- package/components/svg/IconTrashCan.js +21 -19
- package/components/svg/IconTreasureMap.js +21 -19
- package/components/svg/IconUnavailable.js +21 -19
- package/components/svg/IconUndo.js +21 -19
- package/components/svg/IconVariable.js +21 -19
- package/components/svg/IconVideo.js +31 -30
- package/components/svg/IconWifi.js +21 -19
- package/components/svg/Info.js +21 -19
- package/components/svg/Key.js +21 -19
- package/components/svg/Leaf.js +21 -19
- package/components/svg/Lock.js +21 -19
- package/components/svg/Logo.js +43 -48
- package/components/svg/MenuVertical.js +21 -19
- package/components/svg/Notification.js +21 -19
- package/components/svg/Person.js +21 -19
- package/components/svg/Plus.js +21 -19
- package/components/svg/RestorationSite.js +21 -19
- package/components/svg/Search.js +21 -19
- package/components/svg/Seeds.js +21 -19
- package/components/svg/Site.js +21 -19
- package/components/svg/Sparkles.js +21 -19
- package/components/svg/Species.js +21 -19
- package/components/svg/Species2.js +164 -202
- package/components/svg/Spinner.js +48 -54
- package/components/svg/Star.js +21 -19
- package/components/svg/Success.js +21 -19
- package/components/svg/SuccessFilled.js +21 -19
- package/components/svg/TerrawareLogoDesktop.js +49 -55
- package/components/svg/TerrawareLogoMobile.js +26 -25
- package/components/svg/Touchscreen.js +21 -19
- package/components/svg/UploadCloud.js +164 -202
- package/components/svg/Warning.js +21 -19
- package/components/svg/WelcomeClipboard.js +133 -156
- package/components/svg/index.js +129 -129
- package/components/table/EnhancedTableToolbar.js +74 -68
- package/components/table/EnhancedTableToolbarV2.js +41 -31
- package/components/table/TableCellRenderer.js +231 -196
- package/components/table/TableHeader.js +73 -71
- package/components/table/TableHeaderItem.js +102 -97
- package/components/table/density.js +19 -18
- package/components/table/index.js +369 -350
- package/components/table/sort.js +52 -27
- package/components/table/types.js +1 -0
- package/components/types/index.js +1 -0
- package/hooks/cameraEntityContext.js +13 -2
- package/hooks/useBoolean.js +13 -17
- package/hooks/useCameraPosition.js +56 -56
- package/hooks/useDevicePerformance.js +17 -17
- package/hooks/useXr.js +87 -85
- package/index.js +58 -58
- package/package.json +1 -1
- package/setupTests.js +4 -0
- package/strings/export.js +170 -111
- package/strings/index.js +54 -25
- package/strings/locales.js +41 -21
- package/strings/strings-en.js +51 -49
- package/strings/strings-es.js +51 -49
- package/strings/strings-fr.js +51 -49
- package/strings/strings-gx.js +51 -49
- package/style-dictionary-dist/TerrawareTheme.js +186 -186
- package/theme.js +76 -74
- package/utils/color.js +15 -8
- package/utils/date.js +108 -42
- package/utils/index.js +4 -4
- package/utils/preventDefaultEvent.js +3 -3
- package/utils/text.js +11 -11
- package/utils/useDeviceInfo.js +79 -42
- package/virtualWalkthrough.js +23 -23
|
@@ -1,120 +1,157 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { nearestAnnotationHit } from
|
|
4
|
-
import { INITIAL_DWELL_STATE, advanceDwell } from
|
|
5
|
-
import { pieShaderProgress } from
|
|
6
|
-
import {
|
|
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
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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
|
-
|
|
97
|
-
|
|
98
|
-
entity
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
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
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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
|
|
2
|
-
import {
|
|
3
|
-
import { nearestAnnotationHit } from
|
|
4
|
-
import { XrExitButton } from
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
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
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
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
|
-
|
|
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
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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 };
|