@terraware/web-components 4.2.13 → 4.2.14-rc.1

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 (302) hide show
  1. package/components/AntSwitch/index.js +8 -14
  2. package/components/Autocomplete/Autocomplete.js +67 -70
  3. package/components/Badge/index.js +16 -20
  4. package/components/BusySpinner/index.js +30 -35
  5. package/components/Button/Button.js +23 -27
  6. package/components/Button/button.test.js +8 -10
  7. package/components/Checkbox.js +10 -19
  8. package/components/Confirm/index.js +44 -51
  9. package/components/DatePicker/DatePicker.js +116 -117
  10. package/components/DialogBox/DialogBox.js +76 -66
  11. package/components/Divisor.js +15 -20
  12. package/components/Dropdown.js +54 -61
  13. package/components/EditableTable/index.js +129 -135
  14. package/components/ErrorBox/ErrorBox.js +37 -36
  15. package/components/FileChooser/index.js +73 -77
  16. package/components/FormBottomBar/index.js +63 -69
  17. package/components/Icon/Icon.js +14 -21
  18. package/components/Icon/icons/index.js +251 -257
  19. package/components/IconTooltip/index.js +29 -34
  20. package/components/Map/MapBox.js +199 -220
  21. package/components/Map/MapContainer.js +14 -18
  22. package/components/Map/MapDateSliderControl.js +44 -46
  23. package/components/Map/MapDrawer.js +32 -33
  24. package/components/Map/MapLegend.js +96 -95
  25. package/components/Map/MapViewStyleControl.js +38 -48
  26. package/components/Map/ReactMapboxWrapper.js +2 -9
  27. package/components/Map/index.js +47 -55
  28. package/components/Map/types.js +2 -8
  29. package/components/Map/useMaintainLayerOrder.js +4 -11
  30. package/components/Markdown/index.js +14 -21
  31. package/components/Message/Message.js +59 -51
  32. package/components/MultiSelect/MultiSelect.test.js +22 -24
  33. package/components/MultiSelect/index.js +73 -69
  34. package/components/Navbar/NavFooter.js +8 -13
  35. package/components/Navbar/NavItem.js +43 -37
  36. package/components/Navbar/NavSection.js +13 -17
  37. package/components/Navbar/Navbar.js +35 -38
  38. package/components/Navbar/SubNavbar.js +8 -13
  39. package/components/Note.js +14 -19
  40. package/components/OverlayModal/OverlayModal.js +36 -37
  41. package/components/PageForm/index.js +30 -35
  42. package/components/PhotoChooser/index.js +93 -98
  43. package/components/Pill/index.js +29 -31
  44. package/components/PillList/index.js +16 -22
  45. package/components/PlacementWrapper/index.js +20 -25
  46. package/components/PopoverMenu/Popover.js +33 -37
  47. package/components/PopoverMenu/index.js +41 -46
  48. package/components/PopoverMultiSelect/index.js +18 -21
  49. package/components/ProgressCircle/ProgressCircle.js +23 -26
  50. package/components/RadioButton.js +7 -14
  51. package/components/Select/Select.js +8 -13
  52. package/components/Select/SelectT.js +110 -108
  53. package/components/Separator/index.js +8 -14
  54. package/components/Size.js +1 -5
  55. package/components/Slider/index.js +7 -13
  56. package/components/SummaryBox.js +36 -39
  57. package/components/Tabs/index.js +75 -77
  58. package/components/TextTruncated/index.js +64 -65
  59. package/components/Textfield/Textfield.js +100 -95
  60. package/components/Textfield/TruncatedTextArea.js +44 -46
  61. package/components/TimelineSlider/TimelineMarker.js +24 -28
  62. package/components/TimelineSlider/TimelineRail.js +5 -11
  63. package/components/TimelineSlider/index.js +47 -51
  64. package/components/Tooltip/Tooltip.js +21 -26
  65. package/components/ViewPhotosDialog/index.js +58 -62
  66. package/components/VirtualWalkthrough/Annotation.d.ts +41 -0
  67. package/components/VirtualWalkthrough/Annotation.d.ts.map +1 -0
  68. package/components/VirtualWalkthrough/Annotation.js +197 -0
  69. package/components/VirtualWalkthrough/AnnotationEditPane.d.ts +20 -0
  70. package/components/VirtualWalkthrough/AnnotationEditPane.d.ts.map +1 -0
  71. package/components/VirtualWalkthrough/AnnotationEditPane.js +136 -0
  72. package/components/VirtualWalkthrough/AnnotationPanel.d.ts +13 -0
  73. package/components/VirtualWalkthrough/AnnotationPanel.d.ts.map +1 -0
  74. package/components/VirtualWalkthrough/AnnotationPanel.js +196 -0
  75. package/components/VirtualWalkthrough/Application.d.ts +9 -0
  76. package/components/VirtualWalkthrough/Application.d.ts.map +1 -0
  77. package/components/VirtualWalkthrough/Application.js +32 -0
  78. package/components/VirtualWalkthrough/AutoRotator.d.ts +109 -0
  79. package/components/VirtualWalkthrough/AutoRotator.d.ts.map +1 -0
  80. package/components/VirtualWalkthrough/AutoRotator.js +217 -0
  81. package/components/VirtualWalkthrough/BlockingSpinner.d.ts +3 -0
  82. package/components/VirtualWalkthrough/BlockingSpinner.d.ts.map +1 -0
  83. package/components/VirtualWalkthrough/BlockingSpinner.js +27 -0
  84. package/components/VirtualWalkthrough/BoundaryRing.d.ts +9 -0
  85. package/components/VirtualWalkthrough/BoundaryRing.d.ts.map +1 -0
  86. package/components/VirtualWalkthrough/BoundaryRing.js +33 -0
  87. package/components/VirtualWalkthrough/CameraInfo.d.ts +16 -0
  88. package/components/VirtualWalkthrough/CameraInfo.d.ts.map +1 -0
  89. package/components/VirtualWalkthrough/CameraInfo.js +88 -0
  90. package/components/VirtualWalkthrough/ControlsInfoPane.d.ts +37 -0
  91. package/components/VirtualWalkthrough/ControlsInfoPane.d.ts.map +1 -0
  92. package/components/VirtualWalkthrough/ControlsInfoPane.js +196 -0
  93. package/components/VirtualWalkthrough/GradientSky.d.ts +9 -0
  94. package/components/VirtualWalkthrough/GradientSky.d.ts.map +1 -0
  95. package/components/VirtualWalkthrough/GradientSky.js +73 -0
  96. package/components/VirtualWalkthrough/SplatControls.d.ts +50 -0
  97. package/components/VirtualWalkthrough/SplatControls.d.ts.map +1 -0
  98. package/components/VirtualWalkthrough/SplatControls.js +252 -0
  99. package/components/VirtualWalkthrough/SplatCrop.d.ts +8 -0
  100. package/components/VirtualWalkthrough/SplatCrop.d.ts.map +1 -0
  101. package/components/VirtualWalkthrough/SplatCrop.js +18 -0
  102. package/components/VirtualWalkthrough/SplatFadeCrop.d.ts +8 -0
  103. package/components/VirtualWalkthrough/SplatFadeCrop.d.ts.map +1 -0
  104. package/components/VirtualWalkthrough/SplatFadeCrop.js +18 -0
  105. package/components/VirtualWalkthrough/SplatModel.d.ts +15 -0
  106. package/components/VirtualWalkthrough/SplatModel.d.ts.map +1 -0
  107. package/components/VirtualWalkthrough/SplatModel.js +60 -0
  108. package/components/VirtualWalkthrough/SplatRevealRain.d.ts +18 -0
  109. package/components/VirtualWalkthrough/SplatRevealRain.d.ts.map +1 -0
  110. package/components/VirtualWalkthrough/SplatRevealRain.js +38 -0
  111. package/components/VirtualWalkthrough/TfAnnotationManager.d.ts +110 -0
  112. package/components/VirtualWalkthrough/TfAnnotationManager.d.ts.map +1 -0
  113. package/components/VirtualWalkthrough/TfAnnotationManager.js +368 -0
  114. package/components/VirtualWalkthrough/TfXrNavigation.d.ts +15 -0
  115. package/components/VirtualWalkthrough/TfXrNavigation.d.ts.map +1 -0
  116. package/components/VirtualWalkthrough/TfXrNavigation.js +24 -0
  117. package/components/VirtualWalkthrough/VirtualWalkthroughViewer.d.ts +27 -0
  118. package/components/VirtualWalkthrough/VirtualWalkthroughViewer.d.ts.map +1 -0
  119. package/components/VirtualWalkthrough/VirtualWalkthroughViewer.js +282 -0
  120. package/components/VirtualWalkthrough/annotation-styles.css +5 -0
  121. package/components/VirtualWalkthrough/application-styles.css +4 -0
  122. package/components/VirtualWalkthrough/boundary-ring.d.ts +47 -0
  123. package/components/VirtualWalkthrough/boundary-ring.d.ts.map +1 -0
  124. package/components/VirtualWalkthrough/boundary-ring.js +125 -0
  125. package/components/VirtualWalkthrough/boundary-ring.test.js +109 -0
  126. package/components/VirtualWalkthrough/groundPlane.d.ts +14 -0
  127. package/components/VirtualWalkthrough/groundPlane.d.ts.map +1 -0
  128. package/components/VirtualWalkthrough/groundPlane.js +47 -0
  129. package/components/VirtualWalkthrough/groundPlane.test.js +45 -0
  130. package/components/VirtualWalkthrough/gsplat-fade-crop-effect.d.ts +19 -0
  131. package/components/VirtualWalkthrough/gsplat-fade-crop-effect.d.ts.map +1 -0
  132. package/components/VirtualWalkthrough/gsplat-fade-crop-effect.js +119 -0
  133. package/components/VirtualWalkthrough/walkthrough-camera.d.ts +77 -0
  134. package/components/VirtualWalkthrough/walkthrough-camera.d.ts.map +1 -0
  135. package/components/VirtualWalkthrough/walkthrough-camera.js +320 -0
  136. package/components/svg/BlobbyGrayIconImage.js +36 -41
  137. package/components/svg/BlobbyGrayIconImport.js +36 -41
  138. package/components/svg/BlobbyGrayIconUploadToTheCloud.js +36 -41
  139. package/components/svg/BlobbyIconCloudChecked.js +161 -158
  140. package/components/svg/BlobbyIconExportOrUploadPhoto.js +161 -158
  141. package/components/svg/BlobbyIconFolder.js +161 -158
  142. package/components/svg/BlobbyIconGraphReport.js +161 -158
  143. package/components/svg/BlobbyIconHappy.js +161 -158
  144. package/components/svg/BlobbyIconHeartMonitor.js +161 -158
  145. package/components/svg/BlobbyIconLeaf.js +161 -158
  146. package/components/svg/BlobbyIconLibrary.js +161 -158
  147. package/components/svg/BlobbyIconNursery.js +163 -160
  148. package/components/svg/BlobbyIconOrganization.js +161 -158
  149. package/components/svg/BlobbyIconParchment.js +161 -158
  150. package/components/svg/BlobbyIconPeople.js +161 -158
  151. package/components/svg/BlobbyIconSeedBank.js +161 -158
  152. package/components/svg/BlobbyIconSeedling.js +161 -158
  153. package/components/svg/BlobbyIconSeeds.js +161 -158
  154. package/components/svg/BlobbyIconSite.js +164 -161
  155. package/components/svg/BlobbyIconSparkles.js +161 -158
  156. package/components/svg/BlobbyIconWrench.js +161 -158
  157. package/components/svg/Bug.js +18 -27
  158. package/components/svg/Calendar.js +18 -27
  159. package/components/svg/CaretDown.js +18 -27
  160. package/components/svg/CaretLeft.js +18 -27
  161. package/components/svg/CaretRight.js +18 -27
  162. package/components/svg/CaretUp.js +18 -27
  163. package/components/svg/ChevronDown.js +18 -27
  164. package/components/svg/ChevronUp.js +18 -27
  165. package/components/svg/Close.js +18 -27
  166. package/components/svg/Critical.js +18 -27
  167. package/components/svg/Edit.js +161 -158
  168. package/components/svg/Error.js +18 -27
  169. package/components/svg/Filter.js +18 -27
  170. package/components/svg/Folder.js +18 -27
  171. package/components/svg/Help.js +18 -27
  172. package/components/svg/Home.js +18 -27
  173. package/components/svg/IconAdd.js +18 -27
  174. package/components/svg/IconArrowRight.js +19 -28
  175. package/components/svg/IconBusinessNetwork.js +18 -27
  176. package/components/svg/IconCalendar.js +18 -27
  177. package/components/svg/IconCancel.js +18 -27
  178. package/components/svg/IconChargingBattery.js +18 -27
  179. package/components/svg/IconChecklist.js +19 -28
  180. package/components/svg/IconCheckmark.js +18 -27
  181. package/components/svg/IconCoinInHand.js +18 -27
  182. package/components/svg/IconColumns.js +18 -27
  183. package/components/svg/IconComment.js +18 -27
  184. package/components/svg/IconDashboard.js +18 -27
  185. package/components/svg/IconDataMigration.js +18 -27
  186. package/components/svg/IconDocument.js +19 -28
  187. package/components/svg/IconDownloadFromTheCloud.js +19 -28
  188. package/components/svg/IconEdit.js +19 -28
  189. package/components/svg/IconExpand.js +19 -28
  190. package/components/svg/IconExport.js +18 -27
  191. package/components/svg/IconExternalLink.js +18 -27
  192. package/components/svg/IconEye.js +18 -27
  193. package/components/svg/IconEyeOff.js +18 -27
  194. package/components/svg/IconFile.js +18 -27
  195. package/components/svg/IconFolder.js +18 -27
  196. package/components/svg/IconFullScreen.js +18 -27
  197. package/components/svg/IconFutures.js +18 -27
  198. package/components/svg/IconGraphReport.js +18 -27
  199. package/components/svg/IconHeartMonitor.js +18 -27
  200. package/components/svg/IconHelp.js +19 -28
  201. package/components/svg/IconHistory.js +18 -27
  202. package/components/svg/IconImport.js +18 -27
  203. package/components/svg/IconIndex.js +18 -27
  204. package/components/svg/IconLayers.js +18 -27
  205. package/components/svg/IconLibrary.js +18 -27
  206. package/components/svg/IconList.js +18 -27
  207. package/components/svg/IconLivePlant.js +18 -27
  208. package/components/svg/IconLocations.js +18 -27
  209. package/components/svg/IconMail.js +18 -27
  210. package/components/svg/IconManager.js +18 -27
  211. package/components/svg/IconMarker.js +18 -27
  212. package/components/svg/IconMenu.js +18 -27
  213. package/components/svg/IconMenuHorizontal.js +18 -27
  214. package/components/svg/IconModule.js +18 -27
  215. package/components/svg/IconMyLocation.js +18 -27
  216. package/components/svg/IconNote.js +19 -28
  217. package/components/svg/IconNursery.js +18 -27
  218. package/components/svg/IconOrg.js +18 -27
  219. package/components/svg/IconParchment.js +18 -27
  220. package/components/svg/IconPhoto.js +18 -27
  221. package/components/svg/IconPlantsFilled.js +18 -27
  222. package/components/svg/IconRedo.js +18 -27
  223. package/components/svg/IconRestorationSite.js +18 -27
  224. package/components/svg/IconSeedBank.js +18 -27
  225. package/components/svg/IconSeedling.js +18 -27
  226. package/components/svg/IconSettings.js +18 -27
  227. package/components/svg/IconSlice.js +18 -27
  228. package/components/svg/IconSubmit.js +18 -27
  229. package/components/svg/IconSubtract.js +19 -28
  230. package/components/svg/IconSynced.js +18 -27
  231. package/components/svg/IconTrashCan.js +18 -27
  232. package/components/svg/IconTreasureMap.js +18 -27
  233. package/components/svg/IconUnavailable.js +18 -27
  234. package/components/svg/IconUndo.js +18 -27
  235. package/components/svg/IconVariable.js +18 -27
  236. package/components/svg/IconVideo.js +19 -28
  237. package/components/svg/IconWifi.js +18 -27
  238. package/components/svg/Info.js +18 -27
  239. package/components/svg/Key.js +18 -27
  240. package/components/svg/Leaf.js +18 -27
  241. package/components/svg/Lock.js +18 -27
  242. package/components/svg/Logo.js +40 -45
  243. package/components/svg/MenuVertical.js +18 -27
  244. package/components/svg/Notification.js +18 -27
  245. package/components/svg/Person.js +18 -27
  246. package/components/svg/Plus.js +18 -27
  247. package/components/svg/RestorationSite.js +18 -27
  248. package/components/svg/Search.js +18 -27
  249. package/components/svg/Seeds.js +18 -27
  250. package/components/svg/Site.js +18 -27
  251. package/components/svg/Sparkles.js +18 -27
  252. package/components/svg/Species.js +18 -27
  253. package/components/svg/Species2.js +161 -158
  254. package/components/svg/Spinner.js +27 -36
  255. package/components/svg/Star.js +18 -27
  256. package/components/svg/Success.js +18 -27
  257. package/components/svg/SuccessFilled.js +18 -27
  258. package/components/svg/TerrawareLogoDesktop.js +44 -49
  259. package/components/svg/TerrawareLogoMobile.js +20 -29
  260. package/components/svg/Touchscreen.js +18 -27
  261. package/components/svg/UploadCloud.js +161 -158
  262. package/components/svg/Warning.js +18 -27
  263. package/components/svg/WelcomeClipboard.js +130 -135
  264. package/components/svg/index.js +127 -895
  265. package/components/table/EnhancedTableToolbar.js +45 -47
  266. package/components/table/EnhancedTableToolbarV2.js +18 -26
  267. package/components/table/TableCellRenderer.js +137 -143
  268. package/components/table/TableHeader.js +45 -47
  269. package/components/table/TableHeaderItem.js +65 -64
  270. package/components/table/density.js +3 -11
  271. package/components/table/index.js +225 -218
  272. package/components/table/sort.js +8 -18
  273. package/components/table/sort.test.js +6 -8
  274. package/components/table/types.js +1 -5
  275. package/components/types/index.js +1 -5
  276. package/hooks/useBoolean.d.ts +4 -0
  277. package/hooks/useBoolean.d.ts.map +1 -0
  278. package/hooks/useBoolean.js +12 -0
  279. package/hooks/useCameraPosition.d.ts +8 -0
  280. package/hooks/useCameraPosition.d.ts.map +1 -0
  281. package/hooks/useCameraPosition.js +33 -0
  282. package/hooks/useDevicePerformance.d.ts +9 -0
  283. package/hooks/useDevicePerformance.d.ts.map +1 -0
  284. package/hooks/useDevicePerformance.js +16 -0
  285. package/index.js +57 -398
  286. package/license-report.html +1 -1
  287. package/package.json +10 -3
  288. package/style-dictionary-dist/TerrawareTheme.js +1 -7
  289. package/theme.js +7 -14
  290. package/utils/color.d.ts +2 -0
  291. package/utils/color.d.ts.map +1 -0
  292. package/utils/color.js +14 -0
  293. package/utils/date.js +13 -24
  294. package/utils/date.test.js +40 -43
  295. package/utils/index.js +5 -53
  296. package/utils/preventDefaultEvent.js +1 -7
  297. package/utils/text.js +2 -9
  298. package/utils/text.test.js +9 -11
  299. package/utils/useDeviceInfo.js +10 -16
  300. package/virtualWalkthrough.d.ts +34 -0
  301. package/virtualWalkthrough.d.ts.map +1 -0
  302. package/virtualWalkthrough.js +30 -0
@@ -0,0 +1,282 @@
1
+ import React, { useCallback, useEffect, useMemo, useState } from 'react';
2
+ import { useTheme } from '@mui/material';
3
+ import { Entity } from '@playcanvas/react';
4
+ import { Camera, Script } from '@playcanvas/react/components';
5
+ import { useApp } from '@playcanvas/react/hooks';
6
+ import { Color, Vec3 } from 'playcanvas';
7
+ import { XrControllers } from 'playcanvas/scripts/esm/xr-controllers.mjs';
8
+ import { useCameraPosition } from '../../hooks/useCameraPosition';
9
+ import { useDevicePerformance } from '../../hooks/useDevicePerformance';
10
+ import Annotation from './Annotation';
11
+ import AnnotationPanel from './AnnotationPanel';
12
+ import { AutoRotator } from './AutoRotator';
13
+ import BoundaryRing from './BoundaryRing';
14
+ import GradientSky from './GradientSky';
15
+ import SplatControls from './SplatControls';
16
+ import SplatModel from './SplatModel';
17
+ import { TfAnnotationManager } from './TfAnnotationManager';
18
+ import { TfXrNavigation } from './TfXrNavigation';
19
+ import { WalkthroughCamera } from './walkthrough-camera';
20
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
21
+ const DEFAULT_FOCUS_POINT = [0, 0.1, 0];
22
+ const DEFAULT_POSITION = [1, 0.1, 0];
23
+ const VirtualWalkthroughViewer = ({
24
+ splatSrc,
25
+ origin = DEFAULT_FOCUS_POINT,
26
+ cameraPosition = DEFAULT_POSITION,
27
+ sceneBounds,
28
+ groundPlane: groundPlaneProp,
29
+ skyColor,
30
+ groundColor,
31
+ averageCameraHeight = 0,
32
+ annotations,
33
+ onSaveAnnotations,
34
+ strings,
35
+ editable = false,
36
+ showFreeFly = false,
37
+ isFullScreen = false,
38
+ onToggleFullScreen
39
+ }) => {
40
+ const theme = useTheme();
41
+ const {
42
+ setCamera
43
+ } = useCameraPosition();
44
+ const {
45
+ isHighPerformance
46
+ } = useDevicePerformance();
47
+ const app = useApp();
48
+ const [showAnnotations, setShowAnnotations] = useState(true);
49
+ const [autoRotate, setAutoRotate] = useState(true);
50
+ const [isEdit, setIsEdit] = useState(false);
51
+ const [isFreeFly, setIsFreeFly] = useState(false);
52
+ const [selectedAnnotationIndex, setSelectedAnnotationIndex] = useState(-1);
53
+ const [localAnnotations, setLocalAnnotations] = useState(annotations);
54
+ const [isTextFieldFocused, setIsTextFieldFocused] = useState(false);
55
+ const [viewingAnnotation, setViewingAnnotation] = useState(null);
56
+ const [viewingAnnotationIndex, setViewingAnnotationIndex] = useState(-1);
57
+ const [viewedScreenPos, setViewedScreenPos] = useState(null);
58
+ const sceneBoundsRadius = useMemo(() => {
59
+ if (sceneBounds?.m !== undefined) {
60
+ return sceneBounds.m;
61
+ }
62
+ const dx = cameraPosition[0] - origin[0];
63
+ const dy = cameraPosition[1] - origin[1];
64
+ const dz = cameraPosition[2] - origin[2];
65
+ return Math.sqrt(dx * dx + dy * dy + dz * dz) * 0.5;
66
+ }, [cameraPosition, sceneBounds, origin]);
67
+ const sceneBoundsCenter = useMemo(() => sceneBounds ? new Vec3(sceneBounds.x, sceneBounds.y, sceneBounds.z) : new Vec3(origin[0], cameraPosition[1], origin[2]), [sceneBounds, origin, cameraPosition]);
68
+ const groundPlane = useMemo(() => groundPlaneProp?.length === 3 ? groundPlaneProp.map(p => new Vec3(p[0], p[1], p[2])) : [], [groundPlaneProp]);
69
+ useEffect(() => {
70
+ setCamera(origin, cameraPosition);
71
+ }, [origin, cameraPosition, setCamera]);
72
+ useEffect(() => {
73
+ if (!groundPlane.length) {
74
+ return;
75
+ }
76
+ // @ts-expect-error - scripts are added dynamically to the camera entity
77
+ const walkthroughCam = app.root.findByName('camera')?.script?.walkthroughCamera;
78
+ if (walkthroughCam) {
79
+ // Should be changed to a react prop if shallowEquals in playcanvas/react is fixed (see https://github.com/playcanvas/react/pull/298)
80
+ walkthroughCam.groundPlane = groundPlane;
81
+ }
82
+ }, [groundPlane, app]);
83
+ const handleToggleFreeFly = useCallback(() => {
84
+ const newFreeFly = !isFreeFly;
85
+ // @ts-expect-error - scripts are added dynamically to the camera entity
86
+ const walkthroughCam = app.root.findByName('camera')?.script?.walkthroughCamera;
87
+ if (walkthroughCam) {
88
+ walkthroughCam.freeFly = newFreeFly;
89
+ }
90
+ if (!newFreeFly) {
91
+ setCamera(origin, cameraPosition);
92
+ }
93
+ setIsFreeFly(newFreeFly);
94
+ }, [isFreeFly, app, setCamera, origin, cameraPosition]);
95
+ useEffect(() => {
96
+ setLocalAnnotations(annotations);
97
+ }, [annotations]);
98
+ useEffect(() => {
99
+ if (!isEdit) {
100
+ setSelectedAnnotationIndex(-1);
101
+ }
102
+ }, [isEdit]);
103
+ const handleAnnotationPositionChange = useCallback(position => {
104
+ setLocalAnnotations(prev => {
105
+ if (selectedAnnotationIndex === -1) {
106
+ return prev;
107
+ }
108
+ const updated = [...prev];
109
+ updated[selectedAnnotationIndex] = {
110
+ ...updated[selectedAnnotationIndex],
111
+ position
112
+ };
113
+ return updated;
114
+ });
115
+ }, [selectedAnnotationIndex]);
116
+ const handleSave = useCallback(() => {
117
+ const saveAndClose = async () => {
118
+ await onSaveAnnotations(localAnnotations);
119
+ setIsEdit(false);
120
+ setSelectedAnnotationIndex(-1);
121
+ };
122
+ void saveAndClose();
123
+ }, [onSaveAnnotations, localAnnotations]);
124
+ const handleCancel = useCallback(() => {
125
+ setLocalAnnotations(annotations);
126
+ setIsEdit(false);
127
+ setSelectedAnnotationIndex(-1);
128
+ }, [annotations]);
129
+ const handleAddAnnotation = useCallback(() => {
130
+ const newAnnotation = {
131
+ position: origin,
132
+ title: ''
133
+ };
134
+ setLocalAnnotations(prev => [...prev, newAnnotation]);
135
+ setSelectedAnnotationIndex(localAnnotations.length);
136
+ }, [origin, localAnnotations]);
137
+ const handleDeleteAnnotation = useCallback(() => {
138
+ if (selectedAnnotationIndex === -1) {
139
+ return;
140
+ }
141
+ setLocalAnnotations(prev => prev.filter((_, index) => index !== selectedAnnotationIndex));
142
+ setSelectedAnnotationIndex(-1);
143
+ }, [selectedAnnotationIndex]);
144
+ const handleDeselectAnnotation = useCallback(() => {
145
+ setSelectedAnnotationIndex(-1);
146
+ }, []);
147
+ const handleAnnotationView = useCallback(
148
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
149
+ (annotation, annotationIndex, screenX, screenY) => {
150
+ setViewingAnnotation(annotation);
151
+ setViewingAnnotationIndex(annotationIndex);
152
+ // Cleared so the connector line waits for the hotspot's post-aim position
153
+ // rather than briefly pointing at its old spot.
154
+ setViewedScreenPos(null);
155
+ }, []);
156
+ const handleAnnotationScreenPositionUpdate = useCallback((_index, screenX, screenY, size) => {
157
+ // The scene is frozen while the panel is open, so guard against redundant
158
+ // updates from the per-frame callback to avoid needless re-renders.
159
+ setViewedScreenPos(prev => prev && prev.x === screenX && prev.y === screenY && prev.size === size ? prev : {
160
+ x: screenX,
161
+ y: screenY,
162
+ size
163
+ });
164
+ }, []);
165
+ const handleCloseAnnotation = useCallback(() => {
166
+ setViewingAnnotation(null);
167
+ setViewingAnnotationIndex(-1);
168
+ setViewedScreenPos(null);
169
+ }, []);
170
+ const handleAnnotationUpdate = useCallback(updates => {
171
+ if (selectedAnnotationIndex === -1) {
172
+ return;
173
+ }
174
+ setLocalAnnotations(prev => {
175
+ const updated = [...prev];
176
+ updated[selectedAnnotationIndex] = {
177
+ ...updated[selectedAnnotationIndex],
178
+ ...updates
179
+ };
180
+ return updated;
181
+ });
182
+ }, [selectedAnnotationIndex]);
183
+ const canSave = useMemo(() => localAnnotations.every(annotation => annotation.title && annotation.title.trim() !== ''), [localAnnotations]);
184
+ const splatModel = useMemo(() => /*#__PURE__*/_jsx(SplatModel, {
185
+ splatSrc: splatSrc,
186
+ rotation: [-180, 0, 0],
187
+ revealRain: isHighPerformance
188
+ }, 'splat'), [isHighPerformance, splatSrc]);
189
+ return /*#__PURE__*/_jsxs(_Fragment, {
190
+ children: [/*#__PURE__*/_jsx(GradientSky, {
191
+ topColor: skyColor || '#FFFFFF',
192
+ horizonColor: skyColor || '#EAF8FF',
193
+ groundColor: groundColor || '#C3BDB7'
194
+ }), /*#__PURE__*/_jsxs(Entity, {
195
+ name: "camera-root",
196
+ children: [/*#__PURE__*/_jsxs(Entity, {
197
+ name: "camera",
198
+ children: [/*#__PURE__*/_jsx(Camera, {
199
+ clearColor: "#EAF8FF",
200
+ fov: 60
201
+ }), /*#__PURE__*/_jsx(Script, {
202
+ script: WalkthroughCamera,
203
+ boundsCenter: sceneBoundsCenter,
204
+ boundsRadius: sceneBoundsRadius,
205
+ enableFly: !isTextFieldFocused,
206
+ averageCameraHeight: averageCameraHeight
207
+ })]
208
+ }), /*#__PURE__*/_jsx(Script, {
209
+ script: XrControllers,
210
+ enabled: !isEdit
211
+ }), /*#__PURE__*/_jsx(Script, {
212
+ script: TfXrNavigation,
213
+ enabled: !isEdit,
214
+ enableTeleport: false
215
+ }), /*#__PURE__*/_jsx(Script, {
216
+ script: AutoRotator,
217
+ enabled: !isEdit && autoRotate && !viewingAnnotation,
218
+ startDelay: 0.5,
219
+ restartDelay: 3,
220
+ startFadeInTime: 0.5
221
+ })]
222
+ }), splatModel, sceneBounds?.m !== undefined && groundPlane.length === 3 && /*#__PURE__*/_jsx(BoundaryRing, {
223
+ center: sceneBoundsCenter,
224
+ radius: sceneBoundsRadius,
225
+ groundPlane: groundPlane
226
+ }), localAnnotations.length > 0 && /*#__PURE__*/_jsxs(Entity, {
227
+ name: "annotations-root",
228
+ children: [/*#__PURE__*/_jsx(Script, {
229
+ script: TfAnnotationManager,
230
+ enabled: true,
231
+ hotspotSize: 30,
232
+ maxWorldSize: 0.05,
233
+ opacity: 1,
234
+ hotspotColor: new Color().fromString(theme.palette.TwClrIcnBrand),
235
+ hoverColor: new Color().fromString('#ffffff'),
236
+ hotspotBackgroundColor: theme.palette.TwClrBaseWhite
237
+ }), localAnnotations.map((annotation, index) => /*#__PURE__*/_jsx(Annotation, {
238
+ ...annotation,
239
+ index: index,
240
+ visible: showAnnotations,
241
+ isEdit: isEdit,
242
+ isSelected: selectedAnnotationIndex === index,
243
+ isViewed: viewingAnnotationIndex === index,
244
+ onSelect: () => setSelectedAnnotationIndex(index),
245
+ onPositionChange: handleAnnotationPositionChange,
246
+ onView: (anno, screenX, screenY) => handleAnnotationView(anno, index, screenX, screenY),
247
+ onScreenPositionUpdate: handleAnnotationScreenPositionUpdate
248
+ }, `annotation-${index}`))]
249
+ }), /*#__PURE__*/_jsx(SplatControls, {
250
+ defaultCameraFocus: origin,
251
+ defaultCameraPosition: cameraPosition,
252
+ showAnnotations: showAnnotations,
253
+ onToggleAnnotations: setShowAnnotations,
254
+ autoRotate: autoRotate,
255
+ onToggleAutoRotate: setAutoRotate,
256
+ isEdit: isEdit,
257
+ onToggleEdit: setIsEdit,
258
+ onSave: handleSave,
259
+ onCancel: handleCancel,
260
+ onAddAnnotation: handleAddAnnotation,
261
+ onDeleteAnnotation: handleDeleteAnnotation,
262
+ onDeselectAnnotation: handleDeselectAnnotation,
263
+ hasSelectedAnnotation: selectedAnnotationIndex >= 0,
264
+ selectedAnnotation: selectedAnnotationIndex >= 0 ? localAnnotations[selectedAnnotationIndex] : null,
265
+ onAnnotationUpdate: handleAnnotationUpdate,
266
+ onTextFieldFocus: setIsTextFieldFocused,
267
+ canSave: canSave,
268
+ editable: editable,
269
+ isFullScreen: isFullScreen,
270
+ onToggleFullScreen: onToggleFullScreen,
271
+ isFreeFly: isFreeFly,
272
+ onToggleFreeFly: handleToggleFreeFly,
273
+ showFreeFly: showFreeFly,
274
+ strings: strings
275
+ }), /*#__PURE__*/_jsx(AnnotationPanel, {
276
+ annotation: viewingAnnotation,
277
+ hotspotPosition: viewedScreenPos,
278
+ onClose: handleCloseAnnotation
279
+ })]
280
+ });
281
+ };
282
+ export default VirtualWalkthroughViewer;
@@ -0,0 +1,5 @@
1
+ /* This is necessary to ensure annotations are in front of other elements in the page, such as overlay backdrops. */
2
+ .pc-annotation,
3
+ .pc-annotation-hotspot {
4
+ z-index: 5000;
5
+ }
@@ -0,0 +1,4 @@
1
+ /* Allow pointer events on annotation hotspots while container blocks events */
2
+ .annotation-container > * {
3
+ pointer-events: auto;
4
+ }
@@ -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
+ }
@@ -0,0 +1,109 @@
1
+ import { Vec3 } from 'playcanvas';
2
+ import { boundaryRingMesh } from './boundary-ring';
3
+ const FLAT = [new Vec3(0, 0, 0), new Vec3(1, 0, 0), new Vec3(0, 0, 1)];
4
+ const toVertices = positions => {
5
+ const out = [];
6
+ for (let i = 0; i < positions.length; i += 3) {
7
+ out.push([positions[i], positions[i + 1], positions[i + 2]]);
8
+ }
9
+ return out;
10
+ };
11
+ describe('boundaryRingMesh', () => {
12
+ it('produces four vertices and six indices per dash', () => {
13
+ const geom = boundaryRingMesh({
14
+ center: new Vec3(0, 0, 0),
15
+ radius: 5,
16
+ width: 0.1,
17
+ groundPlane: FLAT,
18
+ dashCount: 16
19
+ });
20
+ expect(geom.positions).toHaveLength(16 * 4 * 3);
21
+ expect(geom.indices).toHaveLength(16 * 6);
22
+ });
23
+ it('places every vertex at the inner or outer radius from center in XZ', () => {
24
+ const center = new Vec3(2, 0, -3);
25
+ const radius = 4;
26
+ const width = 0.2;
27
+ const inner = radius - width / 2;
28
+ const outer = radius + width / 2;
29
+ const geom = boundaryRingMesh({
30
+ center,
31
+ radius,
32
+ width,
33
+ groundPlane: FLAT,
34
+ dashCount: 32
35
+ });
36
+ for (const [x,, z] of toVertices(geom.positions)) {
37
+ const d = Math.sqrt((x - center.x) ** 2 + (z - center.z) ** 2);
38
+ const atInner = Math.abs(d - inner) < 1e-6;
39
+ const atOuter = Math.abs(d - outer) < 1e-6;
40
+ expect(atInner || atOuter).toBe(true);
41
+ }
42
+ });
43
+ it('lifts every vertex onto a tilted ground plane (y = z)', () => {
44
+ const tilted = [new Vec3(0, 0, 0), new Vec3(1, 0, 0), new Vec3(0, 1, 1)];
45
+ const geom = boundaryRingMesh({
46
+ center: new Vec3(0, 0, 0),
47
+ radius: 3,
48
+ width: 0.1,
49
+ groundPlane: tilted,
50
+ dashCount: 8
51
+ });
52
+ for (const [, y, z] of toVertices(geom.positions)) {
53
+ expect(y).toBeCloseTo(z);
54
+ }
55
+ });
56
+ it('references only in-range vertices from the index buffer', () => {
57
+ const geom = boundaryRingMesh({
58
+ center: new Vec3(0, 0, 0),
59
+ radius: 5,
60
+ width: 0.1,
61
+ groundPlane: FLAT,
62
+ dashCount: 8
63
+ });
64
+ const vertexCount = geom.positions.length / 3;
65
+ for (const idx of geom.indices) {
66
+ expect(idx).toBeGreaterThanOrEqual(0);
67
+ expect(idx).toBeLessThan(vertexCount);
68
+ }
69
+ });
70
+ it('returns empty geometry for a non-positive radius', () => {
71
+ const geom = boundaryRingMesh({
72
+ center: new Vec3(0, 0, 0),
73
+ radius: 0,
74
+ width: 0.1,
75
+ groundPlane: FLAT
76
+ });
77
+ expect(geom.positions).toHaveLength(0);
78
+ expect(geom.indices).toHaveLength(0);
79
+ });
80
+ it('returns empty geometry for a non-positive width', () => {
81
+ const geom = boundaryRingMesh({
82
+ center: new Vec3(0, 0, 0),
83
+ radius: 5,
84
+ width: 0,
85
+ groundPlane: FLAT
86
+ });
87
+ expect(geom.positions).toHaveLength(0);
88
+ });
89
+ it('returns empty geometry for a non-positive dashCount', () => {
90
+ const geom = boundaryRingMesh({
91
+ center: new Vec3(0, 0, 0),
92
+ radius: 5,
93
+ width: 0.1,
94
+ groundPlane: FLAT,
95
+ dashCount: 0
96
+ });
97
+ expect(geom.positions).toHaveLength(0);
98
+ });
99
+ it('returns empty geometry for an invalid ground plane', () => {
100
+ const collinear = [new Vec3(0, 0, 0), new Vec3(1, 0, 0), new Vec3(2, 0, 0)];
101
+ const geom = boundaryRingMesh({
102
+ center: new Vec3(0, 0, 0),
103
+ radius: 5,
104
+ width: 0.1,
105
+ groundPlane: collinear
106
+ });
107
+ expect(geom.positions).toHaveLength(0);
108
+ });
109
+ });
@@ -0,0 +1,14 @@
1
+ import { Vec3 } from 'playcanvas';
2
+ export type GroundPlane = {
3
+ normal: Vec3;
4
+ point: Vec3;
5
+ };
6
+ /** Compute Y on the plane defined by `normal` passing through `point` at world XZ coords (x, z). */
7
+ export declare const yOnPlane: (x: number, z: number, normal: Vec3, point: Vec3, fallback: number) => number;
8
+ /**
9
+ * Derive a ground plane from 3 world-space points.
10
+ * Returns a normalized normal (flipped so normal.y >= 0) and a point on the plane,
11
+ * or null when fewer than 3 points are supplied or they are degenerate/collinear.
12
+ */
13
+ export declare const computeGroundPlane: (points: Vec3[]) => GroundPlane | null;
14
+ //# sourceMappingURL=groundPlane.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"groundPlane.d.ts","sourceRoot":"","sources":["../../../src/components/VirtualWalkthrough/groundPlane.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAElC,MAAM,MAAM,WAAW,GAAG;IAAE,MAAM,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,IAAI,CAAA;CAAE,CAAC;AAExD,oGAAoG;AACpG,eAAO,MAAM,QAAQ,GAAI,GAAG,MAAM,EAAE,GAAG,MAAM,EAAE,QAAQ,IAAI,EAAE,OAAO,IAAI,EAAE,UAAU,MAAM,KAAG,MAM5F,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,GAAI,QAAQ,IAAI,EAAE,KAAG,WAAW,GAAG,IA8BjE,CAAC"}
@@ -0,0 +1,47 @@
1
+ import { Vec3 } from 'playcanvas';
2
+ /** Compute Y on the plane defined by `normal` passing through `point` at world XZ coords (x, z). */
3
+ export const yOnPlane = (x, z, normal, point, fallback) => {
4
+ if (Math.abs(normal.y) < 1e-6) {
5
+ return fallback;
6
+ }
7
+ return point.y - (normal.x * (x - point.x) + normal.z * (z - point.z)) / normal.y;
8
+ };
9
+
10
+ /**
11
+ * Derive a ground plane from 3 world-space points.
12
+ * Returns a normalized normal (flipped so normal.y >= 0) and a point on the plane,
13
+ * or null when fewer than 3 points are supplied or they are degenerate/collinear.
14
+ */
15
+ export const computeGroundPlane = points => {
16
+ if (points.length < 3) {
17
+ return null;
18
+ }
19
+ const p0 = points[0];
20
+ const p1 = points[1];
21
+ const p2 = points[2];
22
+ const v1x = p1.x - p0.x;
23
+ const v1y = p1.y - p0.y;
24
+ const v1z = p1.z - p0.z;
25
+ const v2x = p2.x - p0.x;
26
+ const v2y = p2.y - p0.y;
27
+ const v2z = p2.z - p0.z;
28
+ let nx = v1y * v2z - v1z * v2y;
29
+ let ny = v1z * v2x - v1x * v2z;
30
+ let nz = v1x * v2y - v1y * v2x;
31
+ const len = Math.sqrt(nx * nx + ny * ny + nz * nz);
32
+ if (len < 1e-10) {
33
+ return null;
34
+ }
35
+ nx /= len;
36
+ ny /= len;
37
+ nz /= len;
38
+ if (ny < 0) {
39
+ nx = -nx;
40
+ ny = -ny;
41
+ nz = -nz;
42
+ }
43
+ return {
44
+ normal: new Vec3(nx, ny, nz),
45
+ point: new Vec3(p0.x, p0.y, p0.z)
46
+ };
47
+ };