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

iTowns / itowns / 9791693583

04 Jul 2024 09:14AM UTC coverage: 89.747% (-0.007%) from 89.754%
9791693583

push

github

AnthonyGlt
fix(PointCloud): fix after pr feedback

2823 of 3721 branches covered (75.87%)

Branch coverage included in aggregate %.

2 of 6 new or added lines in 3 files covered. (33.33%)

8 existing lines in 3 files now uncovered.

24617 of 26854 relevant lines covered (91.67%)

904.66 hits per line

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

70.43
/src/Process/3dTilesProcessing.js
1
import * as THREE from 'three';
1✔
2
import ObjectRemovalHelper from 'Process/ObjectRemovalHelper';
1✔
3
import { C3DTilesBoundingVolumeTypes } from 'Core/3DTiles/C3DTilesEnums';
1✔
4
import { C3DTILES_LAYER_EVENTS } from '../Layer/C3DTilesLayer';
1✔
5

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

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

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

3✔
23
    return scheduler.execute(command);
1✔
24
}
2✔
25

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

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

×
42
const tmpMatrix = new THREE.Matrix4();
×
43
function _subdivideNodeAdditive(context, layer, node, cullingTest) {
×
44
    for (const child of layer.tileset.tiles[node.tileId].children) {
×
45
        // child being downloaded => skip
×
46
        if (child.promise || child.loaded) {
×
47
            continue;
×
48
        }
×
49

×
50
        // 'child' is only metadata (it's *not* a THREE.Object3D). 'cullingTest' needs
×
51
        // a matrixWorld, so we compute it: it's node's matrixWorld x child's transform
×
52
        let overrideMatrixWorld = node.matrixWorld;
×
53
        if (child.transform) {
×
54
            overrideMatrixWorld = tmpMatrix.multiplyMatrices(node.matrixWorld, child.transform);
×
55
        }
×
56

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

×
59
        // child is not visible => skip
×
60
        if (!isVisible) {
×
61
            continue;
×
62
        }
×
63
        child.promise = requestNewTile(context.view, context.scheduler, layer, child, node, true).then((tile) => {
×
64
            node.add(tile);
×
65
            tile.updateMatrixWorld();
×
66
            layer.onTileContentLoaded(tile);
1✔
67

1✔
68
            context.view.notifyChange(child);
1✔
69
            child.loaded = true;
1✔
70
            delete child.promise;
1✔
71
        });
1✔
72
    }
1✔
73
}
1✔
74

1✔
75
function _subdivideNodeSubstractive(context, layer, node) {
1✔
76
    if (!node.pendingSubdivision && getChildTiles(node).length == 0) {
1!
77
        const childrenTiles = layer.tileset.tiles[node.tileId].children;
1✔
78
        if (childrenTiles === undefined || childrenTiles.length === 0) {
1✔
79
            return;
1✔
80
        }
1✔
81
        node.pendingSubdivision = true;
1✔
82

1✔
83
        const promises = [];
1✔
84
        for (let i = 0; i < childrenTiles.length; i++) {
1✔
85
            promises.push(
1✔
86
                requestNewTile(context.view, context.scheduler, layer, childrenTiles[i], node, false).then((tile) => {
1!
87
                    childrenTiles[i].loaded = true;
1✔
88
                    node.add(tile);
1✔
89
                    tile.updateMatrixWorld();
1✔
90
                    // TODO: remove because cannot happen?
1✔
91
                    if (node.additiveRefinement) {
1✔
92
                        context.view.notifyChange(node);
1✔
93
                    }
1✔
94
                    layer.tileset.tiles[tile.tileId].loaded = true;
1✔
95
                    layer.onTileContentLoaded(tile);
1✔
96
                }));
1✔
97
        }
1✔
98
        Promise.all(promises).then(() => {
1✔
99
            node.pendingSubdivision = false;
1✔
100
            context.view.notifyChange(node);
1✔
101
        });
1✔
102
    }
1✔
103
}
1✔
104

1✔
105
/**
1✔
106
 * Check if the node is visible in the camera.
1✔
107
 *
1✔
108
 * @param      {C3DTilesLayer} layer       node 3D tiles layer
1✔
109
 * @param      {Camera}   camera           camera
1✔
110
 * @param      {THREE.Object3D}   node             The 3d tile node to check.
1!
111
 * @param      {THREE.Matrix4}   tileMatrixWorld  The node matrix world
×
112
 * @return     {boolean}  return true if the node is visible
1!
113
 */
1✔
114
export function $3dTilesCulling(layer, camera, node, tileMatrixWorld) {
1✔
115
    // For viewer Request Volume
1✔
116
    // https://github.com/AnalyticalGraphicsInc/3d-tiles-samples/tree/master/tilesets/TilesetWithRequestVolume
1✔
117
    if (node.viewerRequestVolume && node.viewerRequestVolume.viewerRequestVolumeCulling(
1✔
118
        camera, tileMatrixWorld)) {
1✔
119
        return true;
1✔
120
    }
1✔
121

1✔
122
    // For bounding volume
1✔
123
    return !!(node.boundingVolume &&
1✔
124
        node.boundingVolume.boundingVolumeCulling(camera, tileMatrixWorld));
×
125
}
×
126

×
127
// Cleanup all 3dtiles|three.js starting from a given node n.
×
128
// n's children can be of 2 types:
×
129
//   - have a 'content' attribute -> it's a tileset and must
×
130
//     be cleaned with cleanup3dTileset()
×
131
//   - doesn't have 'content' -> it's a raw Object3D object,
×
132
//     and must be cleaned with _cleanupObject3D()
×
133
function cleanup3dTileset(layer, n, depth = 0) {
×
134
    // If this layer is not using additive refinement, we can only
×
135
    // clean a tile if all its neighbours are cleaned as well because
×
136
    // a tile can only be in 2 states:
×
137
    //   - displayed and no children displayed
×
138
    //   - hidden and all of its children displayed
×
139
    // So here we implement a conservative measure: if T is cleanable
×
140
    // we actually only clean its children tiles.
×
141
    const canCleanCompletely = n.additiveRefinement || depth > 0;
×
142

×
143
    for (let i = 0; i < n.children.length; i++) {
×
144
        // skip non-tiles elements
×
145
        if (!n.children[i].content) {
×
146
            if (canCleanCompletely) {
×
147
                ObjectRemovalHelper.removeChildrenAndCleanupRecursively(n.children[i].layer, n.children[i]);
×
148
            }
×
149
        } else {
×
150
            cleanup3dTileset(layer, n.children[i], depth + 1);
1✔
151
        }
1✔
152
    }
1✔
153

1✔
154

1✔
155
    if (canCleanCompletely) {
1✔
156
        if (n.dispose) {
1!
157
            n.dispose();
1✔
158
        }
1✔
159
        delete n.content;
1✔
160
        layer.tileset.tiles[n.tileId].loaded = false;
1✔
161
        n.remove(...n.children);
1✔
162

1✔
163
        // and finally remove from parent
1✔
164
        if (depth == 0 && n.parent) {
1✔
165
            n.parent.remove(n);
1!
166
        }
×
UNCOV
167
    } else {
×
168
        const tiles = getChildTiles(n);
1✔
169
        n.remove(...tiles);
×
170
    }
×
171
}
×
172

×
173
// this is a layer
×
NEW
174
export function pre3dTilesUpdate(context) {
×
175
    if (!this.visible) {
×
176
        return [];
×
177
    }
×
NEW
178
    this.scale = context.camera._preSSE;
×
179

×
180
    // Elements removed are added in the layer._cleanableTiles list.
×
181
    // Since we simply push in this array, the first item is always
×
182
    // the oldest one.
×
183
    const now = Date.now();
×
184
    if (this._cleanableTiles.length
×
185
        && (now - this._cleanableTiles[0].cleanableSince) > this.cleanupDelay) {
1✔
186
        // Make sure we don't clean root tile
1✔
187
        this.root.cleanableSince = undefined;
1✔
188

1✔
189
        let i = 0;
7✔
190
        for (; i < this._cleanableTiles.length; i++) {
7✔
191
            const elt = this._cleanableTiles[i];
7✔
192
            if ((now - elt.cleanableSince) > this.cleanupDelay) {
7✔
193
                cleanup3dTileset(this, elt);
3✔
194
            } else {
3✔
195
                // later entries are younger
3✔
196
                break;
3✔
197
            }
3✔
198
        }
7✔
199
        // remove deleted elements from _cleanableTiles
4✔
200
        this._cleanableTiles.splice(0, i);
4✔
201
    }
2✔
202

2✔
203
    return [this.root];
2✔
204
}
2✔
205

2✔
206
const boundingVolumeBox = new THREE.Box3();
4✔
207
const boundingVolumeSphere = new THREE.Sphere();
4✔
208
export function computeNodeSSE(camera, node) {
4✔
209
    node.distance = 0;
4✔
210
    if (node.boundingVolume.initialVolumeType === C3DTilesBoundingVolumeTypes.box) {
4✔
211
        boundingVolumeBox.copy(node.boundingVolume.volume);
4✔
212
        boundingVolumeBox.applyMatrix4(node.matrixWorld);
7!
213
        node.distance = boundingVolumeBox.distanceToPoint(camera.camera3D.position);
7!
214
    } else if (node.boundingVolume.initialVolumeType === C3DTilesBoundingVolumeTypes.sphere ||
1✔
215
               node.boundingVolume.initialVolumeType === C3DTilesBoundingVolumeTypes.region) {
2✔
216
        boundingVolumeSphere.copy(node.boundingVolume.volume);
2✔
217
        boundingVolumeSphere.applyMatrix4(node.matrixWorld);
2✔
218
        // TODO: see https://github.com/iTowns/itowns/issues/800
2✔
219
        node.distance = Math.max(0.0,
1✔
220
            boundingVolumeSphere.distanceToPoint(camera.camera3D.position));
1✔
221
    } else {
1✔
222
        return Infinity;
1✔
223
    }
1✔
224
    if (node.distance === 0) {
1✔
225
        // This test is needed in case geometricError = distance = 0
1✔
226
        return Infinity;
1✔
227
    }
1✔
228
    return camera._preSSE * (node.geometricError / node.distance);
1✔
229
}
×
230

×
231
export function init3dTilesLayer(view, scheduler, layer, rootTile) {
×
232
    return requestNewTile(view, scheduler, layer, rootTile, undefined, true).then(
1✔
233
        (tile) => {
1✔
234
            layer.object3d.add(tile);
1✔
235
            tile.updateMatrixWorld();
1✔
236
            layer.tileset.tiles[tile.tileId].loaded = true;
1✔
237
            layer.root = tile;
1✔
238
            layer.onTileContentLoaded(tile);
1✔
239
        });
1✔
240
}
1✔
241

1✔
242
function setDisplayed(node, display) {
1✔
243
    // The geometry of the tile is not in node, but in node.content
1✔
244
    // To change the display state, we change node.content.visible instead of
1✔
245
    // node.material.visible
1✔
246
    if (node.content) {
1✔
247
        node.content.visible = display;
1!
248
    }
×
249
}
×
250

×
251
function markForDeletion(layer, elt) {
1!
252
    if (!elt.cleanableSince) {
1✔
253
        elt.cleanableSince = Date.now();
1!
254
        layer._cleanableTiles.push(elt);
1!
255
    }
1✔
256
}
1✔
257

1✔
258
/**
1✔
259
 * This funcion builds the method to update 3d tiles node.
1✔
260
 *
1✔
261
 * The returned method checks the 3d tile visibility with `cullingTest` function.
1!
262
 * It subdivises visible node if `subdivisionTest` return `true`.
1!
263
 *
×
264
 * @param      {Function}  [cullingTest=$3dTilesCulling]                 The culling test method.
1✔
265
 * @param      {Function}  [subdivisionTest=$3dTilesSubdivisionControl]  The subdivision test method.
1!
266
 * @return     {Function}    { description_of_the_return_value }
1!
267
 */
×
268
export function process3dTilesNode(cullingTest = $3dTilesCulling, subdivisionTest = $3dTilesSubdivisionControl) {
1✔
269
    return function _process3dTilesNodes(context, layer, node) {
1✔
270
        // early exit if parent's subdivision is in progress
1✔
271
        if (node.parent.pendingSubdivision && !node.parent.additiveRefinement) {
1✔
272
            node.visible = false;
1!
273
            return undefined;
1✔
274
        }
1✔
275

1✔
276
        // do proper culling
1!
277
        const isVisible = cullingTest ? (!cullingTest(layer, context.camera, node, node.matrixWorld)) : true;
×
278
        node.visible = isVisible;
×
279

×
280
        if (isVisible) {
×
281
            if (node.cleanableSince) {
1!
282
                layer._cleanableTiles.splice(layer._cleanableTiles.indexOf(node), 1);
1✔
283
                node.cleanableSince = undefined;
1✔
284
            }
1✔
285

1✔
286
            let returnValue;
1✔
287
            if (node.pendingSubdivision || subdivisionTest(context, layer, node)) {
1✔
288
                subdivideNode(context, layer, node, cullingTest);
1✔
289
                // display iff children aren't ready
1✔
290
                setDisplayed(node, node.pendingSubdivision || node.additiveRefinement);
1✔
291
                returnValue = getChildTiles(node);
1!
292
            } else {
1✔
293
                setDisplayed(node, true);
1!
294

×
295
                for (const n of getChildTiles(node)) {
1✔
296
                    n.visible = false;
1✔
297
                    markForDeletion(layer, n);
1✔
298
                }
1✔
299
            }
1✔
300
            return returnValue;
1✔
301
        }
1✔
302

1✔
303
        markForDeletion(layer, node);
1✔
304
    };
1✔
305
}
1✔
306

1✔
307
/**
1✔
308
 *
1✔
309
 *
1✔
310
 * the method returns true if the `node` should be subivised.
1✔
311
 *
1✔
312
 * @param      {object}   context  The current context
1✔
313
 * @param      {Camera}   context.camera  The current camera
1✔
314
 * @param      {C3DTilesLayer}   layer  The 3d tile layer
1✔
315
 * @param      {THREE.Object3D}  node  The 3d tile node
1✔
316
 * @return     {boolean}
1✔
317
 */
1✔
318
export function $3dTilesSubdivisionControl(context, layer, node) {
1✔
319
    if (layer.tileset.tiles[node.tileId].children === undefined) {
1✔
320
        return false;
1✔
321
    }
1✔
322
    if (layer.tileset.tiles[node.tileId].isTileset) {
1✔
323
        return true;
1✔
324
    }
1✔
325
    const sse = computeNodeSSE(context.camera, node);
1✔
326
    return sse > layer.sseThreshold;
1✔
327
}
1✔
328

329

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