@terraware/web-components 4.2.12 → 4.2.14-rc.0
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 +8 -14
- package/components/Autocomplete/Autocomplete.js +67 -70
- package/components/Badge/index.js +16 -20
- package/components/BusySpinner/index.js +30 -35
- package/components/Button/Button.js +23 -27
- package/components/Button/button.test.js +8 -10
- package/components/Checkbox.js +10 -19
- package/components/Confirm/index.js +44 -51
- package/components/DatePicker/DatePicker.js +116 -117
- package/components/DialogBox/DialogBox.js +76 -66
- package/components/Divisor.js +15 -20
- package/components/Dropdown.js +54 -61
- package/components/EditableTable/index.js +129 -135
- package/components/ErrorBox/ErrorBox.js +37 -36
- package/components/FileChooser/index.js +73 -77
- package/components/FormBottomBar/index.js +63 -69
- package/components/Icon/Icon.js +14 -21
- package/components/Icon/icons/index.d.ts +1 -1
- package/components/Icon/icons/index.d.ts.map +1 -1
- package/components/Icon/icons/index.js +251 -255
- package/components/IconTooltip/index.js +29 -34
- package/components/Map/MapBox.js +199 -220
- package/components/Map/MapContainer.js +14 -18
- package/components/Map/MapDateSliderControl.js +44 -46
- package/components/Map/MapDrawer.js +32 -33
- package/components/Map/MapLegend.js +96 -95
- package/components/Map/MapViewStyleControl.js +38 -48
- package/components/Map/ReactMapboxWrapper.js +2 -9
- package/components/Map/index.js +47 -55
- package/components/Map/types.js +2 -8
- package/components/Map/useMaintainLayerOrder.js +4 -11
- package/components/Markdown/index.js +14 -21
- package/components/Message/Message.js +59 -51
- package/components/MultiSelect/MultiSelect.test.js +22 -24
- package/components/MultiSelect/index.js +73 -69
- package/components/Navbar/NavFooter.js +8 -13
- package/components/Navbar/NavItem.js +43 -37
- package/components/Navbar/NavSection.js +13 -17
- package/components/Navbar/Navbar.js +35 -38
- package/components/Navbar/SubNavbar.js +8 -13
- package/components/Note.js +14 -19
- package/components/OverlayModal/OverlayModal.js +36 -37
- package/components/PageForm/index.js +30 -35
- package/components/PhotoChooser/index.js +93 -98
- package/components/Pill/index.js +29 -31
- package/components/PillList/index.js +16 -22
- package/components/PlacementWrapper/index.js +20 -25
- package/components/PopoverMenu/Popover.js +33 -37
- package/components/PopoverMenu/index.js +41 -46
- package/components/PopoverMultiSelect/index.js +18 -21
- package/components/ProgressCircle/ProgressCircle.js +23 -26
- package/components/RadioButton.js +7 -14
- package/components/Select/Select.js +8 -13
- package/components/Select/SelectT.js +110 -108
- package/components/Separator/index.js +8 -14
- package/components/Size.js +1 -5
- package/components/Slider/index.js +7 -13
- package/components/SummaryBox.js +36 -39
- package/components/Tabs/index.js +75 -77
- package/components/TextTruncated/index.js +64 -65
- package/components/Textfield/Textfield.js +100 -95
- package/components/Textfield/TruncatedTextArea.js +44 -46
- package/components/TimelineSlider/TimelineMarker.js +24 -28
- package/components/TimelineSlider/TimelineRail.js +5 -11
- package/components/TimelineSlider/index.js +47 -51
- package/components/Tooltip/Tooltip.js +21 -26
- package/components/ViewPhotosDialog/index.js +58 -62
- package/components/VirtualWalkthrough/Annotation.d.ts +41 -0
- package/components/VirtualWalkthrough/Annotation.d.ts.map +1 -0
- package/components/VirtualWalkthrough/Annotation.js +197 -0
- package/components/VirtualWalkthrough/AnnotationEditPane.d.ts +20 -0
- package/components/VirtualWalkthrough/AnnotationEditPane.d.ts.map +1 -0
- package/components/VirtualWalkthrough/AnnotationEditPane.js +136 -0
- package/components/VirtualWalkthrough/AnnotationPanel.d.ts +13 -0
- package/components/VirtualWalkthrough/AnnotationPanel.d.ts.map +1 -0
- package/components/VirtualWalkthrough/AnnotationPanel.js +196 -0
- package/components/VirtualWalkthrough/Application.d.ts +9 -0
- package/components/VirtualWalkthrough/Application.d.ts.map +1 -0
- package/components/VirtualWalkthrough/Application.js +32 -0
- package/components/VirtualWalkthrough/AutoRotator.d.ts +109 -0
- package/components/VirtualWalkthrough/AutoRotator.d.ts.map +1 -0
- package/components/VirtualWalkthrough/AutoRotator.js +217 -0
- package/components/VirtualWalkthrough/BlockingSpinner.d.ts +3 -0
- package/components/VirtualWalkthrough/BlockingSpinner.d.ts.map +1 -0
- package/components/VirtualWalkthrough/BlockingSpinner.js +27 -0
- package/components/VirtualWalkthrough/BoundaryRing.d.ts +9 -0
- package/components/VirtualWalkthrough/BoundaryRing.d.ts.map +1 -0
- package/components/VirtualWalkthrough/BoundaryRing.js +33 -0
- package/components/VirtualWalkthrough/CameraInfo.d.ts +16 -0
- package/components/VirtualWalkthrough/CameraInfo.d.ts.map +1 -0
- package/components/VirtualWalkthrough/CameraInfo.js +88 -0
- package/components/VirtualWalkthrough/ControlsInfoPane.d.ts +37 -0
- package/components/VirtualWalkthrough/ControlsInfoPane.d.ts.map +1 -0
- package/components/VirtualWalkthrough/ControlsInfoPane.js +196 -0
- package/components/VirtualWalkthrough/GradientSky.d.ts +9 -0
- package/components/VirtualWalkthrough/GradientSky.d.ts.map +1 -0
- package/components/VirtualWalkthrough/GradientSky.js +73 -0
- package/components/VirtualWalkthrough/SplatControls.d.ts +50 -0
- package/components/VirtualWalkthrough/SplatControls.d.ts.map +1 -0
- package/components/VirtualWalkthrough/SplatControls.js +252 -0
- package/components/VirtualWalkthrough/SplatCrop.d.ts +8 -0
- package/components/VirtualWalkthrough/SplatCrop.d.ts.map +1 -0
- package/components/VirtualWalkthrough/SplatCrop.js +18 -0
- package/components/VirtualWalkthrough/SplatFadeCrop.d.ts +8 -0
- package/components/VirtualWalkthrough/SplatFadeCrop.d.ts.map +1 -0
- package/components/VirtualWalkthrough/SplatFadeCrop.js +18 -0
- package/components/VirtualWalkthrough/SplatModel.d.ts +15 -0
- package/components/VirtualWalkthrough/SplatModel.d.ts.map +1 -0
- package/components/VirtualWalkthrough/SplatModel.js +60 -0
- package/components/VirtualWalkthrough/SplatRevealRain.d.ts +18 -0
- package/components/VirtualWalkthrough/SplatRevealRain.d.ts.map +1 -0
- package/components/VirtualWalkthrough/SplatRevealRain.js +38 -0
- package/components/VirtualWalkthrough/TfAnnotationManager.d.ts +110 -0
- package/components/VirtualWalkthrough/TfAnnotationManager.d.ts.map +1 -0
- package/components/VirtualWalkthrough/TfAnnotationManager.js +368 -0
- package/components/VirtualWalkthrough/TfXrNavigation.d.ts +15 -0
- package/components/VirtualWalkthrough/TfXrNavigation.d.ts.map +1 -0
- package/components/VirtualWalkthrough/TfXrNavigation.js +24 -0
- package/components/VirtualWalkthrough/annotation-styles.css +5 -0
- package/components/VirtualWalkthrough/application-styles.css +4 -0
- package/components/VirtualWalkthrough/boundary-ring.d.ts +47 -0
- package/components/VirtualWalkthrough/boundary-ring.d.ts.map +1 -0
- package/components/VirtualWalkthrough/boundary-ring.js +125 -0
- package/components/VirtualWalkthrough/boundary-ring.test.js +109 -0
- package/components/VirtualWalkthrough/groundPlane.d.ts +14 -0
- package/components/VirtualWalkthrough/groundPlane.d.ts.map +1 -0
- package/components/VirtualWalkthrough/groundPlane.js +47 -0
- package/components/VirtualWalkthrough/groundPlane.test.js +45 -0
- package/components/VirtualWalkthrough/gsplat-fade-crop-effect.d.ts +19 -0
- package/components/VirtualWalkthrough/gsplat-fade-crop-effect.d.ts.map +1 -0
- package/components/VirtualWalkthrough/gsplat-fade-crop-effect.js +119 -0
- package/components/VirtualWalkthrough/walkthrough-camera.d.ts +77 -0
- package/components/VirtualWalkthrough/walkthrough-camera.d.ts.map +1 -0
- package/components/VirtualWalkthrough/walkthrough-camera.js +320 -0
- package/components/svg/BlobbyGrayIconImage.js +36 -41
- package/components/svg/BlobbyGrayIconImport.js +36 -41
- package/components/svg/BlobbyGrayIconUploadToTheCloud.js +36 -41
- package/components/svg/BlobbyIconCloudChecked.js +161 -158
- package/components/svg/BlobbyIconExportOrUploadPhoto.js +161 -158
- package/components/svg/BlobbyIconFolder.js +161 -158
- package/components/svg/BlobbyIconGraphReport.js +161 -158
- package/components/svg/BlobbyIconHappy.js +161 -158
- package/components/svg/BlobbyIconHeartMonitor.js +161 -158
- package/components/svg/BlobbyIconLeaf.js +161 -158
- package/components/svg/BlobbyIconLibrary.js +161 -158
- package/components/svg/BlobbyIconNursery.js +163 -160
- package/components/svg/BlobbyIconOrganization.js +161 -158
- package/components/svg/BlobbyIconParchment.js +161 -158
- package/components/svg/BlobbyIconPeople.js +161 -158
- package/components/svg/BlobbyIconSeedBank.js +161 -158
- package/components/svg/BlobbyIconSeedling.js +161 -158
- package/components/svg/BlobbyIconSeeds.js +161 -158
- package/components/svg/BlobbyIconSite.js +164 -161
- package/components/svg/BlobbyIconSparkles.js +161 -158
- package/components/svg/BlobbyIconWrench.js +161 -158
- package/components/svg/Bug.js +18 -27
- package/components/svg/Calendar.js +18 -27
- package/components/svg/CaretDown.js +18 -27
- package/components/svg/CaretLeft.js +18 -27
- package/components/svg/CaretRight.js +18 -27
- package/components/svg/CaretUp.js +18 -27
- package/components/svg/ChevronDown.js +18 -27
- package/components/svg/ChevronUp.js +18 -27
- package/components/svg/Close.js +18 -27
- package/components/svg/Critical.js +18 -27
- package/components/svg/Edit.js +161 -158
- package/components/svg/Error.js +18 -27
- package/components/svg/Filter.js +18 -27
- package/components/svg/Folder.js +18 -27
- package/components/svg/Help.js +18 -27
- package/components/svg/Home.js +18 -27
- package/components/svg/IconAdd.js +18 -27
- package/components/svg/IconArrowRight.js +19 -28
- package/components/svg/IconBusinessNetwork.js +18 -27
- package/components/svg/IconCalendar.d.ts +1 -1
- package/components/svg/IconCalendar.d.ts.map +1 -1
- package/components/svg/IconCalendar.js +18 -27
- package/components/svg/IconCancel.js +18 -27
- package/components/svg/IconChargingBattery.js +18 -27
- package/components/svg/IconChecklist.js +19 -28
- package/components/svg/IconCheckmark.js +18 -27
- package/components/svg/IconCoinInHand.js +18 -27
- package/components/svg/IconColumns.js +18 -27
- package/components/svg/IconComment.js +18 -27
- package/components/svg/IconDashboard.js +18 -27
- package/components/svg/IconDataMigration.js +18 -27
- package/components/svg/IconDocument.d.ts +8 -0
- package/components/svg/IconDocument.d.ts.map +1 -0
- package/components/svg/IconDocument.js +21 -0
- package/components/svg/IconDownloadFromTheCloud.js +19 -28
- package/components/svg/IconEdit.js +19 -28
- package/components/svg/IconExpand.js +19 -28
- package/components/svg/IconExport.js +18 -27
- package/components/svg/IconExternalLink.js +18 -27
- package/components/svg/IconEye.js +18 -27
- package/components/svg/IconEyeOff.js +18 -27
- package/components/svg/IconFile.js +18 -27
- package/components/svg/IconFolder.js +18 -27
- package/components/svg/IconFullScreen.js +18 -27
- package/components/svg/IconFutures.js +18 -27
- package/components/svg/IconGraphReport.js +18 -27
- package/components/svg/IconHeartMonitor.js +18 -27
- package/components/svg/IconHelp.js +19 -28
- package/components/svg/IconHistory.js +18 -27
- package/components/svg/IconImport.js +18 -27
- package/components/svg/IconIndex.js +18 -27
- package/components/svg/IconLayers.js +18 -27
- package/components/svg/IconLibrary.js +18 -27
- package/components/svg/IconList.js +18 -27
- package/components/svg/IconLivePlant.js +18 -27
- package/components/svg/IconLocations.js +18 -27
- package/components/svg/IconMail.js +18 -27
- package/components/svg/IconManager.js +18 -27
- package/components/svg/IconMarker.js +18 -27
- package/components/svg/IconMenu.js +18 -27
- package/components/svg/IconMenuHorizontal.js +18 -27
- package/components/svg/IconModule.js +18 -27
- package/components/svg/IconMyLocation.js +18 -27
- package/components/svg/IconNote.js +19 -28
- package/components/svg/IconNursery.js +18 -27
- package/components/svg/IconOrg.js +18 -27
- package/components/svg/IconParchment.js +18 -27
- package/components/svg/IconPhoto.js +18 -27
- package/components/svg/IconPlantsFilled.js +18 -27
- package/components/svg/IconRedo.js +18 -27
- package/components/svg/IconRestorationSite.js +18 -27
- package/components/svg/IconSeedBank.js +18 -27
- package/components/svg/IconSeedling.js +18 -27
- package/components/svg/IconSettings.js +18 -27
- package/components/svg/IconSlice.js +18 -27
- package/components/svg/IconSubmit.js +18 -27
- package/components/svg/IconSubtract.js +19 -28
- package/components/svg/IconSynced.js +18 -27
- package/components/svg/IconTrashCan.js +18 -27
- package/components/svg/IconTreasureMap.js +18 -27
- package/components/svg/IconUnavailable.js +18 -27
- package/components/svg/IconUndo.js +18 -27
- package/components/svg/IconVariable.js +18 -27
- package/components/svg/IconVideo.js +19 -28
- package/components/svg/IconWifi.js +18 -27
- package/components/svg/Info.js +18 -27
- package/components/svg/Key.js +18 -27
- package/components/svg/Leaf.js +18 -27
- package/components/svg/Lock.js +18 -27
- package/components/svg/Logo.js +40 -45
- package/components/svg/MenuVertical.js +18 -27
- package/components/svg/Notification.js +18 -27
- package/components/svg/Person.js +18 -27
- package/components/svg/Plus.js +18 -27
- package/components/svg/RestorationSite.js +18 -27
- package/components/svg/Search.js +18 -27
- package/components/svg/Seeds.js +18 -27
- package/components/svg/Site.js +18 -27
- package/components/svg/Sparkles.js +18 -27
- package/components/svg/Species.js +18 -27
- package/components/svg/Species2.js +161 -158
- package/components/svg/Spinner.js +27 -36
- package/components/svg/Star.js +18 -27
- package/components/svg/Success.js +18 -27
- package/components/svg/SuccessFilled.js +18 -27
- package/components/svg/TerrawareLogoDesktop.js +44 -49
- package/components/svg/TerrawareLogoMobile.js +20 -29
- package/components/svg/Touchscreen.js +18 -27
- package/components/svg/UploadCloud.js +161 -158
- package/components/svg/Warning.js +18 -27
- package/components/svg/WelcomeClipboard.js +130 -135
- package/components/svg/index.d.ts +1 -0
- package/components/svg/index.d.ts.map +1 -1
- package/components/svg/index.js +127 -888
- package/components/table/EnhancedTableToolbar.js +45 -47
- package/components/table/EnhancedTableToolbarV2.js +18 -26
- package/components/table/TableCellRenderer.js +137 -143
- package/components/table/TableHeader.js +45 -47
- package/components/table/TableHeaderItem.js +65 -64
- package/components/table/density.js +3 -11
- package/components/table/index.js +225 -218
- package/components/table/sort.js +8 -18
- package/components/table/sort.test.js +6 -8
- package/components/table/types.js +1 -5
- package/components/types/index.js +1 -5
- package/hooks/useBoolean.d.ts +4 -0
- package/hooks/useBoolean.d.ts.map +1 -0
- package/hooks/useBoolean.js +12 -0
- package/hooks/useCameraPosition.d.ts +8 -0
- package/hooks/useCameraPosition.d.ts.map +1 -0
- package/hooks/useCameraPosition.js +33 -0
- package/index.js +57 -398
- package/license-report.html +1 -1
- package/package.json +10 -3
- package/style-dictionary-dist/TerrawareTheme.js +1 -7
- package/theme.js +7 -14
- package/utils/color.d.ts +2 -0
- package/utils/color.d.ts.map +1 -0
- package/utils/color.js +14 -0
- package/utils/date.js +13 -24
- package/utils/date.test.js +40 -43
- package/utils/index.js +5 -53
- package/utils/preventDefaultEvent.js +1 -7
- package/utils/text.js +2 -9
- package/utils/text.test.js +9 -11
- package/utils/useDeviceInfo.js +10 -16
- package/virtualWalkthrough.d.ts +31 -0
- package/virtualWalkthrough.d.ts.map +1 -0
- package/virtualWalkthrough.js +28 -0
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
import { createElement } from 'react';
|
|
2
|
+
import { renderToStaticMarkup } from 'react-dom/server';
|
|
3
|
+
import { FILTER_LINEAR, PIXELFORMAT_RGBA8, Texture } from 'playcanvas';
|
|
4
|
+
import { AnnotationManager as PcAnnotationManager } from 'playcanvas/scripts/esm/annotations.mjs';
|
|
5
|
+
import Icon from '../Icon/Icon';
|
|
6
|
+
// Maps each annotation icon type to a @terraware/web-components icon.
|
|
7
|
+
const ANNOTATION_ICONS = {
|
|
8
|
+
text: 'iconComment',
|
|
9
|
+
image: 'iconPhoto',
|
|
10
|
+
video: 'iconVideo'
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
// Pixel dimensions of the (square) hotspot texture canvas at rest.
|
|
14
|
+
const HOTSPOT_TEXTURE_SIZE = 64;
|
|
15
|
+
|
|
16
|
+
// How much larger the hotspot appears while hovered.
|
|
17
|
+
const HOTSPOT_HOVER_SCALE = 1.3;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Extended AnnotationManager that adds max size clamping for annotations
|
|
21
|
+
* and fixes positioning when canvas is not at document origin.
|
|
22
|
+
*/
|
|
23
|
+
export class TfAnnotationManager extends PcAnnotationManager {
|
|
24
|
+
static scriptName = 'tfAnnotationManager';
|
|
25
|
+
_maxWorldSize = 1.0;
|
|
26
|
+
_customParentDom = null;
|
|
27
|
+
_clickHandlersAttached = new Set();
|
|
28
|
+
_hotspotBackgroundColor = '#2C8658';
|
|
29
|
+
_iconImages = new Map();
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Maximum world-space size for annotations.
|
|
33
|
+
* This prevents annotations from growing beyond this size regardless of camera distance.
|
|
34
|
+
*
|
|
35
|
+
* @attribute
|
|
36
|
+
* @title Max World Size
|
|
37
|
+
* @type {number}
|
|
38
|
+
* @default 1.0
|
|
39
|
+
*/
|
|
40
|
+
set maxWorldSize(value) {
|
|
41
|
+
this._maxWorldSize = value;
|
|
42
|
+
}
|
|
43
|
+
get maxWorldSize() {
|
|
44
|
+
return this._maxWorldSize;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Background color for the hotspot texture canvas (the circle fill color).
|
|
49
|
+
* This is a hex color string (e.g., '#2C8658') that's drawn on the texture canvas.
|
|
50
|
+
* Note: This is different from hotspotColor and hoverColor, which are PlayCanvas Color objects
|
|
51
|
+
* used for the material's emissive property.
|
|
52
|
+
*
|
|
53
|
+
* @attribute
|
|
54
|
+
* @title Hotspot Background Color
|
|
55
|
+
* @type {string}
|
|
56
|
+
* @default '#2C8658'
|
|
57
|
+
*/
|
|
58
|
+
set hotspotBackgroundColor(value) {
|
|
59
|
+
this._hotspotBackgroundColor = value;
|
|
60
|
+
}
|
|
61
|
+
get hotspotBackgroundColor() {
|
|
62
|
+
return this._hotspotBackgroundColor;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Initialize with custom parent DOM if needed.
|
|
67
|
+
*/
|
|
68
|
+
initialize() {
|
|
69
|
+
// Find a container div with data-annotation-container attribute
|
|
70
|
+
const canvas = this.app.graphicsDevice.canvas;
|
|
71
|
+
const container = canvas.parentElement?.querySelector('[data-annotation-container]');
|
|
72
|
+
if (container) {
|
|
73
|
+
this._customParentDom = container;
|
|
74
|
+
this._parentDom = container;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Call parent initialize
|
|
78
|
+
super.initialize();
|
|
79
|
+
this._loadIconImages();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Renders each web-components icon to an SVG image once. When an image finishes
|
|
84
|
+
* loading, any annotations already using that icon are redrawn.
|
|
85
|
+
* @private
|
|
86
|
+
*/
|
|
87
|
+
_loadIconImages() {
|
|
88
|
+
const fillColor = this._hotspotColor?.toString() ?? '#ffffff';
|
|
89
|
+
Object.keys(ANNOTATION_ICONS).forEach(iconType => {
|
|
90
|
+
const name = ANNOTATION_ICONS[iconType];
|
|
91
|
+
const markup = renderToStaticMarkup(/*#__PURE__*/createElement(Icon, {
|
|
92
|
+
name,
|
|
93
|
+
fillColor
|
|
94
|
+
})).replace(/^<svg/, '<svg width="128" height="128"');
|
|
95
|
+
const image = new Image();
|
|
96
|
+
image.onload = () => this._refreshIcon(iconType);
|
|
97
|
+
image.src = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(markup)}`;
|
|
98
|
+
this._iconImages.set(iconType, image);
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Redraws the texture for all annotations using the given icon, once its image
|
|
104
|
+
* has loaded.
|
|
105
|
+
* @private
|
|
106
|
+
*/
|
|
107
|
+
_refreshIcon(iconType) {
|
|
108
|
+
const annotationResources = this._annotationResources;
|
|
109
|
+
if (!annotationResources) {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
annotationResources.forEach((_resources, annotation) => {
|
|
113
|
+
if ((annotation.icon ?? 'text') === iconType) {
|
|
114
|
+
this._applyAnnotationIcon(annotation);
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Update loop to attach click handlers to annotations and manage visibility.
|
|
121
|
+
*/
|
|
122
|
+
update() {
|
|
123
|
+
const annotationResources = this._annotationResources;
|
|
124
|
+
if (!annotationResources) {
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Attach click handlers and manage visibility for all annotations
|
|
129
|
+
annotationResources.forEach((resources, annotation) => {
|
|
130
|
+
if (resources && resources.hotspotDom) {
|
|
131
|
+
// Attach click handler if not already attached
|
|
132
|
+
if (!this._clickHandlersAttached.has(annotation)) {
|
|
133
|
+
const callback = annotation.onClickCallback;
|
|
134
|
+
if (callback) {
|
|
135
|
+
this._clickHandlersAttached.add(annotation);
|
|
136
|
+
resources.hotspotDom.addEventListener('pointerdown', e => {
|
|
137
|
+
callback(e.clientX, e.clientY);
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Disable/enable the entity itself based on visibility
|
|
143
|
+
const isVisible = annotation.enabled !== undefined ? annotation.enabled : true;
|
|
144
|
+
|
|
145
|
+
// Hide tooltip if the active annotation is hidden
|
|
146
|
+
if (this._activeAnnotation === annotation && !isVisible) {
|
|
147
|
+
this._tooltipDom.style.visibility = 'hidden';
|
|
148
|
+
this._tooltipDom.style.opacity = '0';
|
|
149
|
+
this._activeAnnotation = null;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Override annotation registration to draw the media icon onto the hotspot
|
|
157
|
+
* texture once the annotation (and its icon) is available.
|
|
158
|
+
* @private
|
|
159
|
+
*/
|
|
160
|
+
_registerAnnotation(annotation) {
|
|
161
|
+
super._registerAnnotation(annotation);
|
|
162
|
+
this._applyAnnotationIcon(annotation);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Recreates the hotspot texture with the appropriate media icon drawn on it.
|
|
167
|
+
* @private
|
|
168
|
+
*/
|
|
169
|
+
_applyAnnotationIcon(annotation) {
|
|
170
|
+
const resources = this._annotationResources.get(annotation);
|
|
171
|
+
if (!resources) {
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
const icon = annotation.icon ?? 'text';
|
|
175
|
+
resources.texture.destroy();
|
|
176
|
+
resources.texture = this._createHotspotTexture(annotation.label, HOTSPOT_TEXTURE_SIZE, 6, icon);
|
|
177
|
+
resources.materials.forEach(material => {
|
|
178
|
+
material.emissiveMap = resources.texture;
|
|
179
|
+
material.opacityMap = resources.texture;
|
|
180
|
+
material.update();
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Draws the icon image onto the hotspot texture canvas, scaled to fit the
|
|
186
|
+
* circle. Does nothing if the image has not finished loading yet; the
|
|
187
|
+
* annotation is redrawn once it has (see `_refreshIcon`).
|
|
188
|
+
* @private
|
|
189
|
+
*/
|
|
190
|
+
_drawIcon(ctx, icon, size) {
|
|
191
|
+
const image = this._iconImages.get(icon);
|
|
192
|
+
if (!image || !image.complete || image.naturalWidth === 0) {
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
const iconSize = size * 0.5;
|
|
196
|
+
const offset = (size - iconSize) / 2;
|
|
197
|
+
ctx.drawImage(image, offset, offset, iconSize, iconSize);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Keep the media icon drawn on the hotspot texture. The label text itself
|
|
202
|
+
* is not rendered on the hotspot, so we simply redraw with the icon.
|
|
203
|
+
* @private
|
|
204
|
+
*/
|
|
205
|
+
_onLabelChange(annotation) {
|
|
206
|
+
this._applyAnnotationIcon(annotation);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* The hotspot colors are baked into the texture in `_createHotspotTexture`,
|
|
211
|
+
* so the material emissive is kept white to let those colors render as drawn.
|
|
212
|
+
* @private
|
|
213
|
+
*/
|
|
214
|
+
_createHotspotMaterial(texture, options) {
|
|
215
|
+
const material = super._createHotspotMaterial(texture, options);
|
|
216
|
+
material.emissive.set(1, 1, 1);
|
|
217
|
+
material.update();
|
|
218
|
+
return material;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
222
|
+
_setAnnotationHover(_annotation, _hover) {
|
|
223
|
+
// No-op: the hover growth is handled in _updateAnnotationRotationAndScale
|
|
224
|
+
// (via _hoverAnnotation); the texture is identical in both states.
|
|
225
|
+
}
|
|
226
|
+
_updateAllAnnotationColors() {
|
|
227
|
+
// No-op: hotspot colors are baked into the texture (see _createHotspotMaterial).
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
231
|
+
_showTooltip(_annotation) {
|
|
232
|
+
// No-op: AnnotationPanel handles all annotation UI, this removes default behavior
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
236
|
+
_onTextChange(_annotation, _text) {
|
|
237
|
+
// No-op: AnnotationPanel handles all annotation UI, this removes default behavior
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Override position update to adjust for canvas offset and respect visibility.
|
|
242
|
+
* @private
|
|
243
|
+
*/
|
|
244
|
+
_updateAnnotationPositions(annotation, resources, screenPos) {
|
|
245
|
+
const canvas = this.app.graphicsDevice.canvas;
|
|
246
|
+
const rect = canvas.getBoundingClientRect();
|
|
247
|
+
|
|
248
|
+
// If using a custom parent (positioned container), use raw screen coordinates
|
|
249
|
+
// Otherwise, adjust for canvas position in document
|
|
250
|
+
const offsetX = this._customParentDom ? 0 : rect.left + window.scrollX;
|
|
251
|
+
const offsetY = this._customParentDom ? 0 : rect.top + window.scrollY;
|
|
252
|
+
|
|
253
|
+
// Check if annotation is visible (defaults to true if not specified)
|
|
254
|
+
const isVisible = annotation.enabled !== undefined ? annotation.enabled : true;
|
|
255
|
+
resources.hotspotDom.style.display = isVisible ? 'block' : 'none';
|
|
256
|
+
resources.hotspotDom.style.left = `${screenPos.x + offsetX}px`;
|
|
257
|
+
resources.hotspotDom.style.top = `${screenPos.y + offsetY}px`;
|
|
258
|
+
if (this._activeAnnotation === annotation) {
|
|
259
|
+
this._tooltipDom.style.display = isVisible ? 'block' : 'none';
|
|
260
|
+
this._tooltipDom.style.left = `${screenPos.x + offsetX}px`;
|
|
261
|
+
this._tooltipDom.style.top = `${screenPos.y + offsetY}px`;
|
|
262
|
+
}
|
|
263
|
+
if (annotation.onScreenPositionUpdateCallback) {
|
|
264
|
+
// Rendered hotspot diameter (px), set by _updateAnnotationRotationAndScale.
|
|
265
|
+
// Parsed from the style rather than measured to avoid forcing a reflow.
|
|
266
|
+
const hotspotSize = parseFloat(resources.hotspotDom.style.width) || undefined;
|
|
267
|
+
annotation.onScreenPositionUpdateCallback(screenPos.x + offsetX, screenPos.y + offsetY, hotspotSize);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Override the scale update to clamp the world size to a maximum value.
|
|
273
|
+
* Also scales the hotspot DOM element to match.
|
|
274
|
+
* @private
|
|
275
|
+
*/
|
|
276
|
+
_updateAnnotationRotationAndScale(annotation) {
|
|
277
|
+
const cameraRotation = this._camera.getRotation();
|
|
278
|
+
annotation.entity.setRotation(cameraRotation);
|
|
279
|
+
annotation.entity.rotateLocal(90, 0, 0);
|
|
280
|
+
const cameraPos = this._camera.getPosition();
|
|
281
|
+
const distance = annotation.entity.getPosition().distance(cameraPos);
|
|
282
|
+
const canvas = this.app.graphicsDevice.canvas;
|
|
283
|
+
const screenHeight = canvas.clientHeight;
|
|
284
|
+
const projMatrix = this._camera.camera.projectionMatrix;
|
|
285
|
+
|
|
286
|
+
// Calculate world size based on screen size and distance
|
|
287
|
+
const unclamped = this._hotspotSize / screenHeight * (2 * distance / projMatrix.data[5]);
|
|
288
|
+
|
|
289
|
+
// Clamp to maximum world size
|
|
290
|
+
const worldSize = Math.min(unclamped, this._maxWorldSize);
|
|
291
|
+
const hovered = this._hoverAnnotation === annotation;
|
|
292
|
+
const hoverScale = hovered ? HOTSPOT_HOVER_SCALE : 1;
|
|
293
|
+
const entityScale = worldSize * hoverScale;
|
|
294
|
+
annotation.entity.setLocalScale(entityScale, entityScale, entityScale);
|
|
295
|
+
|
|
296
|
+
// Scale the hotspot DOM element proportionally when clamped, including the
|
|
297
|
+
// hover growth so it tracks the visible circle.
|
|
298
|
+
const resources = this._annotationResources.get(annotation);
|
|
299
|
+
if (resources && resources.hotspotDom) {
|
|
300
|
+
const scaleFactor = worldSize / unclamped;
|
|
301
|
+
const baseSize = this._hotspotSize + 5; // Match the +5 from the stylesheet
|
|
302
|
+
const scaledSize = baseSize * scaleFactor * hoverScale;
|
|
303
|
+
resources.hotspotDom.style.width = `${scaledSize}px`;
|
|
304
|
+
resources.hotspotDom.style.height = `${scaledSize}px`;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Override to create hotspot texture with custom background color.
|
|
310
|
+
* @private
|
|
311
|
+
*/
|
|
312
|
+
_createHotspotTexture(label, size = HOTSPOT_TEXTURE_SIZE, borderWidth = 6, icon) {
|
|
313
|
+
const canvas = document.createElement('canvas');
|
|
314
|
+
canvas.width = size;
|
|
315
|
+
canvas.height = size;
|
|
316
|
+
const ctx = canvas.getContext('2d');
|
|
317
|
+
if (!ctx) {
|
|
318
|
+
throw new Error('Failed to get canvas 2d context');
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// First clear with stroke color at zero alpha
|
|
322
|
+
ctx.fillStyle = 'white';
|
|
323
|
+
ctx.globalAlpha = 0;
|
|
324
|
+
ctx.fillRect(0, 0, size, size);
|
|
325
|
+
ctx.globalAlpha = 1.0;
|
|
326
|
+
|
|
327
|
+
// Draw circle with custom background color
|
|
328
|
+
const centerX = size / 2;
|
|
329
|
+
const centerY = size / 2;
|
|
330
|
+
const radius = size / 2 - 4;
|
|
331
|
+
|
|
332
|
+
// The icon fill and border share the hotspot color
|
|
333
|
+
const foregroundColor = this._hotspotColor?.toString() ?? '#ffffff';
|
|
334
|
+
|
|
335
|
+
// Draw main circle
|
|
336
|
+
ctx.beginPath();
|
|
337
|
+
ctx.arc(centerX, centerY, radius, 0, Math.PI * 2);
|
|
338
|
+
ctx.fillStyle = this._hotspotBackgroundColor;
|
|
339
|
+
ctx.fill();
|
|
340
|
+
|
|
341
|
+
// Draw border
|
|
342
|
+
ctx.beginPath();
|
|
343
|
+
ctx.arc(centerX, centerY, radius, 0, Math.PI * 2);
|
|
344
|
+
ctx.lineWidth = borderWidth;
|
|
345
|
+
ctx.strokeStyle = foregroundColor;
|
|
346
|
+
ctx.stroke();
|
|
347
|
+
|
|
348
|
+
// Draw the media icon inside the circle
|
|
349
|
+
if (icon) {
|
|
350
|
+
this._drawIcon(ctx, icon, size);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// Get pixel data
|
|
354
|
+
const imageData = ctx.getImageData(0, 0, size, size);
|
|
355
|
+
const data = imageData.data;
|
|
356
|
+
|
|
357
|
+
// Create and return the texture
|
|
358
|
+
return new Texture(this.app.graphicsDevice, {
|
|
359
|
+
width: size,
|
|
360
|
+
height: size,
|
|
361
|
+
format: PIXELFORMAT_RGBA8,
|
|
362
|
+
magFilter: FILTER_LINEAR,
|
|
363
|
+
minFilter: FILTER_LINEAR,
|
|
364
|
+
mipmaps: false,
|
|
365
|
+
levels: [new Uint8Array(data.buffer)]
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { XrNavigation as PcXrNavigation } from 'playcanvas/scripts/esm/xr-navigation.mjs';
|
|
2
|
+
/**
|
|
3
|
+
* Extended XrNavigation that fixes the bug where tryTeleport doesn't respect
|
|
4
|
+
* the enableTeleport flag setting.
|
|
5
|
+
*/
|
|
6
|
+
export declare class TfXrNavigation extends PcXrNavigation {
|
|
7
|
+
static scriptName: string;
|
|
8
|
+
/**
|
|
9
|
+
* Override tryTeleport to respect the enableTeleport flag.
|
|
10
|
+
* The base implementation is always called from handleSelectEnd,
|
|
11
|
+
* but we only want to teleport when enableTeleport is true.
|
|
12
|
+
*/
|
|
13
|
+
tryTeleport(inputSource: any): void;
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=TfXrNavigation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TfXrNavigation.d.ts","sourceRoot":"","sources":["../../../src/components/VirtualWalkthrough/TfXrNavigation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,IAAI,cAAc,EAAE,MAAM,0CAA0C,CAAC;AAE1F;;;GAGG;AACH,qBAAa,cAAe,SAAQ,cAAc;IAChD,MAAM,CAAC,UAAU,SAAoB;IAErC;;;;OAIG;IACH,WAAW,CAAC,WAAW,EAAE,GAAG;CAS7B"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { XrNavigation as PcXrNavigation } from 'playcanvas/scripts/esm/xr-navigation.mjs';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Extended XrNavigation that fixes the bug where tryTeleport doesn't respect
|
|
5
|
+
* the enableTeleport flag setting.
|
|
6
|
+
*/
|
|
7
|
+
export class TfXrNavigation extends PcXrNavigation {
|
|
8
|
+
static scriptName = 'tfXrNavigation';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Override tryTeleport to respect the enableTeleport flag.
|
|
12
|
+
* The base implementation is always called from handleSelectEnd,
|
|
13
|
+
* but we only want to teleport when enableTeleport is true.
|
|
14
|
+
*/
|
|
15
|
+
tryTeleport(inputSource) {
|
|
16
|
+
// Only proceed with teleportation if enableTeleport is true
|
|
17
|
+
if (!this.enableTeleport) {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Call the parent implementation
|
|
22
|
+
super.tryTeleport(inputSource);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { Color, Script, Vec3 } from 'playcanvas';
|
|
2
|
+
export type BoundaryRingGeometryParams = {
|
|
3
|
+
center: Vec3;
|
|
4
|
+
radius: number;
|
|
5
|
+
groundPlane: Vec3[];
|
|
6
|
+
/** Band width in world units (the ring spans radius ± width / 2). */
|
|
7
|
+
width: number;
|
|
8
|
+
dashCount?: number;
|
|
9
|
+
dashRatio?: number;
|
|
10
|
+
};
|
|
11
|
+
/** Flat-array triangle geometry ready for `Mesh.setPositions` / `Mesh.setIndices`. */
|
|
12
|
+
export type BoundaryRingGeometry = {
|
|
13
|
+
positions: number[];
|
|
14
|
+
indices: number[];
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Triangle geometry for a dashed boundary ring laid flat on the ground plane. Each dash is a
|
|
18
|
+
* short band (a quad = two triangles) sweeping from `radius - width / 2` to `radius + width / 2`;
|
|
19
|
+
* every vertex is sampled on the circle in XZ and lifted onto the plane via `yOnPlane`.
|
|
20
|
+
* Returns empty arrays when radius, width, or dashCount is non-positive, or the plane is invalid.
|
|
21
|
+
*/
|
|
22
|
+
export declare const boundaryRingMesh: ({ center, radius, groundPlane, width, dashCount, dashRatio, }: BoundaryRingGeometryParams) => BoundaryRingGeometry;
|
|
23
|
+
/**
|
|
24
|
+
* Draws a dashed boundary ring as a flat band mesh on the ground plane.
|
|
25
|
+
*
|
|
26
|
+
* Rendered as a thin triangle band whose width is a fraction of the radius (`widthRatio`).
|
|
27
|
+
* center / radius / groundPlane are set imperatively by the BoundaryRing component (see BoundaryRing.tsx for why
|
|
28
|
+
* they are not reactive Script props), which calls `rebuild()` after updating them.
|
|
29
|
+
*/
|
|
30
|
+
export declare class BoundaryRingScript extends Script {
|
|
31
|
+
static scriptName: string;
|
|
32
|
+
center: Vec3;
|
|
33
|
+
radius: number;
|
|
34
|
+
groundPlane: Vec3[];
|
|
35
|
+
color: Color;
|
|
36
|
+
dashCount: number;
|
|
37
|
+
dashRatio: number;
|
|
38
|
+
/** Band width as a fraction of the radius. */
|
|
39
|
+
widthRatio: number;
|
|
40
|
+
private _material?;
|
|
41
|
+
private _mesh?;
|
|
42
|
+
initialize(): void;
|
|
43
|
+
rebuild(): void;
|
|
44
|
+
private _clearMesh;
|
|
45
|
+
destroy(): void;
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=boundary-ring.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"boundary-ring.d.ts","sourceRoot":"","sources":["../../../src/components/VirtualWalkthrough/boundary-ring.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,EAIL,MAAM,EAEN,IAAI,EACL,MAAM,YAAY,CAAC;AAIpB,MAAM,MAAM,0BAA0B,GAAG;IACvC,MAAM,EAAE,IAAI,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,IAAI,EAAE,CAAC;IACpB,qEAAqE;IACrE,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,sFAAsF;AACtF,MAAM,MAAM,oBAAoB,GAAG;IAAE,SAAS,EAAE,MAAM,EAAE,CAAC;IAAC,OAAO,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC;AAE9E;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,GAAI,+DAO9B,0BAA0B,KAAG,oBAgC/B,CAAC;AAEF;;;;;;GAMG;AACH,qBAAa,kBAAmB,SAAQ,MAAM;IAC5C,MAAM,CAAC,UAAU,SAAkB;IAEnC,MAAM,OAAc;IACpB,MAAM,SAAK;IACX,WAAW,EAAE,IAAI,EAAE,CAAM;IACzB,KAAK,QAAsB;IAC3B,SAAS,SAAM;IACf,SAAS,SAAO;IAChB,8CAA8C;IAC9C,UAAU,SAAQ;IAElB,OAAO,CAAC,SAAS,CAAC,CAAmB;IACrC,OAAO,CAAC,KAAK,CAAC,CAAO;IAErB,UAAU;IAUV,OAAO;IA+BP,OAAO,CAAC,UAAU;IAUlB,OAAO;CAKR"}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { CULLFACE_NONE, Color, LAYERID_IMMEDIATE, Mesh, MeshInstance, Script, StandardMaterial, Vec3 } from 'playcanvas';
|
|
2
|
+
import { computeGroundPlane, yOnPlane } from './groundPlane';
|
|
3
|
+
|
|
4
|
+
/** Flat-array triangle geometry ready for `Mesh.setPositions` / `Mesh.setIndices`. */
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Triangle geometry for a dashed boundary ring laid flat on the ground plane. Each dash is a
|
|
8
|
+
* short band (a quad = two triangles) sweeping from `radius - width / 2` to `radius + width / 2`;
|
|
9
|
+
* every vertex is sampled on the circle in XZ and lifted onto the plane via `yOnPlane`.
|
|
10
|
+
* Returns empty arrays when radius, width, or dashCount is non-positive, or the plane is invalid.
|
|
11
|
+
*/
|
|
12
|
+
export const boundaryRingMesh = ({
|
|
13
|
+
center,
|
|
14
|
+
radius,
|
|
15
|
+
groundPlane,
|
|
16
|
+
width,
|
|
17
|
+
dashCount = 64,
|
|
18
|
+
dashRatio = 0.5
|
|
19
|
+
}) => {
|
|
20
|
+
const plane = computeGroundPlane(groundPlane);
|
|
21
|
+
if (radius <= 0 || width <= 0 || dashCount <= 0 || !plane) {
|
|
22
|
+
return {
|
|
23
|
+
positions: [],
|
|
24
|
+
indices: []
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
const innerR = radius - width / 2;
|
|
28
|
+
const outerR = radius + width / 2;
|
|
29
|
+
const step = Math.PI * 2 / dashCount;
|
|
30
|
+
const on = step * dashRatio;
|
|
31
|
+
const positions = [];
|
|
32
|
+
const indices = [];
|
|
33
|
+
const pushVertex = (r, angle) => {
|
|
34
|
+
const x = center.x + r * Math.cos(angle);
|
|
35
|
+
const z = center.z + r * Math.sin(angle);
|
|
36
|
+
positions.push(x, yOnPlane(x, z, plane.normal, plane.point, center.y), z);
|
|
37
|
+
};
|
|
38
|
+
for (let i = 0; i < dashCount; i++) {
|
|
39
|
+
const a0 = i * step;
|
|
40
|
+
const a1 = a0 + on;
|
|
41
|
+
const base = i * 4;
|
|
42
|
+
// Per dash: v0 inner@a0, v1 outer@a0, v2 inner@a1, v3 outer@a1.
|
|
43
|
+
pushVertex(innerR, a0);
|
|
44
|
+
pushVertex(outerR, a0);
|
|
45
|
+
pushVertex(innerR, a1);
|
|
46
|
+
pushVertex(outerR, a1);
|
|
47
|
+
indices.push(base, base + 1, base + 3, base, base + 3, base + 2);
|
|
48
|
+
}
|
|
49
|
+
return {
|
|
50
|
+
positions,
|
|
51
|
+
indices
|
|
52
|
+
};
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Draws a dashed boundary ring as a flat band mesh on the ground plane.
|
|
57
|
+
*
|
|
58
|
+
* Rendered as a thin triangle band whose width is a fraction of the radius (`widthRatio`).
|
|
59
|
+
* center / radius / groundPlane are set imperatively by the BoundaryRing component (see BoundaryRing.tsx for why
|
|
60
|
+
* they are not reactive Script props), which calls `rebuild()` after updating them.
|
|
61
|
+
*/
|
|
62
|
+
export class BoundaryRingScript extends Script {
|
|
63
|
+
static scriptName = 'boundaryRing';
|
|
64
|
+
center = new Vec3();
|
|
65
|
+
radius = 0;
|
|
66
|
+
groundPlane = [];
|
|
67
|
+
color = new Color(1, 1, 1);
|
|
68
|
+
dashCount = 64;
|
|
69
|
+
dashRatio = 0.5;
|
|
70
|
+
/** Band width as a fraction of the radius. */
|
|
71
|
+
widthRatio = 0.05;
|
|
72
|
+
initialize() {
|
|
73
|
+
const material = new StandardMaterial();
|
|
74
|
+
material.useLighting = false;
|
|
75
|
+
material.emissive = this.color;
|
|
76
|
+
material.cull = CULLFACE_NONE;
|
|
77
|
+
material.update();
|
|
78
|
+
this._material = material;
|
|
79
|
+
this.rebuild();
|
|
80
|
+
}
|
|
81
|
+
rebuild() {
|
|
82
|
+
this._clearMesh();
|
|
83
|
+
const geometry = boundaryRingMesh({
|
|
84
|
+
center: this.center,
|
|
85
|
+
radius: this.radius,
|
|
86
|
+
groundPlane: this.groundPlane,
|
|
87
|
+
width: this.radius * this.widthRatio,
|
|
88
|
+
dashCount: this.dashCount,
|
|
89
|
+
dashRatio: this.dashRatio
|
|
90
|
+
});
|
|
91
|
+
if (geometry.positions.length === 0 || !this._material) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
const mesh = new Mesh(this.app.graphicsDevice);
|
|
95
|
+
mesh.setPositions(geometry.positions);
|
|
96
|
+
mesh.setIndices(geometry.indices);
|
|
97
|
+
mesh.update();
|
|
98
|
+
this._mesh = mesh;
|
|
99
|
+
const meshInstance = new MeshInstance(mesh, this._material);
|
|
100
|
+
if (this.entity.render) {
|
|
101
|
+
this.entity.render.meshInstances = [meshInstance];
|
|
102
|
+
} else {
|
|
103
|
+
// Render on the Immediate layer (drawn after the World layer where the splats render) so
|
|
104
|
+
// the ring is not composited behind them.
|
|
105
|
+
this.entity.addComponent('render', {
|
|
106
|
+
meshInstances: [meshInstance],
|
|
107
|
+
layers: [LAYERID_IMMEDIATE]
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
_clearMesh() {
|
|
112
|
+
if (this.entity.render) {
|
|
113
|
+
this.entity.render.meshInstances = [];
|
|
114
|
+
}
|
|
115
|
+
if (this._mesh) {
|
|
116
|
+
this._mesh.destroy();
|
|
117
|
+
this._mesh = undefined;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
destroy() {
|
|
121
|
+
this._clearMesh();
|
|
122
|
+
this._material?.destroy();
|
|
123
|
+
this._material = undefined;
|
|
124
|
+
}
|
|
125
|
+
}
|