Skip to content

Commit c21f115

Browse files
ENH: Improve camera clipping
- Change layer manager to add one camera per renderer and reset clipping per renderer - Add tests for camera clipping - Add utility function to take screenshots from RW to avoid unwanted behavior with existing VTK vtkWindowToImageFilter
1 parent 17548f6 commit c21f115

13 files changed

+403
-138
lines changed

Docs/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ introduction
1313
getting_started
1414
displayable_manager_reminders
1515
extension_architecture
16+
renderers_and_cameras
1617
examples
1718
api/library_root
1819
```

Docs/renderers_and_cameras.md

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# Renderers and Cameras
2+
3+
## First renderer
4+
5+
The LayerDM library creates and manages a list of renderers. The first renderer is considered not managed by the
6+
library for compatibility reasons with existing 3D Slicer behaviors.
7+
8+
The first renderer has an active camera and the active camera is supposed to be managed by a dedicated pipeline
9+
(usually a camera pipeline).
10+
11+
## Managed renderer lifetime
12+
13+
Pipelines will be grouped to renderers given their `GetRenderOrder` and `GetCustomCamera` values.
14+
15+
If the renderer order is 0 and custom camera is set to default (nullptr), the pipelines will be be set to the default
16+
renderer. Otherwise, new renderers, managed by the library will be created and the pipelines will be attached to them.
17+
18+
When new pipelines are added and removed from the DM, the created renderers will be updated.
19+
20+
## Managed renderers and default camera
21+
22+
If pipelines don't return any `GetCustomCamera` instances, they will use the default camera.
23+
24+
The default camera is updating following two strategies:
25+
26+
- Default strategy (3D camera)
27+
- Slice view strategy
28+
29+
In the default synchronization strategy, when the first renderer's camera is updated, the default camera is updated
30+
to follow the first camera.
31+
32+
For the slice view, the synchronization synchronizes the default camera to the current slice node view configuration.
33+
The Slice view's renderer 0 camera is not configured nor modified during camera changes.
34+
35+
To simplify adding new pipelines, the sync adjusts the default camera to render in parallel projection in the correct
36+
orientation with respect to the current node configuration.
37+
38+
Clipping range is configured to show all actors attached to the default camera.
39+
If clipping is required, then it should be done inside the specific pipeline.
40+
41+
## Unmanaged cameras
42+
43+
Pipelines with a `GetCustomCamera` cameras are managed by their pipelines or pipeline logics.
44+
45+
The camera update can be made to update when the default camera is updated using the `OnDefaultCameraModified` method.
46+
47+
## Reset clipping range
48+
49+
To simplify integration, before request render is triggered, the cameras clipping range will be updated to encompass
50+
the visible actors as present in the different layers regardless of managed or unmanaged cameras.

LayerDM/MRMLDM/vtkMRMLLayerDMCameraSynchronizer.cxx

Lines changed: 45 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,15 @@
1616
// STL includes
1717
#include <array>
1818

19+
/// \brief Abstract class for the camera strategies.
20+
/// Implements only the reset camera clipping range logic for the layer cameras.
21+
/// Other methods are expected to be implemented by deriving classes.
1922
class CameraSynchronizeStrategy
2023
{
2124
public:
22-
explicit CameraSynchronizeStrategy(const vtkSmartPointer<vtkCamera>& camera)
25+
explicit CameraSynchronizeStrategy(const vtkSmartPointer<vtkCamera>& camera, std::function<void()> invokeModifiedEvent)
2326
: m_camera(camera)
27+
, m_invokeModifiedEvent{ std::move(invokeModifiedEvent) }
2428
{
2529
}
2630
virtual ~CameraSynchronizeStrategy() = default;
@@ -29,26 +33,25 @@ class CameraSynchronizeStrategy
2933
protected:
3034
vtkSmartPointer<vtkCamera> m_camera;
3135
vtkNew<vtkMRMLLayerDMObjectEventObserver> m_eventObserver;
36+
std::function<void()> m_invokeModifiedEvent;
3237
};
3338

39+
/// Default camera synchronization consists in updating the camera when the first renderer active camera is updated.
3440
class DefaultCameraSynchronizeStrategy : public CameraSynchronizeStrategy
3541
{
3642
public:
37-
explicit DefaultCameraSynchronizeStrategy(const vtkSmartPointer<vtkCamera>& camera, vtkRenderer* renderer)
38-
: CameraSynchronizeStrategy(camera)
43+
explicit DefaultCameraSynchronizeStrategy(const vtkSmartPointer<vtkCamera>& camera, vtkRenderer* renderer, std::function<void()> invokeModifiedEvent)
44+
: CameraSynchronizeStrategy(camera, std::move(invokeModifiedEvent))
3945
, m_renderer(renderer)
4046
{
4147
this->m_eventObserver->SetUpdateCallback(
4248
[this](vtkObject* object)
4349
{
44-
if (object == this->m_observedCamera)
45-
{
46-
this->UpdateCamera();
47-
}
4850
if (object == this->m_renderer)
4951
{
5052
this->ObserveActiveCamera();
5153
}
54+
this->UpdateCamera();
5255
});
5356

5457
this->m_eventObserver->UpdateObserver(nullptr, this->m_renderer, vtkCommand::ActiveCameraEvent);
@@ -57,11 +60,13 @@ class DefaultCameraSynchronizeStrategy : public CameraSynchronizeStrategy
5760

5861
void UpdateCamera() override
5962
{
60-
if (this->m_observedCamera)
63+
if (!this->m_observedCamera)
6164
{
62-
this->m_camera->DeepCopy(this->m_observedCamera);
63-
this->m_camera->Modified();
65+
return;
6466
}
67+
68+
this->m_camera->DeepCopy(this->m_observedCamera);
69+
this->m_invokeModifiedEvent();
6570
}
6671

6772
private:
@@ -76,18 +81,26 @@ class DefaultCameraSynchronizeStrategy : public CameraSynchronizeStrategy
7681

7782
this->m_eventObserver->UpdateObserver(this->m_observedCamera, camera);
7883
this->m_observedCamera = camera;
79-
this->UpdateCamera();
8084
}
8185

8286
vtkWeakPointer<vtkRenderer> m_renderer;
8387
vtkWeakPointer<vtkCamera> m_observedCamera;
8488
};
8589

90+
/// Synchronizes the default camera to the current slice node view configuration.
91+
/// The Slice renderer 0 camera is not configured nor modified during camera changes.
92+
/// All of its actors are set to render in 2D.
93+
///
94+
/// To simplify adding new pipelines, the sync adjusts the default camera to render in parallel projection in the correct
95+
/// orientation with respect to the current node configuration.
96+
///
97+
/// Clipping range is configured to show all actors attached to the default camera.
98+
/// If clipping is required, then it should be done inside the specific pipeline.
8699
class SliceViewCameraSynchronizeStrategy : public CameraSynchronizeStrategy
87100
{
88101
public:
89-
explicit SliceViewCameraSynchronizeStrategy(const vtkSmartPointer<vtkCamera>& camera, vtkMRMLSliceNode* sliceNode)
90-
: CameraSynchronizeStrategy(camera)
102+
explicit SliceViewCameraSynchronizeStrategy(const vtkSmartPointer<vtkCamera>& camera, vtkMRMLSliceNode* sliceNode, std::function<void()> invokeModifiedEvent)
103+
: CameraSynchronizeStrategy(camera, std::move(invokeModifiedEvent))
91104
, m_sliceNode{ sliceNode }
92105
{
93106
this->m_eventObserver->SetUpdateCallback(
@@ -142,6 +155,7 @@ class SliceViewCameraSynchronizeStrategy : public CameraSynchronizeStrategy
142155
vtkMath::Cross(vRight.data(), vUp.data(), normal.data());
143156
double position[3] = { viewCenterRAS[0] + normal[0] * d, viewCenterRAS[1] + normal[1] * d, viewCenterRAS[2] + normal[2] * d };
144157
this->m_camera->SetPosition(position);
158+
this->m_invokeModifiedEvent();
145159
}
146160

147161
private:
@@ -199,13 +213,29 @@ void vtkMRMLLayerDMCameraSynchronizer::UpdateStrategy()
199213
return;
200214
}
201215

216+
const auto invokeModifiedEvent = [this]
217+
{
218+
if (this->m_isBlocked)
219+
{
220+
return;
221+
}
222+
this->Modified();
223+
};
224+
202225
if (auto sliceNode = vtkMRMLSliceNode::SafeDownCast(this->m_viewNode))
203226
{
204-
this->m_syncStrategy = std::make_unique<SliceViewCameraSynchronizeStrategy>(this->m_defaultCamera, sliceNode);
227+
this->m_syncStrategy = std::make_unique<SliceViewCameraSynchronizeStrategy>(this->m_defaultCamera, sliceNode, invokeModifiedEvent);
205228
}
206229
else
207230
{
208-
this->m_syncStrategy = std::make_unique<DefaultCameraSynchronizeStrategy>(this->m_defaultCamera, this->m_renderer);
231+
this->m_syncStrategy = std::make_unique<DefaultCameraSynchronizeStrategy>(this->m_defaultCamera, this->m_renderer, invokeModifiedEvent);
209232
}
210233
this->m_syncStrategy->UpdateCamera();
211234
}
235+
236+
bool vtkMRMLLayerDMCameraSynchronizer::SetBlocked(bool isBlocked)
237+
{
238+
bool wasBlocked = this->m_isBlocked;
239+
m_isBlocked = isBlocked;
240+
return wasBlocked;
241+
}

LayerDM/MRMLDM/vtkMRMLLayerDMCameraSynchronizer.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@ class VTK_SLICER_LAYERDM_MODULE_MRMLDISPLAYABLEMANAGER_EXPORT vtkMRMLLayerDMCame
3838
/// The renderer active camera will be monitored for change when applicable (for instance 3D views).
3939
void SetRenderer(vtkRenderer* renderer);
4040

41+
/// Set camera update blocked.
42+
/// @return previous blocked state.
43+
bool SetBlocked(bool isBlocked);
44+
4145
protected:
4246
vtkMRMLLayerDMCameraSynchronizer();
4347
~vtkMRMLLayerDMCameraSynchronizer() override;
@@ -50,4 +54,5 @@ class VTK_SLICER_LAYERDM_MODULE_MRMLDISPLAYABLEMANAGER_EXPORT vtkMRMLLayerDMCame
5054
vtkWeakPointer<vtkRenderer> m_renderer;
5155
vtkWeakPointer<vtkMRMLAbstractViewNode> m_viewNode;
5256
std::unique_ptr<CameraSynchronizeStrategy> m_syncStrategy;
57+
bool m_isBlocked{ false };
5358
};

LayerDM/MRMLDM/vtkMRMLLayerDMLayerManager.cxx

Lines changed: 24 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
#include "vtkMRMLLayerDMPipelineI.h"
55

66
// VTK includes
7-
#include <vtkBoundingBox.h>
87
#include <vtkCamera.h>
98
#include <vtkObjectFactory.h>
109
#include <vtkRenderWindow.h>
@@ -74,21 +73,6 @@ void vtkMRMLLayerDMLayerManager::RemovePipeline(vtkMRMLLayerDMPipelineI* pipelin
7473
this->UpdateLayers();
7574
}
7675

77-
void vtkMRMLLayerDMLayerManager::ResetCameraClippingRange() const
78-
{
79-
// Reset first renderer clipping range
80-
if (auto defaultRenderer = this->GetDefaultRenderer())
81-
{
82-
defaultRenderer->ResetCameraClippingRange();
83-
}
84-
85-
// Reset the managed renderers grouped by common cameras
86-
for (const auto& pair : m_cameraRendererMap)
87-
{
88-
this->ResetRenderersCameraClippingRange(pair.second, this->ComputeRenderersVisibleBounds(pair.second));
89-
}
90-
}
91-
9276
void vtkMRMLLayerDMLayerManager::SetRenderWindow(vtkRenderWindow* renderWindow)
9377
{
9478
if (this->m_renderWindow == renderWindow)
@@ -101,14 +85,17 @@ void vtkMRMLLayerDMLayerManager::SetRenderWindow(vtkRenderWindow* renderWindow)
10185
this->UpdateLayers();
10286
}
10387

104-
void vtkMRMLLayerDMLayerManager::SetDefaultCamera(const vtkSmartPointer<vtkCamera>& camera)
88+
void vtkMRMLLayerDMLayerManager::OnDefaultCameraModified(vtkCamera* defaultCamera) const
10589
{
106-
if (this->m_defaultCamera == camera)
90+
if (!defaultCamera)
10791
{
10892
return;
10993
}
110-
this->m_defaultCamera = camera;
111-
this->UpdateLayers();
94+
95+
for (const auto& camera : this->m_managedCameras)
96+
{
97+
camera->DeepCopy(defaultCamera);
98+
}
11299
}
113100

114101
vtkMRMLLayerDMLayerManager::vtkMRMLLayerDMLayerManager()
@@ -157,22 +144,17 @@ void vtkMRMLLayerDMLayerManager::AddMissingLayers()
157144
}
158145
}
159146

160-
std::array<double, 6> vtkMRMLLayerDMLayerManager::ComputeRenderersVisibleBounds(const std::set<vtkWeakPointer<vtkRenderer>>& renderers)
147+
void vtkMRMLLayerDMLayerManager::ResetCameraClippingRange() const
161148
{
162-
vtkBoundingBox bbox;
163-
164-
for (const auto& renderer : renderers)
149+
if (const auto defaultRenderer = this->GetDefaultRenderer())
165150
{
166-
if (!renderer)
167-
{
168-
continue;
169-
}
170-
bbox.AddBounds(renderer->ComputeVisiblePropBounds());
151+
defaultRenderer->ResetCameraClippingRange();
171152
}
172153

173-
std::array<double, 6> bounds{};
174-
bbox.GetBounds(bounds.data());
175-
return bounds;
154+
for (const auto& renderer : m_renderers)
155+
{
156+
renderer->ResetCameraClippingRange();
157+
}
176158
}
177159

178160
bool vtkMRMLLayerDMLayerManager::ContainsLayerKey(const LayerKey& key)
@@ -189,12 +171,12 @@ std::uintptr_t vtkMRMLLayerDMLayerManager::GetCameraId(vtkCamera* camera)
189171
return reinterpret_cast<std::uintptr_t>(camera);
190172
}
191173

192-
vtkCamera* vtkMRMLLayerDMLayerManager::GetCameraForLayer(const LayerKey& key, const std::set<vtkWeakPointer<vtkMRMLLayerDMPipelineI>>& pipelines) const
174+
vtkCamera* vtkMRMLLayerDMLayerManager::GetCameraForLayer(const LayerKey& key, const std::set<vtkWeakPointer<vtkMRMLLayerDMPipelineI>>& pipelines)
193175
{
194176
auto cameraId = std::get<1>(key);
195177
if (cameraId == 0)
196178
{
197-
return this->m_defaultCamera;
179+
return nullptr;
198180
}
199181

200182
for (const auto& pipeline : pipelines)
@@ -290,18 +272,6 @@ void vtkMRMLLayerDMLayerManager::RemoveRenderer(const vtkSmartPointer<vtkRendere
290272
this->m_renderers.erase(std::find(this->m_renderers.begin(), this->m_renderers.end(), renderer));
291273
}
292274

293-
void vtkMRMLLayerDMLayerManager::ResetRenderersCameraClippingRange(const std::set<vtkWeakPointer<vtkRenderer>>& renderers, const std::array<double, 6>& bounds)
294-
{
295-
for (const auto& renderer : renderers)
296-
{
297-
if (!renderer)
298-
{
299-
continue;
300-
}
301-
renderer->ResetCameraClippingRange(bounds.data());
302-
}
303-
}
304-
305275
void vtkMRMLLayerDMLayerManager::SynchronizePipelineRenderers()
306276
{
307277
for (const auto& pair : m_pipelineLayers)
@@ -368,18 +338,23 @@ void vtkMRMLLayerDMLayerManager::UpdateRendererCamera()
368338
{
369339
// Set the camera for the managed renderers
370340
// Layer 0 is unmanaged and its camera is left unchanged by the layer manager
371-
// Pipelines with no explicit camera map to the default camera
341+
// Pipelines with no explicit camera map to a new camera synchronized to the default camera on update
372342
// Pipelines with custom camera are grouped and use their cameras
373-
this->m_cameraRendererMap.clear();
343+
this->m_managedCameras.clear();
374344

375345
int iRenderer = -1;
376346
for (const auto& pair : m_pipelineLayers)
377347
{
378348
if (iRenderer >= 0 && iRenderer < this->GetNumberOfRenderers())
379349
{
380350
auto camera = this->GetCameraForLayer(pair.first, pair.second);
351+
if (!camera)
352+
{
353+
auto newCam = vtkSmartPointer<vtkCamera>::New();
354+
this->m_managedCameras.emplace_back(newCam);
355+
camera = newCam;
356+
}
381357
this->m_renderers[iRenderer]->SetActiveCamera(camera);
382-
this->m_cameraRendererMap[camera].emplace(this->m_renderers[iRenderer]);
383358
}
384359

385360
iRenderer++;

0 commit comments

Comments
 (0)