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

iTowns / itowns / 5691913681

pending completion
5691913681

push

github

mgermerie
chore: update three to r154

3933 of 5864 branches covered (67.07%)

Branch coverage included in aggregate %.

7778 of 9456 relevant lines covered (82.25%)

1619.74 hits per line

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

48.72
/src/Process/3dTilesProcessing.js
1
import * as THREE from 'three';
1✔
2
import Extent from 'Core/Geographic/Extent';
1✔
3
import ObjectRemovalHelper from 'Process/ObjectRemovalHelper';
1✔
4
import { C3DTILES_LAYER_EVENTS } from '../Layer/C3DTilesLayer';
1,616!
5

6
/** @module 3dTilesProcessing
7
*/
8

9
function requestNewTile(view, scheduler, geometryLayer, metadata, parent, redraw) {
10
    const command = {
4✔
11
        /* mandatory */
12
        view,
13
        requester: parent,
14
        layer: geometryLayer,
15
        priority: parent ? 1.0 / (parent.distance + 1) : 100,
4✔
16
        /* specific params */
17
        metadata,
18
        redraw,
19
    };
20

21
    geometryLayer.dispatchEvent({ type: C3DTILES_LAYER_EVENTS.ON_TILE_REQUESTED, metadata });
4✔
22

23
    return scheduler.execute(command);
4✔
24
}
25

26
function getChildTiles(tile) {
27
    // only keep children that have the same layer and a valid tileId
28
    return tile.children.filter(n => n.layer == tile.layer && n.tileId);
2✔
29
}
30

31
function subdivideNode(context, layer, node, cullingTest) {
32
    if (node.additiveRefinement) {
1!
33
        // Additive refinement can only fetch visible children.
34
        _subdivideNodeAdditive(context, layer, node, cullingTest);
×
35
    } else {
36
        // Substractive refinement on the other hand requires to replace
37
        // node with all of its children
38
        _subdivideNodeSubstractive(context, layer, node);
1✔
39
    }
40
}
41

42
const tmpBox3 = new THREE.Box3();
1✔
43
const tmpSphere = new THREE.Sphere();
1✔
44
function boundingVolumeToExtent(crs, volume, transform) {
45
    if (volume.region) {
3!
46
        const box = tmpBox3.copy(volume.region.box3D)
×
47
            .applyMatrix4(volume.region.matrixWorld);
48
        return Extent.fromBox3(crs, box);
×
49
    } else if (volume.box) {
3!
50
        const box = tmpBox3.copy(volume.box).applyMatrix4(transform);
3✔
51
        return Extent.fromBox3(crs, box);
3✔
52
    } else {
53
        const sphere = tmpSphere.copy(volume.sphere).applyMatrix4(transform);
×
54
        const box = sphere.getBoundingBox(tmpBox3);
×
55
        return Extent.fromBox3(crs, box);
×
56
    }
57
}
58

59
const tmpMatrix = new THREE.Matrix4();
1✔
60
function _subdivideNodeAdditive(context, layer, node, cullingTest) {
×
61
    for (const child of layer.tileset.tiles[node.tileId].children) {
×
62
        // child being downloaded => skip
63
        if (child.promise || child.loaded) {
×
64
            continue;
×
65
        }
66

67
        // 'child' is only metadata (it's *not* a THREE.Object3D). 'cullingTest' needs
68
        // a matrixWorld, so we compute it: it's node's matrixWorld x child's transform
69
        let overrideMatrixWorld = node.matrixWorld;
×
70
        if (child.transform) {
×
71
            overrideMatrixWorld = tmpMatrix.multiplyMatrices(node.matrixWorld, child.transform);
×
72
        }
73

74
        const isVisible = cullingTest ? !cullingTest(layer, context.camera, child, overrideMatrixWorld) : true;
×
75

76
        // child is not visible => skip
77
        if (!isVisible) {
×
78
            continue;
79
        }
80
        child.promise = requestNewTile(context.view, context.scheduler, layer, child, node, true).then((tile) => {
×
81
            node.add(tile);
×
82
            tile.updateMatrixWorld();
×
83

84
            // The extent is calculated but it's never used in 3D tiles process
85
            const extent = boundingVolumeToExtent(layer.extent.crs, tile.boundingVolume, tile.matrixWorld);
×
86
            tile.traverse((obj) => {
×
87
                obj.extent = extent;
×
88
            });
89
            layer.onTileContentLoaded(tile);
×
90

91
            context.view.notifyChange(child);
×
92
            child.loaded = true;
×
93
            delete child.promise;
×
94
        });
95
    }
×
96
}
97

98
function _subdivideNodeSubstractive(context, layer, node) {
99
    if (!node.pendingSubdivision && getChildTiles(node).length == 0) {
1!
100
        const childrenTiles = layer.tileset.tiles[node.tileId].children;
1✔
101
        if (childrenTiles === undefined || childrenTiles.length === 0) {
1!
102
            return;
×
103
        }
104
        node.pendingSubdivision = true;
1✔
105

106
        const promises = [];
1✔
107
        for (let i = 0; i < childrenTiles.length; i++) {
1✔
108
            promises.push(
1✔
109
                requestNewTile(context.view, context.scheduler, layer, childrenTiles[i], node, false).then((tile) => {
110
                    childrenTiles[i].loaded = true;
1✔
111
                    node.add(tile);
1✔
112
                    tile.updateMatrixWorld();
1✔
113
                    if (node.additiveRefinement) {
1!
114
                        context.view.notifyChange(node);
×
115
                    }
116
                    layer.tileset.tiles[tile.tileId].loaded = true;
1✔
117
                    layer.onTileContentLoaded(tile);
1✔
118
                }));
119
        }
120
        Promise.all(promises).then(() => {
1✔
121
            node.pendingSubdivision = false;
1✔
122
            context.view.notifyChange(node);
1✔
123
        });
124
    }
125
}
126

127
/**
128
 * Check if the node is visible in the camera.
129
 *
130
 * @param      {C3DTilesLayer} layer       node 3D tiles layer
131
 * @param      {Camera}   camera           camera
132
 * @param      {THREE.Object3D}   node             The 3d tile node to check.
133
 * @param      {THREE.Matrix4}   tileMatrixWorld  The node matrix world
134
 * @return     {boolean}  return true if the node is visible
135
 */
136
export function $3dTilesCulling(layer, camera, node, tileMatrixWorld) {
137
    // For viewer Request Volume
138
    // https://github.com/AnalyticalGraphicsInc/3d-tiles-samples/tree/master/tilesets/TilesetWithRequestVolume
139
    if (node.viewerRequestVolume && node.viewerRequestVolume.viewerRequestVolumeCulling(
1!
140
        camera, tileMatrixWorld)) {
141
        return true;
×
142
    }
143

144
    // For bounding volume
145
    if (node.boundingVolume &&
1!
146
        node.boundingVolume.boundingVolumeCulling(camera, tileMatrixWorld)) {
147
        return true;
×
148
    }
149

150
    return false;
1✔
151
}
152

153
// Cleanup all 3dtiles|three.js starting from a given node n.
154
// n's children can be of 2 types:
155
//   - have a 'content' attribute -> it's a tileset and must
156
//     be cleaned with cleanup3dTileset()
157
//   - doesn't have 'content' -> it's a raw Object3D object,
158
//     and must be cleaned with _cleanupObject3D()
159
function cleanup3dTileset(layer, n, depth = 0) {
×
160
    // If this layer is not using additive refinement, we can only
161
    // clean a tile if all its neighbours are cleaned as well because
162
    // a tile can only be in 2 states:
163
    //   - displayed and no children displayed
164
    //   - hidden and all of its children displayed
165
    // So here we implement a conservative measure: if T is cleanable
166
    // we actually only clean its children tiles.
167
    const canCleanCompletely = n.additiveRefinement || depth > 0;
×
168

169
    for (let i = 0; i < n.children.length; i++) {
×
170
        // skip non-tiles elements
171
        if (!n.children[i].content) {
×
172
            if (canCleanCompletely) {
×
173
                ObjectRemovalHelper.removeChildrenAndCleanupRecursively(n.children[i].layer, n.children[i]);
×
174
            }
175
        } else {
176
            cleanup3dTileset(layer, n.children[i], depth + 1);
×
177
        }
178
    }
179

180

181
    if (canCleanCompletely) {
×
182
        if (n.dispose) {
×
183
            n.dispose();
×
184
        }
185
        delete n.content;
×
186
        layer.tileset.tiles[n.tileId].loaded = false;
×
187
        n.remove(...n.children);
×
188

189
        // and finally remove from parent
190
        if (depth == 0 && n.parent) {
×
191
            n.parent.remove(n);
×
192
        }
193
    } else {
194
        const tiles = getChildTiles(n);
×
195
        n.remove(...tiles);
×
196
    }
197
}
198

199
// this is a layer
200
export function pre3dTilesUpdate() {
201
    if (!this.visible) {
1!
202
        return [];
×
203
    }
204

205
    // Elements removed are added in the layer._cleanableTiles list.
206
    // Since we simply push in this array, the first item is always
207
    // the oldest one.
208
    const now = Date.now();
1✔
209
    if (this._cleanableTiles.length
1!
210
        && (now - this._cleanableTiles[0].cleanableSince) > this.cleanupDelay) {
211
        // Make sure we don't clean root tile
212
        this.root.cleanableSince = undefined;
×
213

214
        let i = 0;
×
215
        for (; i < this._cleanableTiles.length; i++) {
×
216
            const elt = this._cleanableTiles[i];
×
217
            if ((now - elt.cleanableSince) > this.cleanupDelay) {
×
218
                cleanup3dTileset(this, elt);
×
219
            } else {
220
                // later entries are younger
221
                break;
×
222
            }
223
        }
224
        // remove deleted elements from _cleanableTiles
225
        this._cleanableTiles.splice(0, i);
×
226
    }
227

228
    return [this.root];
1✔
229
}
230

231
const boundingVolumeBox = new THREE.Box3();
1✔
232
const boundingVolumeSphere = new THREE.Sphere();
1✔
233
export function computeNodeSSE(camera, node) {
234
    node.distance = 0;
7✔
235
    if (node.boundingVolume.region) {
7✔
236
        boundingVolumeBox.copy(node.boundingVolume.region.box3D);
2✔
237
        boundingVolumeBox.applyMatrix4(node.boundingVolume.region.matrixWorld);
2✔
238
        node.distance = boundingVolumeBox.distanceToPoint(camera.camera3D.position);
2✔
239
    } else if (node.boundingVolume.box) {
5✔
240
        // boundingVolume.box is affected by matrixWorld
241
        boundingVolumeBox.copy(node.boundingVolume.box);
3✔
242
        boundingVolumeBox.applyMatrix4(node.matrixWorld);
3✔
243
        node.distance = boundingVolumeBox.distanceToPoint(camera.camera3D.position);
3✔
244
    } else if (node.boundingVolume.sphere) {
2!
245
        // boundingVolume.sphere is affected by matrixWorld
246
        boundingVolumeSphere.copy(node.boundingVolume.sphere);
2✔
247
        boundingVolumeSphere.applyMatrix4(node.matrixWorld);
2✔
248
        // TODO: see https://github.com/iTowns/itowns/issues/800
249
        node.distance = Math.max(0.0,
2✔
250
            boundingVolumeSphere.distanceToPoint(camera.camera3D.position));
251
    } else {
252
        return Infinity;
×
253
    }
254
    if (node.distance === 0) {
7!
255
        // This test is needed in case geometricError = distance = 0
256
        return Infinity;
×
257
    }
258
    return camera._preSSE * (node.geometricError / node.distance);
7✔
259
}
260

261
export function init3dTilesLayer(view, scheduler, layer, rootTile) {
262
    return requestNewTile(view, scheduler, layer, rootTile, undefined, true).then(
3✔
263
        (tile) => {
264
            layer.object3d.add(tile);
3✔
265
            tile.updateMatrixWorld();
3✔
266
            layer.tileset.tiles[tile.tileId].loaded = true;
3✔
267
            layer.root = tile;
3✔
268
            // The extent is calculated but it's never used in 3D tiles process
269
            layer.extent = boundingVolumeToExtent(layer.crs || view.referenceCrs,
3✔
270
                tile.boundingVolume, tile.matrixWorld);
271
            layer.onTileContentLoaded(tile);
3✔
272
        });
273
}
274

275
function setDisplayed(node, display) {
276
    // The geometry of the tile is not in node, but in node.content
277
    // To change the display state, we change node.content.visible instead of
278
    // node.material.visible
279
    if (node.content) {
1!
280
        node.content.visible = display;
1✔
281
    }
282
}
283

284
function markForDeletion(layer, elt) {
285
    if (!elt.cleanableSince) {
×
286
        elt.cleanableSince = Date.now();
×
287
        layer._cleanableTiles.push(elt);
×
288
    }
289
}
290

291
/**
292
 * This funcion builds the method to update 3d tiles node.
293
 *
294
 * The returned method checks the 3d tile visibility with `cullingTest` function.
295
 * It subdivises visible node if `subdivisionTest` return `true`.
296
 *
297
 * @param      {Function}  [cullingTest=$3dTilesCulling]                 The culling test method.
298
 * @param      {Function}  [subdivisionTest=$3dTilesSubdivisionControl]  The subdivision test method.
299
 * @return     {Function}    { description_of_the_return_value }
300
 */
301
export function process3dTilesNode(cullingTest = $3dTilesCulling, subdivisionTest = $3dTilesSubdivisionControl) {
1!
302
    return function _process3dTilesNodes(context, layer, node) {
1✔
303
        // early exit if parent's subdivision is in progress
304
        if (node.parent.pendingSubdivision && !node.parent.additiveRefinement) {
1!
305
            node.visible = false;
×
306
            return undefined;
×
307
        }
308

309
        // do proper culling
310
        const isVisible = cullingTest ? (!cullingTest(layer, context.camera, node, node.matrixWorld)) : true;
1!
311
        node.visible = isVisible;
1✔
312

313
        if (isVisible) {
1!
314
            if (node.cleanableSince) {
1!
315
                layer._cleanableTiles.splice(layer._cleanableTiles.indexOf(node), 1);
×
316
                node.cleanableSince = undefined;
×
317
            }
318

319
            let returnValue;
320
            if (node.pendingSubdivision || subdivisionTest(context, layer, node)) {
1!
321
                subdivideNode(context, layer, node, cullingTest);
1✔
322
                // display iff children aren't ready
323
                setDisplayed(node, node.pendingSubdivision || node.additiveRefinement);
1!
324
                returnValue = getChildTiles(node);
1✔
325
            } else {
326
                setDisplayed(node, true);
×
327

328
                for (const n of getChildTiles(node)) {
×
329
                    n.visible = false;
×
330
                    markForDeletion(layer, n);
×
331
                }
×
332
            }
333
            return returnValue;
1✔
334
        }
335

336
        markForDeletion(layer, node);
×
337
    };
338
}
339

340
/**
341
 *
342
 *
343
 * the method returns true if the `node` should be subivised.
344
 *
345
 * @param      {object}   context  The current context
346
 * @param      {Camera}   context.camera  The current camera
347
 * @param      {C3DTilesLayer}   layer  The 3d tile layer
348
 * @param      {THREE.Object3D}  node  The 3d tile node
349
 * @return     {boolean}
350
 */
351
export function $3dTilesSubdivisionControl(context, layer, node) {
352
    if (layer.tileset.tiles[node.tileId].children === undefined) {
1!
353
        return false;
×
354
    }
355
    if (layer.tileset.tiles[node.tileId].isTileset) {
1!
356
        return true;
×
357
    }
358
    const sse = computeNodeSSE(context.camera, node);
1✔
359
    return sse > layer.sseThreshold;
1✔
360
}
361

362

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

© 2025 Coveralls, Inc