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

iTowns / itowns / 13074098098

31 Jan 2025 01:51PM UTC coverage: 87.15% (-0.06%) from 87.208%
13074098098

Pull #2494

github

web-flow
Merge a3f6d056a into 6d12fde05
Pull Request #2494: Fix: PointCloud Layer use user elevation range value if present

2766 of 3677 branches covered (75.22%)

Branch coverage included in aggregate %.

17 of 17 new or added lines in 5 files covered. (100.0%)

30 existing lines in 3 files now uncovered.

25162 of 28369 relevant lines covered (88.7%)

1149.28 hits per line

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

81.58
/src/Core/MainLoop.js
1
import { EventDispatcher } from 'three';
1✔
2

1✔
3
export const RENDERING_PAUSED = 0;
1✔
4
export const RENDERING_SCHEDULED = 1;
1✔
5

1✔
6
/**
1✔
7
 * MainLoop's update events list that are fired using
1✔
8
 * {@link View#execFrameRequesters}.
1✔
9
 *
1✔
10
 * @property UPDATE_START {string} fired at the start of the update
1✔
11
 * @property BEFORE_CAMERA_UPDATE {string} fired before the camera update
1✔
12
 * @property AFTER_CAMERA_UPDATE {string} fired after the camera update
1✔
13
 * @property BEFORE_LAYER_UPDATE {string} fired before the layer update
1✔
14
 * @property AFTER_LAYER_UPDATE {string} fired after the layer update
1✔
15
 * @property BEFORE_RENDER {string} fired before the render
1✔
16
 * @property AFTER_RENDER {string} fired after the render
1✔
17
 * @property UPDATE_END {string} fired at the end of the update
1✔
18
 */
1✔
19

1✔
20
export const MAIN_LOOP_EVENTS = {
1✔
21
    UPDATE_START: 'update_start',
1✔
22
    BEFORE_CAMERA_UPDATE: 'before_camera_update',
1✔
23
    AFTER_CAMERA_UPDATE: 'after_camera_update',
1✔
24
    BEFORE_LAYER_UPDATE: 'before_layer_update',
1✔
25
    AFTER_LAYER_UPDATE: 'after_layer_update',
1✔
26
    BEFORE_RENDER: 'before_render',
1✔
27
    AFTER_RENDER: 'after_render',
1✔
28
    UPDATE_END: 'update_end',
1✔
29
};
1✔
30

1✔
31
function updateElements(context, geometryLayer, elements) {
6✔
32
    if (!elements) {
6!
33
        return;
×
34
    }
×
35
    for (const element of elements) {
6✔
36
        // update element
4✔
37
        // TODO find a way to notify attachedLayers when geometryLayer deletes some elements
4✔
38
        // and then update Debug.js:addGeometryLayerDebugFeatures
4✔
39
        const newElementsToUpdate = geometryLayer.update(context, geometryLayer, element);
4✔
40

4✔
41
        const sub = geometryLayer.getObjectToUpdateForAttachedLayers(element);
4✔
42

4✔
43
        if (sub) {
4!
44
            if (sub.element) {
×
45
                if (__DEBUG__) {
×
46
                    if (!(sub.element.isObject3D)) {
×
47
                        throw new Error(`
×
48
                            Invalid object for attached layer to update.
×
49
                            Must be a THREE.Object and have a THREE.Material`);
×
50
                    }
×
51
                }
×
52
                // update attached layers
×
53
                for (const attachedLayer of geometryLayer.attachedLayers) {
×
54
                    if (attachedLayer.ready) {
×
55
                        attachedLayer.update(context, attachedLayer, sub.element, sub.parent);
×
56
                    }
×
57
                }
×
58
            } else if (sub.elements) {
×
59
                for (let i = 0; i < sub.elements.length; i++) {
×
60
                    if (!(sub.elements[i].isObject3D)) {
×
61
                        throw new Error(`
×
62
                            Invalid object for attached layer to update.
×
63
                            Must be a THREE.Object and have a THREE.Material`);
×
64
                    }
×
65
                    // update attached layers
×
66
                    for (const attachedLayer of geometryLayer.attachedLayers) {
×
67
                        if (attachedLayer.ready) {
×
68
                            attachedLayer.update(context, attachedLayer, sub.elements[i], sub.parent);
×
69
                        }
×
70
                    }
×
71
                }
×
72
            }
×
73
        }
×
74
        updateElements(context, geometryLayer, newElementsToUpdate);
4✔
75
    }
4✔
76
}
6✔
77

1✔
78
function filterChangeSources(updateSources, geometryLayer) {
2✔
79
    let fullUpdate = false;
2✔
80
    const filtered = new Set();
2✔
81
    updateSources.forEach((src) => {
2✔
82
        if (src === geometryLayer || src.isCamera) {
2!
83
            geometryLayer.info.clear();
2✔
84
            fullUpdate = true;
2✔
85
        } else if (src.layer === geometryLayer) {
2!
UNCOV
86
            filtered.add(src);
×
UNCOV
87
        }
×
88
    });
2✔
89
    return fullUpdate ? new Set([geometryLayer]) : filtered;
2!
90
}
2✔
91

1✔
92

1✔
93
class MainLoop extends EventDispatcher {
1✔
94
    #needsRedraw = false;
44✔
95
    #updateLoopRestarted = true;
44✔
96
    #lastTimestamp = 0;
44✔
97
    constructor(scheduler, engine) {
44✔
98
        super();
44✔
99
        this.renderingState = RENDERING_PAUSED;
44✔
100
        this.scheduler = scheduler;
44✔
101
        this.gfxEngine = engine; // TODO: remove me
44✔
102
    }
44✔
103

44✔
104
    scheduleViewUpdate(view, forceRedraw) {
44✔
105
        this.#needsRedraw |= forceRedraw;
194✔
106

194✔
107
        if (this.renderingState !== RENDERING_SCHEDULED) {
194✔
108
            this.renderingState = RENDERING_SCHEDULED;
39✔
109

39✔
110
            if (__DEBUG__) {
39✔
111
                document.title += ' ⌛';
39✔
112
            }
39✔
113

39✔
114
            // TODO Fix asynchronization between xr and MainLoop render loops.
39✔
115
            // WebGLRenderer#setAnimationLoop must be used for WebXR projects.
39✔
116
            // (see WebXR#initializeWebXR).
39✔
117
            if (!this.gfxEngine.renderer.xr.isPresenting) {
39✔
118
                requestAnimationFrame((timestamp) => { this.step(view, timestamp); });
39✔
119
            }
39✔
120
        }
39✔
121
    }
194✔
122

44✔
123
    #update(view, updateSources, dt) {
44✔
124
        const context = {
2✔
125
            camera: view.camera,
2✔
126
            engine: this.gfxEngine,
2✔
127
            scheduler: this.scheduler,
2✔
128
            view,
2✔
129
        };
2✔
130

2✔
131
        // replace layer with their parent where needed
2✔
132
        updateSources.forEach((src) => {
2✔
133
            const layer = src.layer || src;
2✔
134
            if (layer.isLayer && layer.parent) {
2!
UNCOV
135
                updateSources.add(layer.parent);
×
UNCOV
136
            }
×
137
        });
2✔
138

2✔
139
        for (const geometryLayer of view.getLayers((x, y) => !y)) {
2✔
140
            context.geometryLayer = geometryLayer;
2✔
141
            if (geometryLayer.ready && geometryLayer.visible && !geometryLayer.frozen) {
2✔
142
                view.execFrameRequesters(MAIN_LOOP_EVENTS.BEFORE_LAYER_UPDATE, dt, this.#updateLoopRestarted, geometryLayer);
2✔
143

2✔
144
                // Filter updateSources that are relevant for the geometryLayer
2✔
145
                const srcs = filterChangeSources(updateSources, geometryLayer);
2✔
146
                if (srcs.size > 0) {
2✔
147
                    // pre update attached layer
2✔
148
                    for (const attachedLayer of geometryLayer.attachedLayers) {
2!
UNCOV
149
                        if (attachedLayer.ready && attachedLayer.preUpdate) {
×
UNCOV
150
                            attachedLayer.preUpdate(context, srcs);
×
151
                        }
×
152
                    }
×
153
                    // `preUpdate` returns an array of elements to update
2✔
154
                    const elementsToUpdate = geometryLayer.preUpdate(context, srcs);
2✔
155
                    // `update` is called in `updateElements`.
2✔
156
                    updateElements(context, geometryLayer, elementsToUpdate);
2✔
157
                    // `postUpdate` is called when this geom layer update process is finished
2✔
158
                    geometryLayer.postUpdate(context, geometryLayer, updateSources);
2✔
159
                }
2✔
160

2✔
161
                // Clear the cache of expired resources
2✔
162

2✔
163
                view.execFrameRequesters(MAIN_LOOP_EVENTS.AFTER_LAYER_UPDATE, dt, this.#updateLoopRestarted, geometryLayer);
2✔
164
            }
2✔
165
        }
2✔
166
    }
2✔
167

44✔
168
    step(view, timestamp) {
44✔
169
        const dt = timestamp - this.#lastTimestamp;
2✔
170
        view._executeFrameRequestersRemovals();
2✔
171

2✔
172
        view.execFrameRequesters(MAIN_LOOP_EVENTS.UPDATE_START, dt, this.#updateLoopRestarted);
2✔
173

2✔
174
        const willRedraw = this.#needsRedraw;
2✔
175
        this.#lastTimestamp = timestamp;
2✔
176

2✔
177
        // Reset internal state before calling _update (so future calls to View.notifyChange()
2✔
178
        // can properly change it)
2✔
179
        this.#needsRedraw = false;
2✔
180
        this.renderingState = RENDERING_PAUSED;
2✔
181
        const updateSources = new Set(view._changeSources);
2✔
182
        view._changeSources.clear();
2✔
183

2✔
184
        // update camera
2✔
185
        const dim = this.gfxEngine.getWindowSize();
2✔
186

2✔
187
        view.execFrameRequesters(MAIN_LOOP_EVENTS.BEFORE_CAMERA_UPDATE, dt, this.#updateLoopRestarted);
2✔
188
        view.camera.update(dim.x, dim.y);
2✔
189
        view.execFrameRequesters(MAIN_LOOP_EVENTS.AFTER_CAMERA_UPDATE, dt, this.#updateLoopRestarted);
2✔
190

2✔
191
        // Disable camera's matrix auto update to make sure the camera's
2✔
192
        // world matrix is never updated mid-update.
2✔
193
        // Otherwise inconsistencies can appear because object visibility
2✔
194
        // testing and object drawing could be performed using different
2✔
195
        // camera matrixWorld.
2✔
196
        // Note: this is required at least because WEBGLRenderer calls
2✔
197
        // camera.updateMatrixWorld()
2✔
198
        const oldAutoUpdate = view.camera3D.matrixAutoUpdate;
2✔
199
        view.camera3D.matrixAutoUpdate = false;
2✔
200

2✔
201
        // update data-structure
2✔
202
        this.#update(view, updateSources, dt);
2✔
203

2✔
204
        if (this.scheduler.commandsWaitingExecutionCount() == 0) {
2✔
205
            this.dispatchEvent({ type: 'command-queue-empty' });
2✔
206
        }
2✔
207

2✔
208
        // Redraw *only* if needed.
2✔
209
        // (redraws only happen when this.#needsRedraw is true, which in turn only happens when
2✔
210
        // view.notifyChange() is called with redraw=true)
2✔
211
        // As such there's no continuous update-loop, instead we use a ad-hoc update/render
2✔
212
        // mechanism.
2✔
213
        if (willRedraw) {
2✔
214
            this.#renderView(view, dt);
1✔
215
        }
1✔
216

2✔
217
        // next time, we'll consider that we've just started the loop if we are still PAUSED now
2✔
218
        this.#updateLoopRestarted = this.renderingState === RENDERING_PAUSED;
2✔
219

2✔
220
        if (__DEBUG__) {
2✔
221
            document.title = document.title.substr(0, document.title.length - 2);
2✔
222
        }
2✔
223

2✔
224
        view.camera3D.matrixAutoUpdate = oldAutoUpdate;
2✔
225

2✔
226
        view.execFrameRequesters(MAIN_LOOP_EVENTS.UPDATE_END, dt, this.#updateLoopRestarted);
2✔
227
    }
2✔
228

44✔
229
    #renderView(view, dt) {
44✔
230
        view.execFrameRequesters(MAIN_LOOP_EVENTS.BEFORE_RENDER, dt, this.#updateLoopRestarted);
1✔
231

1✔
232
        if (view.render) {
1!
UNCOV
233
            view.render();
×
234
        } else {
1✔
235
            // use default rendering method
1✔
236
            this.gfxEngine.renderView(view);
1✔
237
        }
1✔
238

1✔
239
        view.execFrameRequesters(MAIN_LOOP_EVENTS.AFTER_RENDER, dt, this.#updateLoopRestarted);
1✔
240
    }
1✔
241
}
44✔
242

1✔
243
export default MainLoop;
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