The render loop uses a render-on-demand pattern. requestAnimationFrame runs every frame, but renderer.render() only fires when the dirty flag is set — so an idle shape consumes no GPU time.
private startLoop(): void {
const loop = () => {
if (this.disposed) return;
this.animFrameId = requestAnimationFrame(loop);
this.orbit.update();
if (this.dirty) {
this.renderer.render(this.scene, this.camera);
this.dirty = false;
}
};
loop();
}orbit.update() still runs every frame because OrbitControls damping needs continuous updates when enabled. Only the render call is gated.
| Event | Location |
|---|---|
| OrbitControls change event | ThreeDEngine.ts:71 |
| Gizmo drag (change event) | ThreeDEngine.ts:107 |
| selectObject() | ThreeDEngine.ts:173 |
| addPrimitive() | ThreeDEngine.ts:187 |
| removeObject() | ThreeDEngine.ts:233 |
| setGizmoMode() | ThreeDEngine.ts:238 |
| updateTransform() | ThreeDEngine.ts:243 |
| updateMaterial() | ThreeDEngine.ts:248 |
| updateGeometry() | ThreeDEngine.ts:253 |
| updateEnvironment() | ThreeDEngine.ts:263 |
| resize() | ThreeDEngine.ts:312 |
| setInteracting() on ThreeDShapeElement | ThreeDShapeElement.ts:55 |
| markDirty() called directly | ThreeDEngine.ts:153-154 |