itowns 2.45.1-next.0 → 2.45.1-next.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 (185) hide show
  1. package/dist/455.js +2 -0
  2. package/dist/455.js.map +1 -0
  3. package/dist/debug.js +3 -0
  4. package/dist/debug.js.LICENSE.txt +13 -0
  5. package/dist/debug.js.map +1 -0
  6. package/dist/itowns.js +3 -0
  7. package/dist/itowns.js.LICENSE.txt +5 -0
  8. package/dist/itowns.js.map +1 -0
  9. package/dist/itowns_lasparser.js +2 -0
  10. package/dist/itowns_lasparser.js.map +1 -0
  11. package/dist/itowns_lasworker.js +2 -0
  12. package/dist/itowns_lasworker.js.map +1 -0
  13. package/dist/itowns_potree2worker.js +2 -0
  14. package/dist/itowns_potree2worker.js.map +1 -0
  15. package/dist/itowns_widgets.js +2 -0
  16. package/dist/itowns_widgets.js.map +1 -0
  17. package/lib/Controls/FirstPersonControls.js +308 -0
  18. package/lib/Controls/FlyControls.js +175 -0
  19. package/lib/Controls/GlobeControls.js +1178 -0
  20. package/lib/Controls/PlanarControls.js +1025 -0
  21. package/lib/Controls/StateControl.js +432 -0
  22. package/lib/Controls/StreetControls.js +392 -0
  23. package/lib/Converter/Feature2Mesh.js +612 -0
  24. package/lib/Converter/Feature2Texture.js +174 -0
  25. package/lib/Converter/convertToTile.js +70 -0
  26. package/lib/Converter/textureConverter.js +43 -0
  27. package/lib/Core/3DTiles/C3DTBatchTable.js +131 -0
  28. package/lib/Core/3DTiles/C3DTBatchTableHierarchyExtension.js +96 -0
  29. package/lib/Core/3DTiles/C3DTBoundingVolume.js +156 -0
  30. package/lib/Core/3DTiles/C3DTExtensions.js +97 -0
  31. package/lib/Core/3DTiles/C3DTFeature.js +110 -0
  32. package/lib/Core/3DTiles/C3DTilesEnums.js +20 -0
  33. package/lib/Core/3DTiles/C3DTileset.js +99 -0
  34. package/lib/Core/3DTiles/utils/BinaryPropertyAccessor.js +100 -0
  35. package/lib/Core/AnimationPlayer.js +142 -0
  36. package/lib/Core/CopcNode.js +174 -0
  37. package/lib/Core/Deprecated/Undeprecator.js +74 -0
  38. package/lib/Core/EntwinePointTileNode.js +126 -0
  39. package/lib/Core/Feature.js +488 -0
  40. package/lib/Core/Geographic/GeoidGrid.js +108 -0
  41. package/lib/Core/Label.js +222 -0
  42. package/lib/Core/MainLoop.js +209 -0
  43. package/lib/Core/Picking.js +255 -0
  44. package/lib/Core/PointCloudNode.js +42 -0
  45. package/lib/Core/Potree2Node.js +206 -0
  46. package/lib/Core/Potree2PointAttributes.js +139 -0
  47. package/lib/Core/PotreeNode.js +101 -0
  48. package/lib/Core/Prefab/Globe/Atmosphere.js +293 -0
  49. package/lib/Core/Prefab/Globe/GlobeLayer.js +152 -0
  50. package/lib/Core/Prefab/Globe/GlobeTileBuilder.js +110 -0
  51. package/lib/Core/Prefab/Globe/SkyShader.js +78 -0
  52. package/lib/Core/Prefab/GlobeView.js +155 -0
  53. package/lib/Core/Prefab/Planar/PlanarLayer.js +59 -0
  54. package/lib/Core/Prefab/Planar/PlanarTileBuilder.js +71 -0
  55. package/lib/Core/Prefab/PlanarView.js +62 -0
  56. package/lib/Core/Prefab/TileBuilder.js +82 -0
  57. package/lib/Core/Prefab/computeBufferTileGeometry.js +248 -0
  58. package/lib/Core/Scheduler/Cache.js +17 -0
  59. package/lib/Core/Scheduler/CancelledCommandException.js +15 -0
  60. package/lib/Core/Scheduler/Scheduler.js +294 -0
  61. package/lib/Core/Style.js +660 -0
  62. package/lib/Core/StyleOptions.js +486 -0
  63. package/lib/Core/System/Capabilities.js +63 -0
  64. package/lib/Core/Tile/Tile.js +205 -0
  65. package/lib/Core/Tile/TileGrid.js +49 -0
  66. package/lib/Core/TileGeometry.js +124 -0
  67. package/lib/Core/TileMesh.js +108 -0
  68. package/lib/Core/View.js +1115 -0
  69. package/lib/Layer/C3DTilesLayer.js +459 -0
  70. package/lib/Layer/ColorLayer.js +154 -0
  71. package/lib/Layer/CopcLayer.js +63 -0
  72. package/lib/Layer/ElevationLayer.js +139 -0
  73. package/lib/Layer/EntwinePointTileLayer.js +71 -0
  74. package/lib/Layer/FeatureGeometryLayer.js +77 -0
  75. package/lib/Layer/GeoidLayer.js +80 -0
  76. package/lib/Layer/GeometryLayer.js +233 -0
  77. package/lib/Layer/InfoLayer.js +64 -0
  78. package/lib/Layer/LabelLayer.js +469 -0
  79. package/lib/Layer/Layer.js +335 -0
  80. package/lib/Layer/LayerUpdateState.js +89 -0
  81. package/lib/Layer/LayerUpdateStrategy.js +80 -0
  82. package/lib/Layer/OGC3DTilesLayer.js +543 -0
  83. package/lib/Layer/OrientedImageLayer.js +227 -0
  84. package/lib/Layer/PointCloudLayer.js +405 -0
  85. package/lib/Layer/Potree2Layer.js +171 -0
  86. package/lib/Layer/PotreeLayer.js +72 -0
  87. package/lib/Layer/RasterLayer.js +37 -0
  88. package/lib/Layer/ReferencingLayerProperties.js +62 -0
  89. package/lib/Layer/TiledGeometryLayer.js +459 -0
  90. package/lib/Loader/LASLoader.js +193 -0
  91. package/lib/Loader/Potree2BrotliLoader.js +261 -0
  92. package/lib/Loader/Potree2Loader.js +207 -0
  93. package/lib/Main.js +113 -0
  94. package/lib/MainBundle.js +4 -0
  95. package/lib/Parser/B3dmParser.js +174 -0
  96. package/lib/Parser/CameraCalibrationParser.js +94 -0
  97. package/lib/Parser/GDFParser.js +72 -0
  98. package/lib/Parser/GTXParser.js +75 -0
  99. package/lib/Parser/GeoJsonParser.js +212 -0
  100. package/lib/Parser/GpxParser.js +25 -0
  101. package/lib/Parser/ISGParser.js +71 -0
  102. package/lib/Parser/KMLParser.js +25 -0
  103. package/lib/Parser/LASParser.js +137 -0
  104. package/lib/Parser/MapBoxUrlParser.js +83 -0
  105. package/lib/Parser/PntsParser.js +131 -0
  106. package/lib/Parser/Potree2BinParser.js +92 -0
  107. package/lib/Parser/PotreeBinParser.js +106 -0
  108. package/lib/Parser/PotreeCinParser.js +29 -0
  109. package/lib/Parser/ShapefileParser.js +78 -0
  110. package/lib/Parser/VectorTileParser.js +215 -0
  111. package/lib/Parser/XbilParser.js +120 -0
  112. package/lib/Parser/deprecated/LegacyGLTFLoader.js +1386 -0
  113. package/lib/Parser/iGLTFLoader.js +168 -0
  114. package/lib/Process/3dTilesProcessing.js +304 -0
  115. package/lib/Process/FeatureProcessing.js +76 -0
  116. package/lib/Process/LayeredMaterialNodeProcessing.js +229 -0
  117. package/lib/Process/ObjectRemovalHelper.js +97 -0
  118. package/lib/Process/handlerNodeError.js +23 -0
  119. package/lib/Provider/3dTilesProvider.js +149 -0
  120. package/lib/Provider/DataSourceProvider.js +24 -0
  121. package/lib/Provider/Fetcher.js +233 -0
  122. package/lib/Provider/PointCloudProvider.js +45 -0
  123. package/lib/Provider/TileProvider.js +16 -0
  124. package/lib/Provider/URLBuilder.js +116 -0
  125. package/lib/Renderer/Camera.js +281 -0
  126. package/lib/Renderer/Color.js +56 -0
  127. package/lib/Renderer/ColorLayersOrdering.js +115 -0
  128. package/lib/Renderer/CommonMaterial.js +31 -0
  129. package/lib/Renderer/Label2DRenderer.js +192 -0
  130. package/lib/Renderer/LayeredMaterial.js +243 -0
  131. package/lib/Renderer/OBB.js +150 -0
  132. package/lib/Renderer/OrientedImageCamera.js +118 -0
  133. package/lib/Renderer/OrientedImageMaterial.js +167 -0
  134. package/lib/Renderer/PointsMaterial.js +485 -0
  135. package/lib/Renderer/RasterTile.js +243 -0
  136. package/lib/Renderer/RenderMode.js +31 -0
  137. package/lib/Renderer/Shader/ShaderChunk.js +160 -0
  138. package/lib/Renderer/Shader/ShaderUtils.js +47 -0
  139. package/lib/Renderer/SphereHelper.js +17 -0
  140. package/lib/Renderer/WebXR.js +51 -0
  141. package/lib/Renderer/c3DEngine.js +214 -0
  142. package/lib/Source/C3DTilesGoogleSource.js +74 -0
  143. package/lib/Source/C3DTilesIonSource.js +54 -0
  144. package/lib/Source/C3DTilesSource.js +30 -0
  145. package/lib/Source/CopcSource.js +126 -0
  146. package/lib/Source/EntwinePointTileSource.js +72 -0
  147. package/lib/Source/FileSource.js +188 -0
  148. package/lib/Source/OGC3DTilesGoogleSource.js +29 -0
  149. package/lib/Source/OGC3DTilesIonSource.js +34 -0
  150. package/lib/Source/OGC3DTilesSource.js +21 -0
  151. package/lib/Source/OrientedImageSource.js +59 -0
  152. package/lib/Source/Potree2Source.js +167 -0
  153. package/lib/Source/PotreeSource.js +82 -0
  154. package/lib/Source/Source.js +202 -0
  155. package/lib/Source/TMSSource.js +144 -0
  156. package/lib/Source/VectorTilesSource.js +182 -0
  157. package/lib/Source/WFSSource.js +170 -0
  158. package/lib/Source/WMSSource.js +167 -0
  159. package/lib/Source/WMTSSource.js +92 -0
  160. package/lib/ThreeExtended/capabilities/WebGL.js +69 -0
  161. package/lib/ThreeExtended/libs/ktx-parse.module.js +506 -0
  162. package/lib/ThreeExtended/libs/zstddec.module.js +29 -0
  163. package/lib/ThreeExtended/loaders/DDSLoader.js +200 -0
  164. package/lib/ThreeExtended/loaders/DRACOLoader.js +400 -0
  165. package/lib/ThreeExtended/loaders/GLTFLoader.js +2879 -0
  166. package/lib/ThreeExtended/loaders/KTX2Loader.js +709 -0
  167. package/lib/ThreeExtended/math/ColorSpaces.js +59 -0
  168. package/lib/ThreeExtended/utils/BufferGeometryUtils.js +846 -0
  169. package/lib/ThreeExtended/utils/WorkerPool.js +70 -0
  170. package/lib/Utils/CameraUtils.js +554 -0
  171. package/lib/Utils/DEMUtils.js +350 -0
  172. package/lib/Utils/FeaturesUtils.js +156 -0
  173. package/lib/Utils/Gradients.js +16 -0
  174. package/lib/Utils/ThreeUtils.js +115 -0
  175. package/lib/Utils/gui/C3DTilesStyle.js +218 -0
  176. package/lib/Utils/gui/Main.js +7 -0
  177. package/lib/Utils/gui/Minimap.js +152 -0
  178. package/lib/Utils/gui/Navigation.js +245 -0
  179. package/lib/Utils/gui/Scale.js +104 -0
  180. package/lib/Utils/gui/Searchbar.js +234 -0
  181. package/lib/Utils/gui/Widget.js +80 -0
  182. package/lib/Utils/placeObjectOnGround.js +136 -0
  183. package/lib/Worker/LASLoaderWorker.js +19 -0
  184. package/lib/Worker/Potree2Worker.js +21 -0
  185. package/package.json +2 -2
@@ -0,0 +1,1115 @@
1
+ import * as THREE from 'three';
2
+ import { CRS, Coordinates } from '@itowns/geographic';
3
+ import Camera from "../Renderer/Camera.js";
4
+ import initializeWebXR from "../Renderer/WebXR.js";
5
+ import MainLoop, { MAIN_LOOP_EVENTS, RENDERING_PAUSED } from "./MainLoop.js";
6
+ import Capabilities from "./System/Capabilities.js";
7
+ import { COLOR_LAYERS_ORDER_CHANGED } from "../Renderer/ColorLayersOrdering.js";
8
+ import c3DEngine from "../Renderer/c3DEngine.js";
9
+ import RenderMode from "../Renderer/RenderMode.js";
10
+ import FeaturesUtils from "../Utils/FeaturesUtils.js";
11
+ import { getMaxColorSamplerUnitsCount } from "../Renderer/LayeredMaterial.js";
12
+ import Scheduler from "./Scheduler/Scheduler.js";
13
+ import Picking from "./Picking.js";
14
+ import LabelLayer from "../Layer/LabelLayer.js";
15
+ import ObjectRemovalHelper from "../Process/ObjectRemovalHelper.js";
16
+ export const VIEW_EVENTS = {
17
+ /**
18
+ * Fires when all the layers of the view are considered initialized.
19
+ * Initialized in this context means: all layers are ready to be
20
+ * displayed (no pending network access, no visual improvement to be
21
+ * expected, ...).
22
+ * If you add new layers, the event will be fired again when all
23
+ * layers are ready.
24
+ * @event View#layers-initialized
25
+ * @property type {string} layers-initialized
26
+ */
27
+ LAYERS_INITIALIZED: 'layers-initialized',
28
+ LAYER_REMOVED: 'layer-removed',
29
+ LAYER_ADDED: 'layer-added',
30
+ INITIALIZED: 'initialized',
31
+ COLOR_LAYERS_ORDER_CHANGED,
32
+ CAMERA_MOVED: 'camera-moved',
33
+ DISPOSED: 'disposed'
34
+ };
35
+
36
+ /**
37
+ * Fired on current view's domElement when double right-clicking it. Copies all properties of the second right-click
38
+ * MouseEvent (such as cursor position).
39
+ * @event View#dblclick-right
40
+ * @property {string} type dblclick-right
41
+ */
42
+
43
+ function _preprocessLayer(view, layer, parentLayer) {
44
+ const source = layer.source;
45
+ if (parentLayer && !layer.extent) {
46
+ layer.extent = parentLayer.extent;
47
+ if (source && !source.extent) {
48
+ source.extent = parentLayer.extent;
49
+ }
50
+ }
51
+ if (layer.isGeometryLayer && !layer.isLabelLayer) {
52
+ // Find crs projection layer, this is projection destination
53
+ layer.crs = view.referenceCrs;
54
+ } else if (!layer.crs) {
55
+ if (parentLayer && parentLayer.tileMatrixSets && parentLayer.tileMatrixSets.includes(source.crs)) {
56
+ layer.crs = source.crs;
57
+ } else {
58
+ layer.crs = parentLayer && parentLayer.extent.crs;
59
+ }
60
+ }
61
+ if (layer.isLabelLayer) {
62
+ view.mainLoop.gfxEngine.label2dRenderer.registerLayer(layer);
63
+ } else if (layer.labelEnabled || layer.addLabelLayer) {
64
+ if (layer.labelEnabled) {
65
+ // eslint-disable-next-line no-console
66
+ console.info('layer.labelEnabled is deprecated use addLabelLayer, instead of');
67
+ }
68
+ // Because the features are shared between layer and labelLayer.
69
+ layer.buildExtent = true;
70
+ // label layer needs 3d data structure.
71
+ layer.structure = '3d';
72
+ const labelLayer = new LabelLayer(`${layer.id}-label`, {
73
+ source,
74
+ style: layer.style,
75
+ zoom: layer.zoom,
76
+ performance: layer.addLabelLayer.performance,
77
+ crs: source.crs,
78
+ visible: layer.visible,
79
+ margin: 15,
80
+ forceClampToTerrain: layer.addLabelLayer.forceClampToTerrain
81
+ });
82
+ layer.addEventListener('visible-property-changed', () => {
83
+ labelLayer.visible = layer.visible;
84
+ });
85
+ const removeLabelLayer = e => {
86
+ if (e.layerId === layer.id) {
87
+ view.removeLayer(labelLayer.id);
88
+ }
89
+ view.removeEventListener(VIEW_EVENTS.LAYER_REMOVED, removeLabelLayer);
90
+ };
91
+ view.addEventListener(VIEW_EVENTS.LAYER_REMOVED, removeLabelLayer);
92
+ layer.whenReady = layer.whenReady.then(() => {
93
+ view.addLayer(labelLayer);
94
+ return layer;
95
+ });
96
+ }
97
+ if (layer.isOGC3DTilesLayer) {
98
+ layer._setup(view);
99
+ }
100
+ return layer;
101
+ }
102
+ const _eventCoords = new THREE.Vector2();
103
+ const matrix = new THREE.Matrix4();
104
+ const screen = new THREE.Vector2();
105
+ const ray = new THREE.Ray();
106
+ const direction = new THREE.Vector3();
107
+ const positionVector = new THREE.Vector3();
108
+ const coordinates = new Coordinates('EPSG:4326');
109
+ const viewers = [];
110
+ // Size of the camera frustrum, in meters
111
+ let screenMeters;
112
+ let id = 0;
113
+
114
+ /**
115
+ * @property {number} id - The id of the view. It's incremented at each new view instance, starting at 0.
116
+ * @property {HTMLElement} domElement - The domElement holding the canvas where the view is displayed
117
+ * @property {String} referenceCrs - The coordinate reference system of the view
118
+ * @property {MainLoop} mainLoop - itowns mainloop scheduling the operations
119
+ * @property {THREE.Scene} scene - threejs scene of the view
120
+ * @property {Camera} camera - itowns camera (that holds a threejs camera that is directly accessible with View.camera3D)
121
+ * @property {THREE.Camera} camera3D - threejs camera that is stored in itowns camera
122
+ * @property {THREE.WebGLRenderer} renderer - threejs webglrenderer rendering this view
123
+ */
124
+ class View extends THREE.EventDispatcher {
125
+ #layers = [];
126
+ #pixelDepthBuffer = (() => new Uint8Array(4))();
127
+ #fullSizeDepthBuffer;
128
+ /**
129
+ * Constructs an Itowns View instance
130
+ *
131
+ * @example <caption><b>Create a view with a custom Three.js camera.</b></caption>
132
+ * var viewerDiv = document.getElementById('viewerDiv');
133
+ * var customCamera = itowns.THREE.PerspectiveCamera();
134
+ * var view = itowns.View('EPSG:4326', viewerDiv, { camera: { cameraThree: customCamera } });
135
+ *
136
+ * @example <caption><b>Create a view with an orthographic camera, and grant it with Three.js custom controls.</b></caption>
137
+ * var viewerDiv = document.getElementById('viewerDiv');
138
+ * var view = itowns.View('EPSG:4326', viewerDiv, { camera: { type: itowns.CAMERA_TYPE.ORTHOGRAPHIC } });
139
+ * var customControls = itowns.THREE.OrbitControls(view.camera3D, viewerDiv);
140
+ *
141
+ * @param {String} crs - The default CRS of Three.js coordinates. Should be a cartesian CRS.
142
+ * @param {HTMLElement} viewerDiv - Where to instanciate the Three.js scene in the DOM
143
+ * @param {Object} [options] - Optional properties.
144
+ * @param {Object} [options.camera] - Options for the camera associated to the view. See {@link Camera} options.
145
+ * @param {MainLoop} [options.mainLoop] - {@link MainLoop} instance to use, otherwise a default one will be constructed
146
+ * @param {WebGLRenderer|Object} [options.renderer] - {@link WebGLRenderer} instance to use, otherwise
147
+ * a default one will be constructed. In this case, if options.renderer is an object, it will be used to
148
+ * configure the renderer (see {@link c3DEngine}. If not present, a new &lt;canvas> will be created and
149
+ * added to viewerDiv (mutually exclusive with mainLoop)
150
+ * @param {Object} [options.webXR] - enable webxr button to switch on VR visualization.
151
+ * @param {number} [options.webXR.scale=1.0] - apply webxr scale tranformation.
152
+ * @param {Scene} [options.scene3D] - [THREE.Scene](https://threejs.org/docs/#api/en/scenes/Scene) instance to use, otherwise a default one will be constructed
153
+ * @param {Color} [options.diffuse] - [THREE.Color](https://threejs.org/docs/?q=color#api/en/math/Color) Diffuse color terrain material.
154
+ * This color is applied to terrain if there isn't color layer on terrain extent (by example on pole).
155
+ * @param {boolean} [options.enableFocusOnStart=true] - enable focus on dom element on start.
156
+ */
157
+ constructor(crs, viewerDiv) {
158
+ let options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
159
+ if (!viewerDiv) {
160
+ throw new Error('Invalid viewerDiv parameter (must non be null/undefined)');
161
+ }
162
+ super();
163
+ this.domElement = viewerDiv;
164
+ this.id = id++;
165
+ this.referenceCrs = crs;
166
+ let engine;
167
+ // options.renderer can be 2 separate things:
168
+ // - an actual renderer (in this case we don't use viewerDiv)
169
+ // - options for the renderer to be created
170
+ if (options.renderer && options.renderer.domElement) {
171
+ engine = new c3DEngine(options.renderer);
172
+ } else {
173
+ engine = new c3DEngine(viewerDiv, options.renderer);
174
+ }
175
+ this.mainLoop = options.mainLoop || new MainLoop(new Scheduler(), engine);
176
+ this.scene = options.scene3D || new THREE.Scene();
177
+ if (!options.scene3D) {
178
+ this.scene.matrixWorldAutoUpdate = false;
179
+ }
180
+ this.camera = new Camera(this.referenceCrs, this.mainLoop.gfxEngine.getWindowSize().x, this.mainLoop.gfxEngine.getWindowSize().y, options.camera);
181
+ this._frameRequesters = {};
182
+ this._resizeListener = () => this.resize();
183
+ window.addEventListener('resize', this._resizeListener, false);
184
+ this._changeSources = new Set();
185
+ this._delayedFrameRequesterRemoval = [];
186
+ this._allLayersAreReadyCallback = () => {
187
+ // all layers must be ready
188
+ const allReady = this.getLayers().every(layer => layer.ready);
189
+ if (allReady && this.mainLoop.scheduler.commandsWaitingExecutionCount() == 0 && this.mainLoop.renderingState == RENDERING_PAUSED) {
190
+ this.dispatchEvent({
191
+ type: VIEW_EVENTS.LAYERS_INITIALIZED
192
+ });
193
+ this.removeFrameRequester(MAIN_LOOP_EVENTS.UPDATE_END, this._allLayersAreReadyCallback);
194
+ }
195
+ };
196
+ this.camera.resize(this.domElement.clientWidth, this.domElement.clientHeight);
197
+ const fn = () => {
198
+ this.removeEventListener(VIEW_EVENTS.LAYERS_INITIALIZED, fn);
199
+ this.dispatchEvent({
200
+ type: VIEW_EVENTS.INITIALIZED
201
+ });
202
+ };
203
+ this.addEventListener(VIEW_EVENTS.LAYERS_INITIALIZED, fn);
204
+ this.#fullSizeDepthBuffer = new Uint8Array(4 * this.camera.width * this.camera.height);
205
+
206
+ // Indicates that view's domElement can be focused (the negative value indicates that domElement can't be
207
+ // focused sequentially using tab key). Focus is needed to capture some key events.
208
+ this.domElement.tabIndex = -1;
209
+ // Set focus on view's domElement.
210
+ if (!options.disableFocusOnStart) {
211
+ this.domElement.focus();
212
+ }
213
+
214
+ // Create a custom `dblclick-right` event that is triggered when double right-clicking
215
+ let rightClickTimeStamp;
216
+ this.domElement.addEventListener('mouseup', event => {
217
+ if (event.button === 2) {
218
+ // If pressed mouse button is right button
219
+ // If time between two right-clicks is bellow 500 ms, triggers a `dblclick-right` event
220
+ if (rightClickTimeStamp && event.timeStamp - rightClickTimeStamp < 500) {
221
+ this.domElement.dispatchEvent(new MouseEvent('dblclick-right', event));
222
+ }
223
+ rightClickTimeStamp = event.timeStamp;
224
+ }
225
+ });
226
+
227
+ // push all viewer to keep source.cache
228
+ viewers.push(this);
229
+ if (options.webXR) {
230
+ initializeWebXR(this, options.webXR);
231
+ }
232
+ }
233
+
234
+ /**
235
+ * Get the Threejs renderer used to render this view.
236
+ * @returns {THREE.WebGLRenderer} the WebGLRenderer used to render this view.
237
+ */
238
+ get renderer() {
239
+ return this.mainLoop?.gfxEngine?.getRenderer();
240
+ }
241
+
242
+ /**
243
+ * Get the threejs Camera of this view
244
+ * @returns {THREE.Camera} the threejs camera of this view
245
+ */
246
+ get camera3D() {
247
+ return this.camera?.camera3D;
248
+ }
249
+
250
+ /**
251
+ * Dispose viewer before delete it.
252
+ *
253
+ * Method dispose all viewer objects
254
+ * - remove control
255
+ * - remove all layers
256
+ * - remove all frame requester
257
+ * - remove all events
258
+ * @param {boolean} [clearCache=false] Whether to clear all the caches or not (layers cache, style cache, tilesCache)
259
+ */
260
+ dispose() {
261
+ let clearCache = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
262
+ const id = viewers.indexOf(this);
263
+ if (id == -1) {
264
+ console.warn('View already disposed');
265
+ return;
266
+ }
267
+ window.removeEventListener('resize', this._resizeListener);
268
+
269
+ // controls dispose
270
+ if (this.controls) {
271
+ if (typeof this.controls.dispose === 'function') {
272
+ this.controls.dispose();
273
+ }
274
+ delete this.controls;
275
+ }
276
+ // remove alls frameRequester
277
+ this.removeAllFrameRequesters();
278
+ // remove all layers
279
+ const layers = this.getLayers(l => !l.isTiledGeometryLayer && !l.isAtmosphere);
280
+ for (const layer of layers) {
281
+ this.removeLayer(layer.id, clearCache);
282
+ }
283
+ const atmospheres = this.getLayers(l => l.isAtmosphere);
284
+ for (const atmosphere of atmospheres) {
285
+ this.removeLayer(atmosphere.id, clearCache);
286
+ }
287
+ const tileLayers = this.getLayers(l => l.isTiledGeometryLayer);
288
+ for (const tileLayer of tileLayers) {
289
+ this.removeLayer(tileLayer.id, clearCache);
290
+ }
291
+ viewers.splice(id, 1);
292
+ // Remove remaining objects in the scene (e.g. helpers, debug, etc.)
293
+ this.scene.traverse(ObjectRemovalHelper.cleanup);
294
+ this.dispatchEvent({
295
+ type: VIEW_EVENTS.DISPOSED
296
+ });
297
+ // remove alls events
298
+ this.removeAllEvents();
299
+ }
300
+
301
+ /**
302
+ * Add layer in viewer.
303
+ * The layer id must be unique.
304
+ *
305
+ * The `layer.whenReady` is a promise that resolves when
306
+ * the layer is done. This promise is also returned by
307
+ * `addLayer` allowing to chain call.
308
+ *
309
+ * @param {LayerOptions|Layer|GeometryLayer} layer The layer to add in view.
310
+ * @param {Layer=} parentLayer it's the layer to which the layer will be attached.
311
+ * @return {Promise} a promise resolved with the new layer object when it is fully initialized or rejected if any error occurred.
312
+ */
313
+ addLayer(layer, parentLayer) {
314
+ if (!layer || !layer.isLayer) {
315
+ return Promise.reject(new Error('Add Layer type object'));
316
+ }
317
+ const duplicate = this.getLayerById(layer.id);
318
+ if (duplicate) {
319
+ return layer._reject(new Error(`Invalid id '${layer.id}': id already used`));
320
+ }
321
+ layer = _preprocessLayer(this, layer, parentLayer);
322
+ if (parentLayer) {
323
+ if (layer.isColorLayer) {
324
+ const layerColors = this.getLayers(l => l.isColorLayer);
325
+ layer.sequence = layerColors.length;
326
+ const sumColorLayers = parentLayer.countColorLayersTextures(...layerColors, layer);
327
+ if (sumColorLayers <= getMaxColorSamplerUnitsCount()) {
328
+ parentLayer.attach(layer);
329
+ } else {
330
+ return layer._reject(new Error(`Cant add color layer ${layer.id}: the maximum layer is reached`));
331
+ }
332
+ } else {
333
+ parentLayer.attach(layer);
334
+ }
335
+ } else {
336
+ if (typeof layer.update !== 'function') {
337
+ return layer._reject(new Error('Cant add GeometryLayer: missing a update function'));
338
+ }
339
+ if (typeof layer.preUpdate !== 'function') {
340
+ return layer._reject(new Error('Cant add GeometryLayer: missing a preUpdate function'));
341
+ }
342
+ this.#layers.push(layer);
343
+ }
344
+ if (layer.object3d && !layer.object3d.parent && layer.object3d !== this.scene) {
345
+ this.scene.add(layer.object3d);
346
+ }
347
+ Promise.all(layer._promises).then(() => {
348
+ layer._resolve();
349
+ this.notifyChange(parentLayer || layer, false);
350
+ if (!this._frameRequesters[MAIN_LOOP_EVENTS.UPDATE_END] || !this._frameRequesters[MAIN_LOOP_EVENTS.UPDATE_END].includes(this._allLayersAreReadyCallback)) {
351
+ this.addFrameRequester(MAIN_LOOP_EVENTS.UPDATE_END, this._allLayersAreReadyCallback);
352
+ }
353
+ this.dispatchEvent({
354
+ type: VIEW_EVENTS.LAYER_ADDED,
355
+ layerId: layer.id
356
+ });
357
+ }, layer._reject);
358
+ return layer.whenReady;
359
+ }
360
+
361
+ /**
362
+ * Removes a specific imagery layer from the current layer list. This removes layers inserted with attach().
363
+ * @example
364
+ * view.removeLayer('layerId');
365
+ * @param {string} layerId The identifier
366
+ * @param {boolean} [clearCache=false] Whether to clear all the layer cache or not
367
+ * @return {boolean}
368
+ */
369
+ removeLayer(layerId, clearCache) {
370
+ const layer = this.getLayerById(layerId);
371
+ if (layer) {
372
+ const parentLayer = layer.parent;
373
+
374
+ // Remove and dispose all nodes
375
+ layer.delete(clearCache);
376
+
377
+ // Detach layer if it's attached
378
+ if (parentLayer && !parentLayer.detach(layer)) {
379
+ throw new Error(`Error to detach ${layerId} from ${parentLayer.id}`);
380
+ } else if (parentLayer == undefined) {
381
+ // Remove layer from viewer
382
+ this.#layers.splice(this.#layers.findIndex(l => l.id == layerId), 1);
383
+ }
384
+ if (layer.isColorLayer) {
385
+ // Update color layers sequence
386
+ const imageryLayers = this.getLayers(l => l.isColorLayer);
387
+ for (const color of imageryLayers) {
388
+ if (color.sequence > layer.sequence) {
389
+ color.sequence--;
390
+ }
391
+ }
392
+ }
393
+
394
+ // Remove unused cache in all viewers
395
+
396
+ // count of times the source is used in all viewer
397
+ let sharedSourceCount = 0;
398
+ for (const view of viewers) {
399
+ // add count of times the source is used in other layers
400
+ sharedSourceCount += view.getLayers(l => l.source.uid == layer.source.uid && l.crs == layer.crs).length;
401
+ }
402
+ // if sharedSourceCount equals to 0 so remove unused cache for this CRS
403
+ layer.source.onLayerRemoved({
404
+ unusedCrs: sharedSourceCount == 0 ? layer.crs : undefined
405
+ });
406
+ this.notifyChange(this.camera);
407
+ this.dispatchEvent({
408
+ type: VIEW_EVENTS.LAYER_REMOVED,
409
+ layerId
410
+ });
411
+ return true;
412
+ } else {
413
+ throw new Error(`${layerId} doesn't exist`);
414
+ }
415
+ }
416
+
417
+ /**
418
+ * Notifies the scene it needs to be updated due to changes exterior to the
419
+ * scene itself (e.g. camera movement).
420
+ * non-interactive events (e.g: texture loaded)
421
+ * @param {*} changeSource
422
+ * @param {boolean} needsRedraw - indicates if notified change requires a full scene redraw.
423
+ */
424
+ notifyChange() {
425
+ let changeSource = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : undefined;
426
+ let needsRedraw = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
427
+ if (changeSource) {
428
+ this._changeSources.add(changeSource);
429
+ if (!this.mainLoop.gfxEngine.renderer.xr.isPresenting && (changeSource.isTileMesh || changeSource.isCamera)) {
430
+ this.#fullSizeDepthBuffer.needsUpdate = true;
431
+ }
432
+ }
433
+ this.mainLoop.scheduleViewUpdate(this, needsRedraw);
434
+ }
435
+
436
+ /**
437
+ * Get all layers, with an optionnal filter applied.
438
+ * The filter method will be called with 2 args:
439
+ * - 1st: current layer
440
+ * - 2nd: (optional) the geometry layer to which the current layer is attached
441
+ * @example
442
+ * // get all layers
443
+ * view.getLayers();
444
+ * // get all color layers
445
+ * view.getLayers(layer => layer.isColorLayer);
446
+ * // get all elevation layers
447
+ * view.getLayers(layer => layer.isElevationLayer);
448
+ * // get all geometry layers
449
+ * view.getLayers(layer => layer.isGeometryLayer);
450
+ * // get one layer with id
451
+ * view.getLayers(layer => layer.id === 'itt');
452
+ * @param {function(Layer):boolean} filter
453
+ * @returns {Array<Layer>}
454
+ */
455
+ getLayers(filter) {
456
+ const result = [];
457
+ for (const layer of this.#layers) {
458
+ if (!filter || filter(layer)) {
459
+ result.push(layer);
460
+ }
461
+ if (layer.attachedLayers) {
462
+ for (const attached of layer.attachedLayers) {
463
+ if (!filter || filter(attached, layer)) {
464
+ result.push(attached);
465
+ }
466
+ }
467
+ }
468
+ }
469
+ return result;
470
+ }
471
+
472
+ /**
473
+ * Gets the layer by identifier.
474
+ *
475
+ * @param {String} layerId The layer identifier
476
+ * @return {Layer} The layer by identifier.
477
+ */
478
+
479
+ getLayerById(layerId) {
480
+ return this.getLayers(l => l.id === layerId)[0];
481
+ }
482
+
483
+ /**
484
+ * @name FrameRequester
485
+ * @function
486
+ *
487
+ * @description
488
+ * Method that will be called each time the `MainLoop` updates. This function
489
+ * will be given as parameter the delta (in ms) between this update and the
490
+ * previous one, and whether or not we just started to render again. This update
491
+ * is considered as the "next" update if `view.notifyChange` was called during a
492
+ * precedent update. If `view.notifyChange` has been called by something else
493
+ * (other micro/macrotask, UI events etc...), then this update is considered as
494
+ * being the "first". It can also receive optional arguments, depending on the
495
+ * attach point of this function. Currently only `BEFORE_LAYER_UPDATE /
496
+ * AFTER_LAYER_UPDATE` attach points provide an additional argument: the layer
497
+ * being updated.
498
+ * <br><br>
499
+ *
500
+ * This means that if a `frameRequester` function wants to animate something, it
501
+ * should keep on calling `view.notifyChange` until its task is done.
502
+ * <br><br>
503
+ *
504
+ * Implementors of `frameRequester` should keep in mind that this function will
505
+ * be potentially called at each frame, thus care should be given about
506
+ * performance.
507
+ * <br><br>
508
+ *
509
+ * Typical frameRequesters are controls, module wanting to animate moves or UI
510
+ * elements etc... Basically anything that would want to call
511
+ * requestAnimationFrame.
512
+ *
513
+ * @param {number} dt
514
+ * @param {boolean} updateLoopRestarted
515
+ * @param {...*} args
516
+ */
517
+ /**
518
+ * Add a frame requester to this view.
519
+ *
520
+ * FrameRequesters can activate the MainLoop update by calling view.notifyChange.
521
+ *
522
+ * @param {String} when - decide when the frameRequester should be called during
523
+ * the update cycle. Can be any of {@link MAIN_LOOP_EVENTS}.
524
+ * @param {FrameRequester} frameRequester - this function will be called at each
525
+ * MainLoop update with the time delta between last update, or 0 if the MainLoop
526
+ * has just been relaunched.
527
+ */
528
+ addFrameRequester(when, frameRequester) {
529
+ if (typeof frameRequester !== 'function') {
530
+ throw new Error('frameRequester must be a function');
531
+ }
532
+ if (!this._frameRequesters[when]) {
533
+ this._frameRequesters[when] = [frameRequester];
534
+ } else {
535
+ this._frameRequesters[when].push(frameRequester);
536
+ }
537
+ }
538
+
539
+ /**
540
+ * Remove a frameRequester.
541
+ * The effective removal will happen either later; at worst it'll be at
542
+ * the beginning of the next frame.
543
+ *
544
+ * @param {String} when - attach point of this requester. Can be any of
545
+ * {@link MAIN_LOOP_EVENTS}.
546
+ * @param {FrameRequester} frameRequester
547
+ */
548
+ removeFrameRequester(when, frameRequester) {
549
+ if (this._frameRequesters[when].includes(frameRequester)) {
550
+ this._delayedFrameRequesterRemoval.push({
551
+ when,
552
+ frameRequester
553
+ });
554
+ } else {
555
+ console.error('Invalid call to removeFrameRequester: frameRequester isn\'t registered');
556
+ }
557
+ }
558
+
559
+ /**
560
+ * Removes all frame requesters.
561
+ */
562
+ removeAllFrameRequesters() {
563
+ for (const when in this._frameRequesters) {
564
+ if (Object.prototype.hasOwnProperty.call(this._frameRequesters, when)) {
565
+ const frameRequesters = this._frameRequesters[when];
566
+ for (const frameRequester of frameRequesters) {
567
+ this.removeFrameRequester(when, frameRequester);
568
+ }
569
+ }
570
+ }
571
+ this._executeFrameRequestersRemovals();
572
+ }
573
+
574
+ /**
575
+ * Removes all viewer events.
576
+ */
577
+ removeAllEvents() {
578
+ if (this._listeners === undefined) {
579
+ return;
580
+ }
581
+ for (const type in this._listeners) {
582
+ if (Object.prototype.hasOwnProperty.call(this._listeners, type)) {
583
+ delete this._listeners[type];
584
+ }
585
+ }
586
+ this._listeners = undefined;
587
+ }
588
+ _executeFrameRequestersRemovals() {
589
+ for (const toDelete of this._delayedFrameRequesterRemoval) {
590
+ const index = this._frameRequesters[toDelete.when].indexOf(toDelete.frameRequester);
591
+ if (index >= 0) {
592
+ this._frameRequesters[toDelete.when].splice(index, 1);
593
+ } else {
594
+ console.warn('FrameReq has already been removed');
595
+ }
596
+ }
597
+ this._delayedFrameRequesterRemoval.length = 0;
598
+ }
599
+
600
+ /**
601
+ * Execute a frameRequester.
602
+ *
603
+ * @param {String} when - attach point of this (these) requester(s). Can be any
604
+ * of {@link MAIN_LOOP_EVENTS}.
605
+ * @param {Number} dt - delta between this update and the previous one
606
+ * @param {boolean} updateLoopRestarted
607
+ * @param {...*} args - optional arguments
608
+ */
609
+ execFrameRequesters(when, dt, updateLoopRestarted) {
610
+ if (!this._frameRequesters[when]) {
611
+ return;
612
+ }
613
+ if (this._delayedFrameRequesterRemoval.length > 0) {
614
+ this._executeFrameRequestersRemovals();
615
+ }
616
+ for (var _len = arguments.length, args = new Array(_len > 3 ? _len - 3 : 0), _key = 3; _key < _len; _key++) {
617
+ args[_key - 3] = arguments[_key];
618
+ }
619
+ for (const frameRequester of this._frameRequesters[when]) {
620
+ if (frameRequester.update) {
621
+ frameRequester.update(dt, updateLoopRestarted, args);
622
+ } else {
623
+ frameRequester(dt, updateLoopRestarted, args);
624
+ }
625
+ }
626
+ }
627
+
628
+ /**
629
+ * Extract view coordinates from a mouse-event / touch-event
630
+ * @param {event} event - event can be a MouseEvent or a TouchEvent
631
+ * @param {THREE.Vector2} target - the target to set the view coords in
632
+ * @param {number} [touchIdx=0] - finger index when using a TouchEvent
633
+ * @return {THREE.Vector2|undefined} - view coordinates (in pixels, 0-0 = top-left of the View).
634
+ * If the event is neither a `MouseEvent` nor a `TouchEvent`, the return is `undefined`.
635
+ */
636
+ eventToViewCoords(event) {
637
+ let target = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : _eventCoords;
638
+ let touchIdx = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0;
639
+ const br = this.domElement.getBoundingClientRect();
640
+ if (event.touches && event.touches.length) {
641
+ return target.set(event.touches[touchIdx].clientX - br.x, event.touches[touchIdx].clientY - br.y);
642
+ } else if (event.offsetX !== undefined && event.offsetY !== undefined) {
643
+ const targetBoundingRect = event.target.getBoundingClientRect();
644
+ return target.set(targetBoundingRect.x + event.offsetX - br.x, targetBoundingRect.y + event.offsetY - br.y);
645
+ }
646
+ }
647
+
648
+ /**
649
+ * Extract normalized coordinates (NDC) from a mouse-event / touch-event
650
+ * @param {event} event - event can be a MouseEvent or a TouchEvent
651
+ * @param {number} touchIdx - finger index when using a TouchEvent (default: 0)
652
+ * @return {THREE.Vector2} - NDC coordinates (x and y are [-1, 1])
653
+ */
654
+ eventToNormalizedCoords(event) {
655
+ let touchIdx = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
656
+ return this.viewToNormalizedCoords(this.eventToViewCoords(event, _eventCoords, touchIdx));
657
+ }
658
+
659
+ /**
660
+ * Convert view coordinates to normalized coordinates (NDC)
661
+ * @param {THREE.Vector2} viewCoords (in pixels, 0-0 = top-left of the View)
662
+ * @param {THREE.Vector2} target
663
+ * @return {THREE.Vector2} - NDC coordinates (x and y are [-1, 1])
664
+ */
665
+ viewToNormalizedCoords(viewCoords) {
666
+ let target = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : _eventCoords;
667
+ target.x = 2 * (viewCoords.x / this.camera.width) - 1;
668
+ target.y = -2 * (viewCoords.y / this.camera.height) + 1;
669
+ return target;
670
+ }
671
+
672
+ /**
673
+ * Convert NDC coordinates to view coordinates
674
+ * @param {THREE.Vector2} ndcCoords
675
+ * @return {THREE.Vector2} - view coordinates (in pixels, 0-0 = top-left of the View)
676
+ */
677
+ normalizedToViewCoords(ndcCoords) {
678
+ _eventCoords.x = (ndcCoords.x + 1) * 0.5 * this.camera.width;
679
+ _eventCoords.y = (ndcCoords.y - 1) * -0.5 * this.camera.height;
680
+ return _eventCoords;
681
+ }
682
+
683
+ /**
684
+ * Searches for objects in {@link GeometryLayer} and specified
685
+ * `THREE.Object3D`, under the mouse or at a specified coordinates, in this
686
+ * view.
687
+ *
688
+ * @param {Object} mouseOrEvt - Mouse position in window coordinates (from
689
+ * the top left corner of the window) or `MouseEvent` or `TouchEvent`.
690
+ * @param {number} [radius=0] - The picking will happen in a circle centered
691
+ * on mouseOrEvt. This is the radius of this circle, in pixels.
692
+ * @param {GeometryLayer|string|Object3D|Array<GeometryLayer|string|Object3D>} [where] - Where to look for
693
+ * objects. It can be a single {@link GeometryLayer}, `THREE.Object3D`, ID of a layer or an array of one of these or
694
+ * of a mix of these. If no location is specified, it will query on all {@link GeometryLayer} present in this `View`.
695
+ *
696
+ * @return {Object[]} - An array of objects. Each element contains at least
697
+ * an object property which is the `THREE.Object3D` under the cursor. Then
698
+ * depending on the queried layer/source, there may be additionnal
699
+ * properties (coming from `THREE.Raycaster` for instance).
700
+ *
701
+ * @example
702
+ * view.pickObjectsAt({ x, y })
703
+ * view.pickObjectsAt({ x, y }, 1, 'wfsBuilding')
704
+ * view.pickObjectsAt({ x, y }, 3, 'wfsBuilding', myLayer)
705
+ */
706
+ pickObjectsAt(mouseOrEvt) {
707
+ let radius = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
708
+ let where = arguments.length > 2 ? arguments[2] : undefined;
709
+ const sources = [];
710
+ if (!where || where.length === 0) {
711
+ where = this.getLayers(l => l.isGeometryLayer);
712
+ }
713
+ if (!Array.isArray(where)) {
714
+ where = [where];
715
+ }
716
+ where.forEach(l => {
717
+ if (typeof l === 'string') {
718
+ l = this.getLayerById(l);
719
+ }
720
+ if (l && (l.isGeometryLayer || l.isObject3D)) {
721
+ sources.push(l);
722
+ }
723
+ });
724
+ if (sources.length == 0) {
725
+ return [];
726
+ }
727
+ const results = [];
728
+ const mouse = mouseOrEvt instanceof Event ? this.eventToViewCoords(mouseOrEvt) : mouseOrEvt;
729
+ for (const source of sources) {
730
+ if (source.isAtmosphere) {
731
+ continue;
732
+ }
733
+ if (source.isGeometryLayer) {
734
+ if (!source.ready) {
735
+ console.warn('view.pickObjectAt : layer is not ready : ', source);
736
+ continue;
737
+ }
738
+ source.pickObjectsAt(this, mouse, radius, results);
739
+ } else {
740
+ Picking.pickObjectsAt(this, mouse, radius, source, results);
741
+ }
742
+ }
743
+ return results;
744
+ }
745
+
746
+ /**
747
+ * Return the current zoom scale at the central point of the view. This
748
+ * function compute the scale of a map.
749
+ *
750
+ * @param {number} pitch - Screen pitch, in millimeters ; 0.28 by default
751
+ *
752
+ * @return {number} The zoom scale.
753
+ */
754
+ getScale() {
755
+ let pitch = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0.28;
756
+ if (this.camera3D.isOrthographicCamera) {
757
+ return pitch * 1E-3 / this.getPixelsToMeters();
758
+ }
759
+ return this.getScaleFromDistance(pitch, this.getDistanceFromCamera());
760
+ }
761
+ getScaleFromDistance() {
762
+ let pitch = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0.28;
763
+ let distance = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 1;
764
+ pitch /= 1000;
765
+ const fov = THREE.MathUtils.degToRad(this.camera3D.fov);
766
+ const unit = this.camera.height / (2 * distance * Math.tan(fov * 0.5));
767
+ return pitch * unit;
768
+ }
769
+
770
+ /**
771
+ * Given a screen coordinates, get the distance between the projected
772
+ * coordinates and the camera associated to this view.
773
+ *
774
+ * @param {THREE.Vector2} [screenCoord] - The screen coordinate to get the
775
+ * distance at. By default this is the middle of the screen.
776
+ *
777
+ * @return {number} The distance in meters.
778
+ */
779
+ getDistanceFromCamera(screenCoord) {
780
+ this.getPickingPositionFromDepth(screenCoord, positionVector);
781
+ return this.camera3D.position.distanceTo(positionVector);
782
+ }
783
+
784
+ /**
785
+ * Get, for a specific screen coordinate, the projected distance on the
786
+ * surface of the main layer of the view.
787
+ *
788
+ * @param {number} [pixels=1] - The size, in pixels, to get in meters.
789
+ * @param {THREE.Vector2} [screenCoord] - The screen coordinate to get the
790
+ * projected distance at. By default, this is the middle of the screen.
791
+ *
792
+ * @return {number} The projected distance in meters.
793
+ */
794
+ getPixelsToMeters() {
795
+ let pixels = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 1;
796
+ let screenCoord = arguments.length > 1 ? arguments[1] : undefined;
797
+ if (this.camera3D.isOrthographicCamera) {
798
+ screenMeters = (this.camera3D.right - this.camera3D.left) / this.camera3D.zoom;
799
+ return pixels * screenMeters / this.camera.width;
800
+ }
801
+ return this.getPixelsToMetersFromDistance(pixels, this.getDistanceFromCamera(screenCoord));
802
+ }
803
+ getPixelsToMetersFromDistance() {
804
+ let pixels = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 1;
805
+ let distance = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 1;
806
+ return pixels * distance / this.camera._preSSE;
807
+ }
808
+
809
+ /**
810
+ * Get, for a specific screen coordinate, the size in pixels of a projected
811
+ * distance on the surface of the main layer of the view.
812
+ *
813
+ * @param {number} [meters=1] - The size, in meters, to get in pixels.
814
+ * @param {THREE.Vector2} [screenCoord] - The screen coordinate to get the
815
+ * projected distance at. By default, this is the middle of the screen.
816
+ *
817
+ * @return {number} The projected distance in pixels.
818
+ */
819
+ getMetersToPixels() {
820
+ let meters = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 1;
821
+ let screenCoord = arguments.length > 1 ? arguments[1] : undefined;
822
+ if (this.camera3D.isOrthographicCamera) {
823
+ screenMeters = (this.camera3D.right - this.camera3D.left) / this.camera3D.zoom;
824
+ return meters * this.camera.width / screenMeters;
825
+ }
826
+ return this.getMetersToPixelsFromDistance(meters, this.getDistanceFromCamera(screenCoord));
827
+ }
828
+ getMetersToPixelsFromDistance() {
829
+ let meters = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 1;
830
+ let distance = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 1;
831
+ return this.camera._preSSE * meters / distance;
832
+ }
833
+
834
+ /**
835
+ * Searches for {@link FeatureGeometry} in {@link ColorLayer}, under the mouse or at
836
+ * the specified coordinates, in this view. Combining them per layer and in a Feature
837
+ * like format.
838
+ *
839
+ * @param {Object} mouseOrEvt - Mouse position in window coordinates (from
840
+ * the top left corner of the window) or `MouseEvent` or `TouchEvent`.
841
+ * @param {number} [radius=3] - The picking will happen in a circle centered
842
+ * on mouseOrEvt. This is the radius of this circle, in pixels.
843
+ * @param {...ColorLayer|GeometryLayer|string} [where] - The layers to look
844
+ * into. If not specified, all {@link ColorLayer} and {@link GeometryLayer}
845
+ * layers of this view will be looked in.
846
+ *
847
+ * @return {Object} - An object, having one property per layer.
848
+ * For example, looking for features on layers `wfsBuilding` and `wfsRoads`
849
+ * will give an object like `{ wfsBuilding: [...], wfsRoads: [] }`.
850
+ * Each property is made of an array, that can be empty or filled with
851
+ * Feature like objects composed of:
852
+ * - the FeatureGeometry
853
+ * - the feature type
854
+ * - the style
855
+ * - the coordinate if the FeatureGeometry is a point
856
+ *
857
+ * @example
858
+ * view.pickFeaturesAt({ x, y });
859
+ * view.pickFeaturesAt({ x, y }, 1, 'wfsBuilding');
860
+ * view.pickFeaturesAt({ x, y }, 3, 'wfsBuilding', myLayer);
861
+ */
862
+ pickFeaturesAt(mouseOrEvt) {
863
+ let radius = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 3;
864
+ for (var _len2 = arguments.length, where = new Array(_len2 > 2 ? _len2 - 2 : 0), _key2 = 2; _key2 < _len2; _key2++) {
865
+ where[_key2 - 2] = arguments[_key2];
866
+ }
867
+ if (Array.isArray(where[0])) {
868
+ console.warn('Deprecated: the ...where argument of View#pickFeaturesAt should not be an array anymore, but a list: use the spread operator if needed.');
869
+ where = where[0];
870
+ }
871
+ const layers = [];
872
+ const result = {};
873
+ where = where.length == 0 ? this.getLayers(l => l.isColorLayer || l.isGeometryLayer) : where;
874
+ where.forEach(l => {
875
+ if (typeof l === 'string') {
876
+ l = this.getLayerById(l);
877
+ }
878
+ if (l && l.isLayer) {
879
+ result[l.id] = [];
880
+ if (l.isColorLayer) {
881
+ layers.push(l.id);
882
+ }
883
+ }
884
+ });
885
+
886
+ // Get the mouse coordinates to the correct system
887
+ const mouse = mouseOrEvt instanceof Event ? this.eventToViewCoords(mouseOrEvt, _eventCoords) : mouseOrEvt;
888
+ const objects = this.pickObjectsAt(mouse, radius, ...where);
889
+ if (objects.length > 0) {
890
+ objects.forEach(o => result[o.layer.id].push(o));
891
+ }
892
+ if (layers.length == 0) {
893
+ return result;
894
+ }
895
+ this.getPickingPositionFromDepth(mouse, positionVector);
896
+ coordinates.crs = this.referenceCrs;
897
+ coordinates.setFromVector3(positionVector);
898
+
899
+ // Get the correct precision; the position variable will be set in this
900
+ // function.
901
+ let precision;
902
+ const precisions = {
903
+ M: this.getPixelsToMeters(radius, mouse),
904
+ D: 0.001 * radius
905
+ };
906
+ if (this.isPlanarView) {
907
+ precisions.D = precisions.M;
908
+ } else if (this.getPixelsToDegrees) {
909
+ precisions.D = this.getMetersToDegrees(precisions.M);
910
+ }
911
+
912
+ // Get the tile corresponding to where the cursor is
913
+ const tiles = Picking.pickTilesAt(this, mouse, radius, this.tileLayer);
914
+ for (const tile of tiles) {
915
+ if (!tile.object.material) {
916
+ continue;
917
+ }
918
+ for (const materialLayer of tile.object.material.getLayers(layers)) {
919
+ for (const texture of materialLayer.textures) {
920
+ if (!texture.features) {
921
+ continue;
922
+ }
923
+ precision = CRS.isMetricUnit(texture.features.crs) ? precisions.M : precisions.D;
924
+ const featuresUnderCoor = FeaturesUtils.filterFeaturesUnderCoordinate(coordinates, texture.features, precision);
925
+ featuresUnderCoor.forEach(feature => {
926
+ if (!result[materialLayer.id].find(f => f.geometry === feature.geometry)) {
927
+ result[materialLayer.id].push(feature);
928
+ }
929
+ });
930
+ }
931
+ }
932
+ }
933
+ return result;
934
+ }
935
+ readDepthBuffer(x, y, width, height, buffer) {
936
+ const g = this.mainLoop.gfxEngine;
937
+ const currentWireframe = this.tileLayer.wireframe;
938
+ const currentOpacity = this.tileLayer.opacity;
939
+ const currentVisibility = this.tileLayer.visible;
940
+ if (currentWireframe) {
941
+ this.tileLayer.wireframe = false;
942
+ }
943
+ if (currentOpacity < 1.0) {
944
+ this.tileLayer.opacity = 1.0;
945
+ }
946
+ if (!currentVisibility) {
947
+ this.tileLayer.visible = true;
948
+ }
949
+ const restore = this.tileLayer.level0Nodes.map(n => RenderMode.push(n, RenderMode.MODES.DEPTH));
950
+ buffer = g.renderViewToBuffer({
951
+ camera: this.camera,
952
+ scene: this.tileLayer.object3d
953
+ }, {
954
+ x,
955
+ y,
956
+ width,
957
+ height,
958
+ buffer
959
+ });
960
+ restore.forEach(r => r());
961
+ if (this.tileLayer.wireframe !== currentWireframe) {
962
+ this.tileLayer.wireframe = currentWireframe;
963
+ }
964
+ if (this.tileLayer.opacity !== currentOpacity) {
965
+ this.tileLayer.opacity = currentOpacity;
966
+ }
967
+ if (this.tileLayer.visible !== currentVisibility) {
968
+ this.tileLayer.visible = currentVisibility;
969
+ }
970
+ return buffer;
971
+ }
972
+
973
+ /**
974
+ * Returns the world position on the terrain (view's crs: referenceCrs) under view coordinates.
975
+ * This position is computed with depth buffer.
976
+ *
977
+ * @param {THREE.Vector2} mouse position in view coordinates (in pixel), if it's null so it's view's center.
978
+ * @param {THREE.Vector3} [target=THREE.Vector3()] target. the result will be copied into this Vector3. If not present a new one will be created.
979
+ * @return {THREE.Vector3} the world position on the terrain in view's crs: referenceCrs.
980
+ */
981
+
982
+ getPickingPositionFromDepth(mouse) {
983
+ let target = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : new THREE.Vector3();
984
+ if (!this.tileLayer || this.tileLayer.level0Nodes.length == 0 || !this.tileLayer.level0Nodes[0]) {
985
+ target = undefined;
986
+ return;
987
+ }
988
+ const l = this.mainLoop;
989
+ const viewPaused = l.scheduler.commandsWaitingExecutionCount() == 0 && l.renderingState == RENDERING_PAUSED;
990
+ const g = l.gfxEngine;
991
+ const dim = g.getWindowSize();
992
+ mouse = mouse || dim.clone().multiplyScalar(0.5);
993
+ mouse.x = Math.floor(mouse.x);
994
+ mouse.y = Math.floor(mouse.y);
995
+
996
+ // Render/Read to buffer
997
+ let buffer;
998
+ if (viewPaused) {
999
+ if (this.#fullSizeDepthBuffer.needsUpdate) {
1000
+ this.readDepthBuffer(0, 0, dim.x, dim.y, this.#fullSizeDepthBuffer);
1001
+ this.#fullSizeDepthBuffer.needsUpdate = false;
1002
+ }
1003
+ const id = ((dim.y - mouse.y - 1) * dim.x + mouse.x) * 4;
1004
+ buffer = this.#fullSizeDepthBuffer.slice(id, id + 4);
1005
+ } else {
1006
+ buffer = this.readDepthBuffer(mouse.x, mouse.y, 1, 1, this.#pixelDepthBuffer);
1007
+ }
1008
+ screen.x = mouse.x / dim.x * 2 - 1;
1009
+ screen.y = -(mouse.y / dim.y) * 2 + 1;
1010
+ if (Capabilities.isLogDepthBufferSupported() && this.camera3D.type == 'PerspectiveCamera') {
1011
+ // TODO: solve this part with gl_FragCoord_Z and unproject
1012
+ // Origin
1013
+ ray.origin.copy(this.camera3D.position);
1014
+
1015
+ // Direction
1016
+ ray.direction.set(screen.x, screen.y, 0.5);
1017
+ // Unproject
1018
+ matrix.multiplyMatrices(this.camera3D.matrixWorld, matrix.copy(this.camera3D.projectionMatrix).invert());
1019
+ ray.direction.applyMatrix4(matrix);
1020
+ ray.direction.sub(ray.origin);
1021
+ direction.set(0, 0, 1.0);
1022
+ direction.applyMatrix4(matrix);
1023
+ direction.sub(ray.origin);
1024
+ const angle = direction.angleTo(ray.direction);
1025
+ const orthoZ = g.depthBufferRGBAValueToOrthoZ(buffer, this.camera3D);
1026
+ const length = orthoZ / Math.cos(angle);
1027
+ target.addVectors(this.camera3D.position, ray.direction.setLength(length));
1028
+ } else {
1029
+ const gl_FragCoord_Z = g.depthBufferRGBAValueToOrthoZ(buffer, this.camera3D);
1030
+ target.set(screen.x, screen.y, gl_FragCoord_Z);
1031
+ target.unproject(this.camera3D);
1032
+ }
1033
+ if (target.length() > 10000000) {
1034
+ return undefined;
1035
+ }
1036
+ return target;
1037
+ }
1038
+
1039
+ /**
1040
+ * Returns the world {@link Coordinates} of the terrain at given view coordinates.
1041
+ *
1042
+ * @param {THREE.Vector2|event} [mouse] The view coordinates at which the world coordinates must be returned. This
1043
+ * parameter can also be set to a mouse event from which the view coordinates will be deducted. If not specified,
1044
+ * it will be defaulted to the view's center coordinates.
1045
+ * @param {Coordinates} [target] The result will be copied into this {@link Coordinates} in the coordinate reference
1046
+ * system of the given coordinate. If not specified, a new {@link Coordinates} instance will be created (in the
1047
+ * view referenceCrs).
1048
+ *
1049
+ * @returns {Coordinates} The world {@link Coordinates} of the terrain at the given view coordinates in the
1050
+ * coordinate reference system of the target or in the view referenceCrs if no target is specified.
1051
+ */
1052
+ pickTerrainCoordinates(mouse) {
1053
+ let target = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : new Coordinates(this.referenceCrs);
1054
+ if (mouse instanceof Event) {
1055
+ this.eventToViewCoords(mouse);
1056
+ } else if (mouse && mouse.x !== undefined && mouse.y !== undefined) {
1057
+ _eventCoords.copy(mouse);
1058
+ } else {
1059
+ _eventCoords.set(this.mainLoop.gfxEngine.width / 2, this.mainLoop.gfxEngine.height / 2);
1060
+ }
1061
+ this.getPickingPositionFromDepth(_eventCoords, positionVector);
1062
+ coordinates.crs = this.referenceCrs;
1063
+ coordinates.setFromVector3(positionVector);
1064
+ coordinates.as(target.crs, target);
1065
+ return target;
1066
+ }
1067
+
1068
+ /**
1069
+ * Returns the world {@link Coordinates} of the terrain at given view coordinates.
1070
+ *
1071
+ * @param {THREE.Vector2|event} [mouse] The view coordinates at which the world coordinates must be
1072
+ * returned. This parameter can also be set to a mouse event from
1073
+ * which the view coordinates will be deducted. If not specified, it
1074
+ * will be defaulted to the view's center coordinates.
1075
+ * @param {Coordinates} [target] The result will be copied into this {@link Coordinates}. If not
1076
+ * specified, a new {@link Coordinates} instance will be created.
1077
+ *
1078
+ * @returns {Coordinates} The world {@link Coordinates} of the terrain at the given view coordinates.
1079
+ *
1080
+ * @deprecated Use View#pickTerrainCoordinates instead.
1081
+ */
1082
+ pickCoordinates(mouse) {
1083
+ let target = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : new Coordinates(this.referenceCrs);
1084
+ console.warn('Deprecated, use View#pickTerrainCoordinates instead.');
1085
+ return this.pickTerrainCoordinates(mouse, target);
1086
+ }
1087
+
1088
+ /**
1089
+ * Resize the viewer.
1090
+ *
1091
+ * @param {number} [width=viewerDiv.clientWidth] - The width to resize the
1092
+ * viewer with. By default it is the `clientWidth` of the `viewerDiv`.
1093
+ * @param {number} [height=viewerDiv.clientHeight] - The height to resize
1094
+ * the viewer with. By default it is the `clientHeight` of the `viewerDiv`.
1095
+ */
1096
+ resize(width, height) {
1097
+ if (width < 0 || height < 0) {
1098
+ console.warn(`Trying to resize the View with negative height (${height}) or width (${width}). Skipping resize.`);
1099
+ return;
1100
+ }
1101
+ if (width == undefined) {
1102
+ width = this.domElement.clientWidth;
1103
+ }
1104
+ if (height == undefined) {
1105
+ height = this.domElement.clientHeight;
1106
+ }
1107
+ this.#fullSizeDepthBuffer = new Uint8Array(4 * width * height);
1108
+ this.mainLoop.gfxEngine.onWindowResize(width, height);
1109
+ if (width !== 0 && height !== 0) {
1110
+ this.camera.resize(width, height);
1111
+ this.notifyChange(this.camera3D);
1112
+ }
1113
+ }
1114
+ }
1115
+ export default View;