• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

iTowns / itowns / 12256691367

10 Dec 2024 01:03PM UTC coverage: 87.065% (+0.3%) from 86.793%
12256691367

push

github

jailln
fix(doc): fix doc generation error

2781 of 3692 branches covered (75.33%)

Branch coverage included in aggregate %.

1 of 1 new or added line in 1 file covered. (100.0%)

285 existing lines in 14 files now uncovered.

24990 of 28205 relevant lines covered (88.6%)

992.06 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

78.79
/src/Layer/TiledGeometryLayer.js
1
import * as THREE from 'three';
1✔
2
import GeometryLayer from 'Layer/GeometryLayer';
1✔
3
import { InfoTiledGeometryLayer } from 'Layer/InfoLayer';
1✔
4
import Picking from 'Core/Picking';
1✔
5
import convertToTile from 'Converter/convertToTile';
1✔
6
import ObjectRemovalHelper from 'Process/ObjectRemovalHelper';
1✔
7
import { ImageryLayers } from 'Layer/Layer';
1✔
8
import { CACHE_POLICIES } from 'Core/Scheduler/Cache';
1✔
9

1✔
10
const subdivisionVector = new THREE.Vector3();
1✔
11
const boundingSphereCenter = new THREE.Vector3();
1✔
12

1✔
13
/**
1✔
14
 * @property {InfoTiledGeometryLayer} info - Status information of layer
1✔
15
 * @property {boolean} isTiledGeometryLayer - Used to checkout whether this
1✔
16
 * layer is a TiledGeometryLayer. Default is true. You should not change this,
1✔
17
 * as it is used internally for optimisation.
1✔
18
 * @property {boolean} hideSkirt (default false) - Used to hide the skirt (tile borders).
1✔
19
 * Useful when the layer opacity < 1
1✔
20
 *
1✔
21
 * @extends GeometryLayer
1✔
22
 */
1✔
23
class TiledGeometryLayer extends GeometryLayer {
1✔
24
    /**
1✔
25
     * A layer extending the {@link GeometryLayer}, but with a tiling notion.
1✔
26
     *
1✔
27
     * `TiledGeometryLayer` is the ground where `ColorLayer` and `ElevationLayer` are attached.
1✔
28
     * `TiledGeometryLayer` is a quadtree data structure. At zoom 0,
1✔
29
     * there is a single tile for the whole earth. At zoom level 1,
1✔
30
     * the single tile splits into 4 tiles (2x2 tile square).
1✔
31
     * Each zoom level quadtree divides the geometry tiles of the one before it.
1✔
32
     * The camera distance determines how the tiles are subdivided for optimal data display.
1✔
33
     *
1✔
34
     * Some `GeometryLayer` can also be attached to the `TiledGeometryLayer` if they want to take advantage of the quadtree.
1✔
35
     *
1✔
36
     * ![tiled geometry](/docs/static/images/tiledGeometry.jpeg)
1✔
37
     * _In `GlobeView`, **red lines** represents the **WGS84 grid** and **orange lines** the **Pseudo-mercator grid**._
1✔
38
     * _In this picture, there are tiles with 3 different zoom/levels._
1✔
39
     *
1✔
40
     * The zoom/level is based on [tiled web map](https://en.wikipedia.org/wiki/Tiled_web_map).
1✔
41
     * It corresponds at meters by pixel. If the projection tile exceeds a certain pixel size (on screen)
1✔
42
     * then it is subdivided into 4 tiles with a zoom greater than 1.
1✔
43
     *
1✔
44
     * @param {string} id - The id of the layer, that should be unique. It is
1✔
45
     * not mandatory, but an error will be emitted if this layer is added a
1✔
46
     * {@link View} that already has a layer going by that id.
1✔
47
     * @param {THREE.Object3D} object3d - The object3d used to contain the
1✔
48
     * geometry of the TiledGeometryLayer. It is usually a `THREE.Group`, but it
1✔
49
     * can be anything inheriting from a `THREE.Object3d`.
1✔
50
     * @param {Array} schemeTile - extents Array of root tiles
1✔
51
     * @param {Object} builder - builder geometry object
1✔
52
     * @param {Object} [config] - Optional configuration, all elements in it
1✔
53
     * will be merged as is in the layer. For example, if the configuration
1✔
54
     * contains three elements `name, protocol, extent`, these elements will be
1✔
55
     * available using `layer.name` or something else depending on the property
1✔
56
     * name.
1✔
57
     * @param {Source} [config.source] - Description and options of the source.
1✔
58
     *
1✔
59
     * @throws {Error} `object3d` must be a valid `THREE.Object3d`.
1✔
60
     */
1✔
61
    constructor(id, object3d, schemeTile, builder, config) {
1✔
62
        const {
30✔
63
            sseSubdivisionThreshold = 1.0,
30✔
64
            minSubdivisionLevel,
30✔
65
            maxSubdivisionLevel,
30✔
66
            maxDeltaElevationLevel,
30✔
67
            tileMatrixSets,
30✔
68
            diffuse,
30✔
69
            showOutline = false,
30✔
70
            segments,
30✔
71
            disableSkirt = false,
30✔
72
            materialOptions,
30✔
73
            ...configGeometryLayer
30✔
74
        } = config;
30✔
75

30✔
76
        super(id, object3d, {
30✔
77
            ...configGeometryLayer,
30✔
78
            // cacheLifeTime = CACHE_POLICIES.INFINITE because the cache is handled by the builder
30✔
79
            cacheLifeTime: CACHE_POLICIES.INFINITE,
30✔
80
            source: false,
30✔
81
        });
30✔
82

30✔
83
        /**
30✔
84
         * @type {boolean}
30✔
85
         * @readonly
30✔
86
         */
30✔
87
        this.isTiledGeometryLayer = true;
30✔
88

30✔
89
        this.protocol = 'tile';
30✔
90

30✔
91
        // TODO : this should be add in a preprocess method specific to GeoidLayer.
30✔
92
        this.object3d.geoidHeight = 0;
30✔
93

30✔
94
        /**
30✔
95
         * @type {boolean}
30✔
96
         */
30✔
97
        this.disableSkirt = disableSkirt;
30✔
98

30✔
99
        this._hideSkirt = !!config.hideSkirt;
30✔
100

30✔
101
        /**
30✔
102
         * @type {number}
30✔
103
         */
30✔
104
        this.sseSubdivisionThreshold = sseSubdivisionThreshold;
30✔
105

30✔
106
        /**
30✔
107
         * @type {number}
30✔
108
         */
30✔
109
        this.minSubdivisionLevel = minSubdivisionLevel;
30✔
110

30✔
111
        /**
30✔
112
         * @type {number}
30✔
113
         */
30✔
114
        this.maxSubdivisionLevel = maxSubdivisionLevel;
30✔
115

30✔
116
        /**
30✔
117
         * @type {number}
30✔
118
         * @deprecated
30✔
119
         */
30✔
120
        this.maxDeltaElevationLevel = maxDeltaElevationLevel;
30✔
121

30✔
122
        this.segments = segments;
30✔
123
        this.schemeTile = schemeTile;
30✔
124
        this.builder = builder;
30✔
125
        this.info = new InfoTiledGeometryLayer(this);
30✔
126

30✔
127
        if (!this.schemeTile) {
30!
UNCOV
128
            throw new Error(`Cannot init tiled layer without schemeTile for layer ${this.id}`);
×
UNCOV
129
        }
×
130

30✔
131
        if (!this.builder) {
30!
UNCOV
132
            throw new Error(`Cannot init tiled layer without builder for layer ${this.id}`);
×
UNCOV
133
        }
×
134

30✔
135
        this.maxScreenSizeNode = this.sseSubdivisionThreshold * (this.sizeDiagonalTexture * 2);
30✔
136

30✔
137
        this.tileMatrixSets = tileMatrixSets;
30✔
138

30✔
139
        this.materialOptions = materialOptions;
30✔
140

30✔
141
        /*
30✔
142
         * @type {boolean}
30✔
143
         */
30✔
144
        this.showOutline = showOutline;
30✔
145

30✔
146
        /**
30✔
147
         * @type {THREE.Vector3 | undefined}
30✔
148
         */
30✔
149
        this.diffuse = diffuse;
30✔
150

30✔
151
        this.level0Nodes = [];
30✔
152
        const promises = [];
30✔
153

30✔
154
        for (const root of this.schemeTile) {
30✔
155
            promises.push(this.convert(undefined, root));
51✔
156
        }
51✔
157

30✔
158
        this._promises.push(Promise.all(promises).then((level0s) => {
30✔
159
            this.level0Nodes = level0s;
30✔
160
            this.object3d.add(...level0s);
30✔
161
            this.object3d.updateMatrixWorld();
30✔
162
        }));
30✔
163
    }
30✔
164

1✔
165
    get hideSkirt() {
1✔
166
        return this._hideSkirt;
63✔
167
    }
63✔
168

1✔
169
    set hideSkirt(value) {
1✔
UNCOV
170
        if (!this.level0Nodes) {
×
171
            return;
×
172
        }
×
UNCOV
173
        this._hideSkirt = value;
×
UNCOV
174
        for (const node of this.level0Nodes) {
×
UNCOV
175
            node.traverse((obj) => {
×
UNCOV
176
                if (obj.isTileMesh) {
×
UNCOV
177
                    obj.geometry.hideSkirt = value;
×
UNCOV
178
                }
×
UNCOV
179
            });
×
UNCOV
180
        }
×
UNCOV
181
    }
×
182
    /**
1✔
183
     * Picking method for this layer. It uses the {@link Picking#pickTilesAt}
1✔
184
     * method.
1✔
185
     *
1✔
186
     * @param {View} view - The view instance.
1✔
187
     * @param {Object} coordinates - The coordinates to pick in the view. It
1✔
188
     * should have at least `x` and `y` properties.
1✔
189
     * @param {number} radius - Radius of the picking circle.
1✔
190
     * @param {Array} target - Array to push picking result.
1✔
191
     *
1✔
192
     * @return {Array} An array containing all targets picked under the
1✔
193
     * specified coordinates.
1✔
194
     */
1✔
195
    pickObjectsAt(view, coordinates, radius = this.options.defaultPickingRadius, target = []) {
1!
196
        return Picking.pickTilesAt(view, coordinates, radius, this, target);
1✔
197
    }
1✔
198

1✔
199
    /**
1✔
200
     * Does pre-update work on the context:
1✔
201
     * <ul>
1✔
202
     *  <li>update the `colorLayers` and `elevationLayers`</li>
1✔
203
     *  <li>update the `maxElevationLevel`</li>
1✔
204
     * </ul>
1✔
205
     *
1✔
206
     * Once this work is done, it returns a list of nodes to update. Depending
1✔
207
     * on the origin of `sources`, it can return a few things:
1✔
208
     * <ul>
1✔
209
     *  <li>if `sources` is empty, it returns the first node of the layer
1✔
210
     *  (stored as `level0Nodes`), which will trigger the update of the whole
1✔
211
     *  tree</li>
1✔
212
     *  <li>if the update is triggered by a camera move, the whole tree is
1✔
213
     *  returned too</li>
1✔
214
     *  <li>if `source.layer` is this layer, it means that `source` is a node; a
1✔
215
     *  common ancestor will be found if there are multiple sources, with the
1✔
216
     *  default common ancestor being the first source itself</li>
1✔
217
     *  <li>else it returns the whole tree</li>
1✔
218
     * </ul>
1✔
219
     *
1✔
220
     * @param {Object} context - The context of the update; see the {@link
1✔
221
     * MainLoop} for more informations.
1✔
222
     * @param {Set<GeometryLayer|TileMesh>} sources - A list of sources to
1✔
223
     * generate a list of nodes to update.
1✔
224
     *
1✔
225
     * @return {TileMesh[]} The array of nodes to update.
1✔
226
     */
1✔
227
    preUpdate(context, sources) {
1✔
228
        if (sources.has(undefined) || sources.size == 0) {
2!
UNCOV
229
            return this.level0Nodes;
×
UNCOV
230
        }
×
231

2✔
232
        if (__DEBUG__) {
2✔
233
            this._latestUpdateStartingLevel = 0;
2✔
234
        }
2✔
235

2✔
236
        context.colorLayers = context.view.getLayers(
2✔
237
            (l, a) => a && a.id == this.id && l.isColorLayer);
2!
238
        context.elevationLayers = context.view.getLayers(
2✔
239
            (l, a) => a && a.id == this.id && l.isElevationLayer);
2!
240

2✔
241
        context.maxElevationLevel = -1;
2✔
242
        for (const e of context.elevationLayers) {
2!
UNCOV
243
            context.maxElevationLevel = Math.max(e.source.zoom.max, context.maxElevationLevel);
×
UNCOV
244
        }
×
245
        if (context.maxElevationLevel == -1) {
2✔
246
            context.maxElevationLevel = Infinity;
2✔
247
        }
2✔
248

2✔
249
        // Prepare ColorLayer sequence order
2✔
250
        // In this moment, there is only one color layers sequence, because they are attached to tileLayer.
2✔
251
        // In future, the sequence must be returned by parent geometry layer.
2✔
252
        this.colorLayersOrder = ImageryLayers.getColorLayersIdOrderedBySequence(context.colorLayers);
2✔
253

2✔
254
        let commonAncestor;
2✔
255
        for (const source of sources.values()) {
2✔
256
            if (source.isCamera) {
2!
UNCOV
257
                // if the change is caused by a camera move, no need to bother
×
UNCOV
258
                // to find common ancestor: we need to update the whole tree:
×
UNCOV
259
                // some invisible tiles may now be visible
×
UNCOV
260
                return this.level0Nodes;
×
UNCOV
261
            }
×
262
            if (source.layer === this) {
2!
UNCOV
263
                if (!commonAncestor) {
×
UNCOV
264
                    commonAncestor = source;
×
UNCOV
265
                } else {
×
UNCOV
266
                    commonAncestor = source.findCommonAncestor(commonAncestor);
×
UNCOV
267
                    if (!commonAncestor) {
×
UNCOV
268
                        return this.level0Nodes;
×
UNCOV
269
                    }
×
UNCOV
270
                }
×
UNCOV
271
                if (commonAncestor.material == null) {
×
UNCOV
272
                    commonAncestor = undefined;
×
UNCOV
273
                }
×
UNCOV
274
            }
×
275
        }
2✔
276
        if (commonAncestor) {
2!
UNCOV
277
            if (__DEBUG__) {
×
UNCOV
278
                this._latestUpdateStartingLevel = commonAncestor.level;
×
UNCOV
279
            }
×
UNCOV
280
            return [commonAncestor];
×
281
        } else {
2✔
282
            return this.level0Nodes;
2✔
283
        }
2✔
284
    }
2✔
285

1✔
286
    /**
1✔
287
     * Update a node of this layer. The node will not be updated if:
1✔
288
     * <ul>
1✔
289
     *  <li>it does not have a parent, then it is removed</li>
1✔
290
     *  <li>its parent is being subdivided</li>
1✔
291
     *  <li>is not visible in the camera</li>
1✔
292
     * </ul>
1✔
293
     *
1✔
294
     * @param {Object} context - The context of the update; see the {@link
1✔
295
     * MainLoop} for more informations.
1✔
296
     * @param {Layer} layer - Parameter to be removed once all update methods
1✔
297
     * have been aligned.
1✔
298
     * @param {TileMesh} node - The node to update.
1✔
299
     *
1✔
300
     * @returns {Object}
1✔
301
     */
1✔
302
    update(context, layer, node) {
1✔
303
        if (!node.parent) {
5✔
304
            return ObjectRemovalHelper.removeChildrenAndCleanup(this, node);
4✔
305
        }
4✔
306
        // early exit if parent' subdivision is in progress
1✔
307
        if (node.parent.pendingSubdivision) {
5!
UNCOV
308
            node.visible = false;
×
UNCOV
309
            node.material.visible = false;
×
UNCOV
310
            this.info.update(node);
×
UNCOV
311
            return undefined;
×
UNCOV
312
        }
✔
313

1✔
314
        // do proper culling
1✔
315
        node.visible = !this.culling(node, context.camera);
1✔
316

1✔
317
        if (node.visible) {
1✔
318
            let requestChildrenUpdate = false;
1✔
319

1✔
320
            node.material.visible = true;
1✔
321
            this.info.update(node);
1✔
322

1✔
323
            if (node.pendingSubdivision || (TiledGeometryLayer.hasEnoughTexturesToSubdivide(context, node) && this.subdivision(context, this, node))) {
1✔
324
                this.subdivideNode(context, node);
1✔
325
                // display iff children aren't ready
1✔
326
                node.material.visible = node.pendingSubdivision;
1✔
327
                this.info.update(node);
1✔
328
                requestChildrenUpdate = true;
1✔
329
            }
1✔
330

1✔
331
            if (node.material.visible) {
1✔
332
                if (!requestChildrenUpdate) {
1!
333
                    return ObjectRemovalHelper.removeChildren(this, node);
×
UNCOV
334
                }
×
335
            }
1✔
336

1✔
337
            return requestChildrenUpdate ? node.children.filter(n => n.layer == this) : undefined;
1!
338
        }
1!
339

×
340
        node.material.visible = false;
×
341
        this.info.update(node);
×
342
        return ObjectRemovalHelper.removeChildren(this, node);
×
343
    }
×
344

1✔
345
    convert(requester, extent) {
1✔
346
        return convertToTile.convert(requester, extent, this);
63✔
347
    }
63✔
348

1✔
349
    countColorLayersTextures(...layers) {
1✔
350
        return layers.length;
×
351
    }
×
352

1✔
353
    // eslint-disable-next-line
1✔
354
    culling(node, camera) {
1✔
355
        return !camera.isBox3Visible(node.obb.box3D, node.matrixWorld);
1✔
356
    }
1✔
357

1✔
358
    /**
1✔
359
     * Tell if a node has enough elevation or color textures to subdivide.
1✔
360
     * Subdivision is prevented if:
1✔
361
     * <ul>
1✔
362
     *  <li>the node is covered by at least one elevation layer and if the node
1✔
363
     *  doesn't have an elevation texture yet</li>
1✔
364
     *  <li>a color texture is missing</li>
1✔
365
     * </ul>
1✔
366
     *
1✔
367
     * @param {Object} context - The context of the update; see the {@link
1✔
368
     * MainLoop} for more informations.
1✔
369
     * @param {TileMesh} node - The node to subdivide.
1✔
370
     *
1✔
371
     * @returns {boolean} False if the node can not be subdivided, true
1✔
372
     * otherwise.
1✔
373
     */
1✔
374
    static hasEnoughTexturesToSubdivide(context, node) {
1✔
375
        const layerUpdateState = node.layerUpdateState || {};
1!
376
        let nodeLayer = node.material.getElevationLayer();
1✔
377

1✔
378
        for (const e of context.elevationLayers) {
1!
UNCOV
379
            const extents = node.getExtentsByProjection(e.crs);
×
UNCOV
380
            const zoom = extents[0].zoom;
×
UNCOV
381
            if (zoom > e.zoom.max || zoom < e.zoom.min) {
×
UNCOV
382
                continue;
×
UNCOV
383
            }
×
UNCOV
384
            if (!e.frozen && e.ready && e.source.extentInsideLimit(node.extent, zoom) && (!nodeLayer || nodeLayer.level < 0)) {
×
UNCOV
385
                // no stop subdivision in the case of a loading error
×
UNCOV
386
                if (layerUpdateState[e.id] && layerUpdateState[e.id].inError()) {
×
UNCOV
387
                    continue;
×
UNCOV
388
                }
×
UNCOV
389
                return false;
×
UNCOV
390
            }
×
UNCOV
391
        }
×
392

1✔
393
        for (const c of context.colorLayers) {
1!
UNCOV
394
            if (c.frozen || !c.visible || !c.ready) {
×
395
                continue;
×
396
            }
×
397
            const extents = node.getExtentsByProjection(c.crs);
×
398
            const zoom = extents[0].zoom;
×
UNCOV
399
            if (zoom > c.zoom.max || zoom < c.zoom.min) {
×
UNCOV
400
                continue;
×
UNCOV
401
            }
×
UNCOV
402
            // no stop subdivision in the case of a loading error
×
UNCOV
403
            if (layerUpdateState[c.id] && layerUpdateState[c.id].inError()) {
×
UNCOV
404
                continue;
×
UNCOV
405
            }
×
UNCOV
406
            nodeLayer = node.material.getLayer(c.id);
×
UNCOV
407
            if (c.source.extentInsideLimit(node.extent, zoom) && (!nodeLayer || nodeLayer.level < 0)) {
×
UNCOV
408
                return false;
×
UNCOV
409
            }
×
UNCOV
410
        }
×
411
        return true;
1✔
412
    }
1✔
413

1✔
414
    /**
1✔
415
     * Subdivides a node of this layer. If the node is currently in the process
1✔
416
     * of subdivision, it will not do anything here. The subdivision of a node
1✔
417
     * will occur in four part, to create a quadtree. The extent of the node
1✔
418
     * will be divided in four parts: north-west, north-east, south-west and
1✔
419
     * south-east. Once all four nodes are created, they will be added to the
1✔
420
     * current node and the view of the context will be notified of this change.
1✔
421
     *
1✔
422
     * @param {Object} context - The context of the update; see the {@link
1✔
423
     * MainLoop} for more informations.
1✔
424
     * @param {TileMesh} node - The node to subdivide.
1✔
425
     * @return {Promise}  { description_of_the_return_value }
1✔
426
     */
1✔
427
    subdivideNode(context, node) {
1✔
428
        if (!node.pendingSubdivision && !node.children.some(n => n.layer == this)) {
4✔
429
            const extents = node.extent.subdivision();
4✔
430
            // TODO: pendingSubdivision mechanism is fragile, get rid of it
4✔
431
            node.pendingSubdivision = true;
4✔
432

4✔
433
            const command = {
4✔
434
                /* mandatory */
4✔
435
                view: context.view,
4✔
436
                requester: node,
4✔
437
                layer: this,
4✔
438
                priority: 10000,
4✔
439
                /* specific params */
4✔
440
                extentsSource: extents,
4✔
441
                redraw: false,
4✔
442
            };
4✔
443

4✔
444
            return context.scheduler.execute(command).then((children) => {
4✔
445
                for (const child of children) {
1✔
446
                    node.add(child);
4✔
447
                    child.updateMatrixWorld(true);
4✔
448
                }
4✔
449

1✔
450
                node.pendingSubdivision = false;
1✔
451
                context.view.notifyChange(node, false);
1✔
452
            }, (err) => {
4✔
UNCOV
453
                node.pendingSubdivision = false;
×
UNCOV
454
                if (!err.isCancelledCommandException) {
×
UNCOV
455
                    throw new Error(err);
×
UNCOV
456
                }
×
457
            });
4✔
458
        }
4✔
459
    }
4✔
460

1✔
461
    /**
1✔
462
     * Test the subdvision of a node, compared to this layer.
1✔
463
     *
1✔
464
     * @param {Object} context - The context of the update; see the {@link
1✔
465
     * MainLoop} for more informations.
1✔
466
     * @param {PlanarLayer} layer - This layer, parameter to be removed.
1✔
467
     * @param {TileMesh} node - The node to test.
1✔
468
     *
1✔
469
     * @return {boolean} - True if the node is subdivisable, otherwise false.
1✔
470
     */
1✔
471
    subdivision(context, layer, node) {
1✔
472
        if (node.level < this.minSubdivisionLevel) {
3✔
473
            return true;
1✔
474
        }
1✔
475

2✔
476
        if (this.maxSubdivisionLevel <= node.level) {
3!
UNCOV
477
            return false;
×
UNCOV
478
        }
✔
479
        subdivisionVector.setFromMatrixScale(node.matrixWorld);
2✔
480
        boundingSphereCenter.copy(node.boundingSphere.center).applyMatrix4(node.matrixWorld);
2✔
481
        const distance = Math.max(
2✔
482
            0.0,
2✔
483
            context.camera.camera3D.position.distanceTo(boundingSphereCenter) - node.boundingSphere.radius * subdivisionVector.x);
2✔
484

2✔
485
        // Size projection on pixel of bounding
2✔
486
        if (context.camera.camera3D.isOrthographicCamera) {
3!
UNCOV
487
            const camera3D = context.camera.camera3D;
×
UNCOV
488
            const preSSE = context.camera._preSSE * 2 * camera3D.zoom / (camera3D.top - camera3D.bottom);
×
UNCOV
489
            node.screenSize = preSSE * node.boundingSphere.radius * subdivisionVector.x;
×
490
        } else {
3✔
491
            node.screenSize = context.camera._preSSE * (2 * node.boundingSphere.radius * subdivisionVector.x) / distance;
2✔
492
        }
2✔
493

2✔
494
        // The screen space error is calculated to have a correct texture display.
2✔
495
        // For the projection of a texture's texel to be less than or equal to one pixel
2✔
496
        const sse = node.screenSize / (this.sizeDiagonalTexture * 2);
2✔
497

2✔
498
        return this.sseSubdivisionThreshold < sse;
2✔
499
    }
2✔
500
}
1✔
501

1✔
502
export default TiledGeometryLayer;
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc