itowns 2.43.2-next.8 → 2.44.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/CONTRIBUTORS.md +1 -0
  2. package/changelog.md +107 -0
  3. package/dist/455.js +2 -0
  4. package/dist/455.js.map +1 -0
  5. package/dist/debug.js +1 -1
  6. package/dist/debug.js.LICENSE.txt +2 -2
  7. package/dist/debug.js.map +1 -1
  8. package/dist/itowns.js +1 -1
  9. package/dist/itowns.js.LICENSE.txt +1 -22
  10. package/dist/itowns.js.map +1 -1
  11. package/dist/itowns_lasparser.js +2 -0
  12. package/dist/itowns_lasparser.js.map +1 -0
  13. package/dist/itowns_lasworker.js +2 -0
  14. package/dist/itowns_lasworker.js.map +1 -0
  15. package/dist/itowns_potree2worker.js +2 -0
  16. package/dist/itowns_potree2worker.js.map +1 -0
  17. package/dist/itowns_widgets.js +1 -1
  18. package/dist/itowns_widgets.js.map +1 -1
  19. package/examples/3dtiles_loader.html +150 -0
  20. package/examples/config.json +5 -0
  21. package/examples/jsm/.eslintrc.cjs +38 -0
  22. package/examples/jsm/OGC3DTilesHelper.js +105 -0
  23. package/examples/misc_instancing.html +2 -1
  24. package/examples/potree2_25d_map.html +127 -0
  25. package/lib/Core/3DTiles/C3DTFeature.js +6 -6
  26. package/lib/Core/Potree2Node.js +206 -0
  27. package/lib/Core/Potree2PointAttributes.js +139 -0
  28. package/lib/Core/Scheduler/Scheduler.js +1 -1
  29. package/lib/Core/Style.js +52 -49
  30. package/lib/Core/View.js +3 -0
  31. package/lib/Layer/C3DTilesLayer.js +6 -6
  32. package/lib/Layer/OGC3DTilesLayer.js +386 -0
  33. package/lib/Layer/PointCloudLayer.js +1 -1
  34. package/lib/Layer/Potree2Layer.js +165 -0
  35. package/lib/Layer/ReferencingLayerProperties.js +5 -0
  36. package/lib/Layer/TiledGeometryLayer.js +4 -1
  37. package/lib/Loader/Potree2BrotliLoader.js +261 -0
  38. package/lib/Loader/Potree2Loader.js +207 -0
  39. package/lib/Main.js +9 -2
  40. package/lib/Parser/B3dmParser.js +11 -22
  41. package/lib/Parser/LASParser.js +48 -16
  42. package/lib/Parser/Potree2BinParser.js +92 -0
  43. package/lib/Parser/ShapefileParser.js +2 -2
  44. package/lib/Parser/deprecated/LegacyGLTFLoader.js +25 -5
  45. package/lib/Parser/iGLTFLoader.js +169 -0
  46. package/lib/Process/3dTilesProcessing.js +2 -1
  47. package/lib/Provider/3dTilesProvider.js +6 -4
  48. package/lib/Renderer/PointsMaterial.js +128 -94
  49. package/lib/Source/FileSource.js +1 -1
  50. package/lib/Source/OGC3DTilesGoogleSource.js +32 -0
  51. package/lib/Source/OGC3DTilesIonSource.js +37 -0
  52. package/lib/Source/OGC3DTilesSource.js +24 -0
  53. package/lib/Source/Potree2Source.js +172 -0
  54. package/lib/ThreeExtended/loaders/DRACOLoader.js +5 -3
  55. package/lib/ThreeExtended/loaders/GLTFLoader.js +38 -2
  56. package/lib/ThreeExtended/loaders/KTX2Loader.js +17 -10
  57. package/lib/ThreeExtended/utils/BufferGeometryUtils.js +32 -30
  58. package/lib/Worker/LASLoaderWorker.js +19 -0
  59. package/lib/Worker/Potree2Worker.js +21 -0
  60. package/package.json +33 -31
  61. package/lib/Parser/GLTFParser.js +0 -88
  62. /package/lib/{Parser → Loader}/LASLoader.js +0 -0
package/lib/Core/Style.js CHANGED
@@ -15,29 +15,30 @@ function baseAltitudeDefault(properties, ctx) {
15
15
  return ctx?.coordinates?.z || 0;
16
16
  }
17
17
  export function readExpression(property, ctx) {
18
- if (property != undefined) {
19
- if (property.expression) {
20
- return property.expression.evaluate(ctx);
21
- } else if (property.stops) {
22
- for (let i = property.stops.length - 1; i >= 0; i--) {
23
- const stop = property.stops[i];
24
- if (ctx.zoom >= stop[0]) {
25
- return stop[1];
26
- }
18
+ if (property.expression) {
19
+ return property.expression.evaluate(ctx);
20
+ }
21
+ if (property.stops) {
22
+ const stops = property.stops;
23
+ property = property.stops[0][1];
24
+ for (let i = stops.length - 1; i >= 0; i--) {
25
+ const stop = stops[i];
26
+ if (ctx.zoom >= stop[0]) {
27
+ property = stop[1];
28
+ break;
27
29
  }
28
- return property.stops[0][1];
29
- }
30
- if (typeof property === 'string' || property instanceof String) {
31
- property = property.replace(/\{(.+?)\}/g, (a, b) => ctx.properties[b] || '').trim();
32
30
  }
33
- if (property instanceof Function) {
34
- // TOBREAK: Pass the current `context` as a unique parameter.
35
- // In this proposal, metadata will be accessed in the callee by the
36
- // `context.properties` property.
37
- return property(ctx.properties, ctx);
38
- }
39
- return property;
40
31
  }
32
+ if (typeof property === 'string' || property instanceof String) {
33
+ return property.replace(/\{(.+?)\}/g, (a, b) => ctx.properties[b] || '').trim();
34
+ }
35
+ if (property instanceof Function) {
36
+ // TOBREAK: Pass the current `context` as a unique parameter.
37
+ // In this proposal, metadata will be accessed in the callee by the
38
+ // `context.properties` property.
39
+ return property(ctx.properties, ctx);
40
+ }
41
+ return property;
41
42
  }
42
43
  function rgba2rgb(orig) {
43
44
  if (!orig) {
@@ -48,7 +49,7 @@ function rgba2rgb(orig) {
48
49
  };
49
50
  } else if (typeof orig == 'string') {
50
51
  const result = orig.match(/(?:((hsl|rgb)a? *\(([\d.%]+(?:deg|g?rad|turn)?)[ ,]*([\d.%]+)[ ,]*([\d.%]+)[ ,/]*([\d.%]*)\))|(#((?:[\d\w]{3}){1,2})([\d\w]{1,2})?))/i);
51
- if (!result) {
52
+ if (result === null) {
52
53
  return {
53
54
  color: orig,
54
55
  opacity: 1.0
@@ -62,10 +63,10 @@ function rgba2rgb(orig) {
62
63
  color: `#${result[8]}`,
63
64
  opacity
64
65
  };
65
- } else if (result[0]) {
66
+ } else if (result[1]) {
66
67
  return {
67
68
  color: `${result[2]}(${result[3]},${result[4]},${result[5]})`,
68
- opacity: Number(result[6]) || 1.0
69
+ opacity: result[6] ? Number(result[6]) : 1.0
69
70
  };
70
71
  }
71
72
  }
@@ -692,31 +693,6 @@ class Style {
692
693
  defineStyleProperty(this, 'icon', 'color', params.icon.color);
693
694
  defineStyleProperty(this, 'icon', 'opacity', params.icon.opacity, 1.0);
694
695
  }
695
-
696
- /**
697
- * Copies the content of the target style into this style.
698
- * @param {Style} style - The style to copy.
699
- *
700
- * @return {Style} This style.
701
- */
702
- copy(style) {
703
- Object.assign(this.fill, style.fill);
704
- Object.assign(this.stroke, style.stroke);
705
- Object.assign(this.point, style.point);
706
- Object.assign(this.text, style.text);
707
- Object.assign(this.icon, style.icon);
708
- return this;
709
- }
710
-
711
- /**
712
- * Clones this style.
713
- *
714
- * @return {Style} The new style, cloned from this one.
715
- */
716
- clone() {
717
- const clone = new Style();
718
- return clone.copy(this);
719
- }
720
696
  setContext(ctx) {
721
697
  this.context = ctx;
722
698
  }
@@ -940,8 +916,35 @@ class Style {
940
916
  if (iconImg) {
941
917
  try {
942
918
  style.icon.id = iconImg;
919
+ if (iconImg.stops) {
920
+ const iconCropValue = {
921
+ ...(iconImg.base !== undefined && {
922
+ base: iconImg.base
923
+ }),
924
+ stops: iconImg.stops.map(stop => {
925
+ let cropValues = sprites[stop[1]];
926
+ if (stop[1].includes('{')) {
927
+ cropValues = function (p) {
928
+ const id = stop[1].replace(/\{(.+?)\}/g, (a, b) => p[b] || '').trim();
929
+ cropValues = sprites[id];
930
+ return sprites[id];
931
+ };
932
+ }
933
+ return [stop[0], cropValues];
934
+ })
935
+ };
936
+ style.icon.cropValues = iconCropValue;
937
+ } else {
938
+ style.icon.cropValues = sprites[iconImg];
939
+ if (iconImg[0].includes('{')) {
940
+ style.icon.cropValues = function (p) {
941
+ const id = iconImg.replace(/\{(.+?)\}/g, (a, b) => p[b] || '').trim();
942
+ style.icon.cropValues = sprites[id];
943
+ return sprites[id];
944
+ };
945
+ }
946
+ }
943
947
  style.icon.source = sprites.source;
944
- style.icon.cropValues = sprites[iconImg];
945
948
  style.icon.size = readVectorProperty(layer.layout['icon-size']) || 1;
946
949
  const {
947
950
  color,
package/lib/Core/View.js CHANGED
@@ -94,6 +94,9 @@ function _preprocessLayer(view, layer, parentLayer) {
94
94
  return layer;
95
95
  });
96
96
  }
97
+ if (layer.isOGC3DTilesLayer) {
98
+ layer._setup(view);
99
+ }
97
100
  return layer;
98
101
  }
99
102
  const _eventCoords = new THREE.Vector2();
@@ -54,7 +54,7 @@ function object3DHasFeature(object3d) {
54
54
  class C3DTilesLayer extends GeometryLayer {
55
55
  #fillColorMaterialsBuffer;
56
56
  /**
57
- * Constructs a new instance of 3d tiles layer.
57
+ * @deprecated Deprecated 3D Tiles layer. Use {@link OGC3DTilesLayer} instead.
58
58
  * @constructor
59
59
  * @extends GeometryLayer
60
60
  *
@@ -116,8 +116,8 @@ class C3DTilesLayer extends GeometryLayer {
116
116
  this.pntsShape = PNTS_SHAPE.CIRCLE;
117
117
  this.classification = config.classification;
118
118
  this.pntsSizeMode = PNTS_SIZE_MODE.VALUE;
119
- this.pntsMinAttenuatedSize = config.pntsMinAttenuatedSize || 3;
120
- this.pntsMaxAttenuatedSize = config.pntsMaxAttenuatedSize || 10;
119
+ this.pntsMinAttenuatedSize = config.pntsMinAttenuatedSize || 1;
120
+ this.pntsMaxAttenuatedSize = config.pntsMaxAttenuatedSize || 7;
121
121
  if (config.pntsMode) {
122
122
  const exists = Object.values(PNTS_MODE).includes(config.pntsMode);
123
123
  if (!exists) {
@@ -186,8 +186,8 @@ class C3DTilesLayer extends GeometryLayer {
186
186
  init3dTilesLayer(view, view.mainLoop.scheduler, this, tileset.root).then(resolve);
187
187
  });
188
188
  }
189
- preUpdate() {
190
- return pre3dTilesUpdate.bind(this)();
189
+ preUpdate(context) {
190
+ return pre3dTilesUpdate.bind(this)(context);
191
191
  }
192
192
  update(context, layer, node) {
193
193
  return update(context, layer, node);
@@ -243,7 +243,7 @@ class C3DTilesLayer extends GeometryLayer {
243
243
  // face is a Face3 object of THREE which is a
244
244
  // triangular face. face.a is its first vertex
245
245
  const vertex = closestIntersect.face.a;
246
- const batchID = closestIntersect.object.geometry.attributes._BATCHID.array[vertex];
246
+ const batchID = closestIntersect.object.geometry.attributes._BATCHID.getX(vertex);
247
247
  return this.tilesC3DTileFeatures.get(tileId).get(batchID);
248
248
  }
249
249
 
@@ -0,0 +1,386 @@
1
+ import * as THREE from 'three';
2
+ import { TilesRenderer, GLTFStructuralMetadataExtension, GLTFMeshFeaturesExtension, GLTFCesiumRTCExtension, CesiumIonAuthPlugin, GoogleCloudAuthPlugin, ImplicitTilingPlugin } from '3d-tiles-renderer';
3
+ import GeometryLayer from "./GeometryLayer.js";
4
+ import iGLTFLoader from "../Parser/iGLTFLoader.js";
5
+ import { DRACOLoader } from "../ThreeExtended/loaders/DRACOLoader.js";
6
+ import { KTX2Loader } from "../ThreeExtended/loaders/KTX2Loader.js";
7
+ import ReferLayerProperties from "./ReferencingLayerProperties.js";
8
+ import PointsMaterial, { PNTS_MODE, PNTS_SHAPE, PNTS_SIZE_MODE, ClassificationScheme } from "../Renderer/PointsMaterial.js";
9
+ const _raycaster = new THREE.Raycaster();
10
+
11
+ // Internal instance of GLTFLoader, passed to 3d-tiles-renderer-js to support GLTF 1.0 and 2.0
12
+ // Temporary exported to be used in deprecated B3dmParser
13
+ export const itownsGLTFLoader = new iGLTFLoader();
14
+ itownsGLTFLoader.register(() => new GLTFMeshFeaturesExtension());
15
+ itownsGLTFLoader.register(() => new GLTFStructuralMetadataExtension());
16
+ itownsGLTFLoader.register(() => new GLTFCesiumRTCExtension());
17
+
18
+ // Instantiated by the first tileset. Used to share cache and download and parse queues between tilesets
19
+ let lruCache = null;
20
+ let downloadQueue = null;
21
+ let parseQueue = null;
22
+ export const OGC3DTILES_LAYER_EVENTS = {
23
+ /**
24
+ * Fired when a new root or child tile set is loaded
25
+ * @event OGC3DTilesLayer#load-tile-set
26
+ * @type {Object}
27
+ * @property {Object} tileset - the tileset json parsed in an Object
28
+ * @property {String} url - tileset url
29
+ */
30
+ LOAD_TILE_SET: 'load-tile-set',
31
+ /**
32
+ * Fired when a tile model is loaded
33
+ * @event OGC3DTilesLayer#load-model
34
+ * @type {Object}
35
+ * @property {THREE.Group} scene - the model (tile content) parsed in a THREE.GROUP
36
+ * @property {Object} tile - the tile metadata from the tileset
37
+ */
38
+ LOAD_MODEL: 'load-model',
39
+ /**
40
+ * Fired when a tile model is disposed
41
+ * @event OGC3DTilesLayer#dispose-model
42
+ * @type {Object}
43
+ * @property {THREE.Group} scene - the model (tile content) that is disposed
44
+ * @property {Object} tile - the tile metadata from the tileset
45
+ */
46
+ DISPOSE_MODEL: 'dispose-model',
47
+ /**
48
+ * Fired when a tiles visibility changes
49
+ * @event OGC3DTilesLayer#tile-visibility-change
50
+ * @type {Object}
51
+ * @property {THREE.Group} scene - the model (tile content) parsed in a THREE.GROUP
52
+ * @property {Object} tile - the tile metadata from the tileset
53
+ * @property {boolean} visible - the tile visible state
54
+ */
55
+ TILE_VISIBILITY_CHANGE: 'tile-visibility-change'
56
+ };
57
+
58
+ /**
59
+ * Enable loading 3D Tiles with [Draco](https://google.github.io/draco/) geometry extension.
60
+ *
61
+ * @param {String} path path to draco library folder containing the JS and WASM decoder libraries. They can be found in
62
+ * [itowns examples](https://github.com/iTowns/itowns/tree/master/examples/libs/draco).
63
+ * @param {Object} [config] optional configuration for Draco decoder (see threejs'
64
+ * [setDecoderConfig](https://threejs.org/docs/index.html?q=draco#examples/en/loaders/DRACOLoader.setDecoderConfig) that
65
+ * is called under the hood with this configuration for details.
66
+ */
67
+ export function enableDracoLoader(path, config) {
68
+ if (!path) {
69
+ throw new Error('Path to draco folder is mandatory');
70
+ }
71
+ const dracoLoader = new DRACOLoader();
72
+ dracoLoader.setDecoderPath(path);
73
+ if (config) {
74
+ dracoLoader.setDecoderConfig(config);
75
+ }
76
+ itownsGLTFLoader.setDRACOLoader(dracoLoader);
77
+ }
78
+
79
+ /**
80
+ * Enable loading 3D Tiles with [KTX2](https://www.khronos.org/ktx/) texture extension.
81
+ *
82
+ * @param {String} path path to ktx2 library folder containing the JS and WASM decoder libraries. They can be found in
83
+ * [itowns examples](https://github.com/iTowns/itowns/tree/master/examples/libs/basis).
84
+ * @param {THREE.WebGLRenderer} renderer the threejs renderer
85
+ */
86
+ export function enableKtx2Loader(path, renderer) {
87
+ if (!path || !renderer) {
88
+ throw new Error('Path to ktx2 folder and renderer are mandatory');
89
+ }
90
+ const ktx2Loader = new KTX2Loader();
91
+ ktx2Loader.setTranscoderPath(path);
92
+ ktx2Loader.detectSupport(renderer);
93
+ itownsGLTFLoader.setKTX2Loader(ktx2Loader);
94
+ }
95
+ class OGC3DTilesLayer extends GeometryLayer {
96
+ /**
97
+ * Layer for [3D Tiles](https://www.ogc.org/standard/3dtiles/) datasets.
98
+ * @extends Layer
99
+ *
100
+ * @constructor
101
+ *
102
+ * @param {String} id - unique layer id.
103
+ * @param {Object} config - layer specific configuration
104
+ * @param {OGC3DTilesSource} config.source - data source configuration
105
+ * @param {String} [config.pntsMode= PNTS_MODE.COLOR] Point cloud coloring mode (passed to {@link PointsMaterial}).
106
+ * Only 'COLOR' or 'CLASSIFICATION' are possible. COLOR uses RGB colors of the points,
107
+ * CLASSIFICATION uses a classification property of the batch table to color points.
108
+ * @param {ClassificationScheme} [config.classificationScheme] {@link PointsMaterial} classification scheme
109
+ * @param {String} [config.pntsShape= PNTS_SHAPE.CIRCLE] Point cloud point shape. Only 'CIRCLE' or 'SQUARE' are possible.
110
+ * (passed to {@link PointsMaterial}).
111
+ * @param {String} [config.pntsSizeMode= PNTS_SIZE_MODE.VALUE] {@link PointsMaterial} Point cloud size mode (passed to {@link PointsMaterial}).
112
+ * Only 'VALUE' or 'ATTENUATED' are possible. VALUE use constant size, ATTENUATED compute size depending on distance
113
+ * from point to camera.
114
+ * @param {Number} [config.pntsMinAttenuatedSize=3] Minimum scale used by 'ATTENUATED' size mode.
115
+ * @param {Number} [config.pntsMaxAttenuatedSize=10] Maximum scale used by 'ATTENUATED' size mode.
116
+ */
117
+ constructor(id, config) {
118
+ super(id, new THREE.Group(), {
119
+ source: config.source
120
+ });
121
+ this.isOGC3DTilesLayer = true;
122
+ this._handlePointsMaterialConfig(config);
123
+ this.tilesRenderer = new TilesRenderer(this.source.url);
124
+ if (config.source.isOGC3DTilesIonSource) {
125
+ this.tilesRenderer.registerPlugin(new CesiumIonAuthPlugin({
126
+ apiToken: config.source.accessToken,
127
+ assetId: config.source.assetId
128
+ }));
129
+ } else if (config.source.isOGC3DTilesGoogleSource) {
130
+ this.tilesRenderer.registerPlugin(new GoogleCloudAuthPlugin({
131
+ apiToken: config.source.key
132
+ }));
133
+ }
134
+ this.tilesRenderer.registerPlugin(new ImplicitTilingPlugin());
135
+ this.tilesRenderer.manager.addHandler(/\.gltf$/, itownsGLTFLoader);
136
+ this._setupCacheAndQueues();
137
+ this._setupEvents();
138
+ this.object3d.add(this.tilesRenderer.group);
139
+
140
+ // Add an initialization step that is resolved when the root tileset is loaded (see this._setup below), meaning
141
+ // that the layer will be marked ready when the tileset has been loaded.
142
+ this._res = this.addInitializationStep();
143
+
144
+ /**
145
+ * @type {number}
146
+ */
147
+ this.sseThreshold = this.tilesRenderer.errorTarget;
148
+ Object.defineProperty(this, 'sseThreshold', {
149
+ get() {
150
+ return this.tilesRenderer.errorTarget;
151
+ },
152
+ set(value) {
153
+ this.tilesRenderer.errorTarget = value;
154
+ }
155
+ });
156
+ if (config.sseThreshold) {
157
+ this.sseThreshold = config.sseThreshold;
158
+ }
159
+ }
160
+
161
+ /**
162
+ * Store points material config so they can be used later to substitute points tiles material by our own PointsMaterial
163
+ * These properties should eventually be managed through the Style API (see https://github.com/iTowns/itowns/issues/2336)
164
+ * @param {Object} config - points material configuration as passed to the layer constructor.
165
+ * @private
166
+ */
167
+ _handlePointsMaterialConfig(config) {
168
+ this.pntsMode = config.pntsMode ?? PNTS_MODE.COLOR;
169
+ this.pntsShape = config.pntsShape ?? PNTS_SHAPE.CIRCLE;
170
+ this.classification = config.classification ?? ClassificationScheme.DEFAULT;
171
+ this.pntsSizeMode = config.pntsSizeMode ?? PNTS_SIZE_MODE.VALUE;
172
+ this.pntsMinAttenuatedSize = config.pntsMinAttenuatedSize || 3;
173
+ this.pntsMaxAttenuatedSize = config.pntsMaxAttenuatedSize || 10;
174
+ }
175
+
176
+ /**
177
+ * Sets the lruCache and download and parse queues so they are shared amongst all tilesets.
178
+ * @private
179
+ */
180
+ _setupCacheAndQueues() {
181
+ if (lruCache === null) {
182
+ lruCache = this.tilesRenderer.lruCache;
183
+ } else {
184
+ this.tilesRenderer.lruCache = lruCache;
185
+ }
186
+ if (downloadQueue === null) {
187
+ downloadQueue = this.tilesRenderer.downloadQueue;
188
+ } else {
189
+ this.tilesRenderer.downloadQueue = downloadQueue;
190
+ }
191
+ if (parseQueue === null) {
192
+ parseQueue = this.tilesRenderer.parseQueue;
193
+ } else {
194
+ this.tilesRenderer.parseQueue = parseQueue;
195
+ }
196
+ }
197
+
198
+ /**
199
+ * Binds 3d-tiles-renderer events to this layer.
200
+ * @private
201
+ */
202
+ _setupEvents() {
203
+ for (const ev of Object.values(OGC3DTILES_LAYER_EVENTS)) {
204
+ this.tilesRenderer.addEventListener(ev, e => {
205
+ this.dispatchEvent(e);
206
+ });
207
+ }
208
+ }
209
+
210
+ /**
211
+ * Setup 3D tiles renderer js TilesRenderer with the camera, binds events and start updating. Executed when the
212
+ * layer has been added to the view.
213
+ * @param {View} view - the view the layer has been added to.
214
+ * @private
215
+ */
216
+ _setup(view) {
217
+ this.tilesRenderer.setCamera(view.camera3D);
218
+ this.tilesRenderer.setResolutionFromRenderer(view.camera3D, view.renderer);
219
+ // Setup whenReady to be fullfiled when the root tileset has been loaded
220
+ let rootTilesetLoaded = false;
221
+ this.tilesRenderer.addEventListener('load-tile-set', () => {
222
+ view.notifyChange(this);
223
+ if (!rootTilesetLoaded) {
224
+ rootTilesetLoaded = true;
225
+ this._res();
226
+ }
227
+ });
228
+ this.tilesRenderer.addEventListener('load-model', _ref => {
229
+ let {
230
+ scene
231
+ } = _ref;
232
+ scene.traverse(obj => {
233
+ this._assignFinalMaterial(obj);
234
+ this._assignFinalAttributes(obj);
235
+ });
236
+ view.notifyChange(this);
237
+ });
238
+ // Start loading tileset and tiles
239
+ this.tilesRenderer.update();
240
+ }
241
+
242
+ /**
243
+ * Replace materials from GLTFLoader by our own custom materials. Note that
244
+ * the replaced materials are not compiled yet and will be disposed by the
245
+ * GC.
246
+ * @param {Object3D} model
247
+ * @private
248
+ */
249
+ _assignFinalMaterial(model) {
250
+ let material = model.material;
251
+ if (model.isPoints) {
252
+ const pointsMaterial = new PointsMaterial({
253
+ mode: this.pntsMode,
254
+ shape: this.pntsShape,
255
+ classificationScheme: this.classification,
256
+ sizeMode: this.pntsSizeMode,
257
+ minAttenuatedSize: this.pntsMinAttenuatedSize,
258
+ maxAttenuatedSize: this.pntsMaxAttenuatedSize
259
+ });
260
+ pointsMaterial.copy(material);
261
+ material = pointsMaterial;
262
+ }
263
+ if (material) {
264
+ ReferLayerProperties(material, this);
265
+ }
266
+ model.material = material;
267
+ }
268
+
269
+ /**
270
+ * @param {Object3D} model
271
+ * @private
272
+ */
273
+ _assignFinalAttributes(model) {
274
+ const geometry = model.geometry;
275
+ const batchTable = model.batchTable;
276
+
277
+ // Setup classification bufferAttribute
278
+ if (model.isPoints) {
279
+ const classificationData = batchTable?.getData('Classification');
280
+ if (classificationData) {
281
+ geometry.setAttribute('classification', new THREE.BufferAttribute(classificationData, 1));
282
+ }
283
+ }
284
+ }
285
+ preUpdate() {
286
+ this.tilesRenderer.update();
287
+ return null; // don't return any element because 3d-tiles-renderer already updates them
288
+ }
289
+ update() {
290
+ // empty, elements are updated by 3d-tiles-renderer
291
+ }
292
+
293
+ /**
294
+ * Deletes the layer and frees associated memory
295
+ */
296
+ delete() {
297
+ this.tilesRenderer.dispose();
298
+ }
299
+
300
+ /**
301
+ * Get the attributes for the closest intersection from a list of
302
+ * intersects.
303
+ * @param {Array} intersects - An array containing all
304
+ * objects picked under mouse coordinates computed with view.pickObjectsAt(..).
305
+ * @returns {Object | null} - An object containing
306
+ */
307
+ getC3DTileFeatureFromIntersectsArray(intersects) {
308
+ if (!intersects.length) {
309
+ return null;
310
+ }
311
+ const {
312
+ face,
313
+ index,
314
+ object
315
+ } = intersects[0];
316
+
317
+ /** @type{number|null} */
318
+ let batchId;
319
+ if (object.isPoints && index) {
320
+ batchId = object.geometry.getAttribute('_BATCHID')?.getX(index) ?? index;
321
+ } else if (object.isMesh && face) {
322
+ batchId = object.geometry.getAttribute('_BATCHID')?.getX(face.a);
323
+ }
324
+ if (batchId === undefined) {
325
+ return null;
326
+ }
327
+ let tileObject = object;
328
+ while (!tileObject.batchTable) {
329
+ tileObject = tileObject.parent;
330
+ }
331
+ return tileObject.batchTable.getDataFromId(batchId);
332
+ }
333
+
334
+ /**
335
+ * Get all 3D objects (mesh and points primitives) as intersects at the
336
+ * given non-normalized screen coordinates.
337
+ * @param {View} view - The view instance.
338
+ * @param {THREE.Vector2} coords - The coordinates to pick in the view. It
339
+ * should have at least `x` and `y` properties.
340
+ * @param {number} radius - Radius of the picking circle.
341
+ * @param {Array} [target=[]] - Target array to push results too
342
+ * @returns {Array} Array containing all target objects picked under the
343
+ * specified coordinates.
344
+ */
345
+ pickObjectsAt(view, coords) {
346
+ let target = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : [];
347
+ const camera = view.camera.camera3D;
348
+ _raycaster.setFromCamera(view.viewToNormalizedCoords(coords), camera);
349
+ _raycaster.near = camera.near;
350
+ _raycaster.far = camera.far;
351
+ _raycaster.firstHitOnly = true;
352
+ _raycaster.intersectObject(this.tilesRenderer.group, true, target);
353
+ return target;
354
+ }
355
+
356
+ // eslint-disable-next-line no-unused-vars
357
+ attach() {
358
+ console.warn('[OGC3DTilesLayer]: Attaching / detaching layers is not yet implemented for OGC3DTilesLayer.');
359
+ }
360
+
361
+ // eslint-disable-next-line no-unused-vars
362
+ detach() {
363
+ console.warn('[OGC3DTilesLayer]: Attaching / detaching layers is not yet implemented for OGC3DTilesLayer.');
364
+ return true;
365
+ }
366
+
367
+ // eslint-disable-next-line no-unused-vars
368
+ getObjectToUpdateForAttachedLayers() {
369
+ return null;
370
+ }
371
+
372
+ /**
373
+ * Executes a callback for each tile of this layer tileset.
374
+ *
375
+ * @param {Function} callback The callback to execute for each tile. Has the following two parameters:
376
+ * 1. tile (Object) - the JSON tile
377
+ * 2. scene (THREE.Object3D | null) - The tile content. Contains a `batchTable` property. Can be null if the tile
378
+ * has not yet been loaded.
379
+ */
380
+ forEachTile(callback) {
381
+ this.tilesRenderer.traverse(tile => {
382
+ callback(tile, tile.cached.scene);
383
+ });
384
+ }
385
+ }
386
+ export default OGC3DTilesLayer;
@@ -134,7 +134,7 @@ class PointCloudLayer extends GeometryLayer {
134
134
  */
135
135
  constructor(id) {
136
136
  let config = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
137
- super(id, new THREE.Group(), config);
137
+ super(id, config.object3d || new THREE.Group(), config);
138
138
  this.isPointCloudLayer = true;
139
139
  this.protocol = 'pointcloud';
140
140
  this.group = config.group || new THREE.Group();