Mercurial > hg > orthanc-stone
diff Framework/Widgets/LayeredSceneWidget.cpp @ 0:351ab0da0150
initial commit
author | Sebastien Jodogne <s.jodogne@gmail.com> |
---|---|
date | Fri, 14 Oct 2016 15:34:11 +0200 |
parents | |
children | ff1e935768e7 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Framework/Widgets/LayeredSceneWidget.cpp Fri Oct 14 15:34:11 2016 +0200 @@ -0,0 +1,637 @@ +/** + * Stone of Orthanc + * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics + * Department, University Hospital of Liege, Belgium + * + * This program is free software: you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * In addition, as a special exception, the copyright holders of this + * program give permission to link the code of its release with the + * OpenSSL project's "OpenSSL" library (or with modified versions of it + * that use the same license as the "OpenSSL" library), and distribute + * the linked executables. You must obey the GNU General Public License + * in all respects for all of the code used other than "OpenSSL". If you + * modify file(s) with this exception, you may extend this exception to + * your version of the file(s), but you are not obligated to do so. If + * you do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source files + * in the program, then also delete it here. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + **/ + + +#define _USE_MATH_DEFINES // To access M_PI in Visual Studio +#include <cmath> + +#include "LayeredSceneWidget.h" + +#include "../Orthanc/Core/OrthancException.h" + +namespace OrthancStone +{ + class LayeredSceneWidget::Renderers : public boost::noncopyable + { + private: + boost::mutex mutex_; + std::vector<ILayerRenderer*> renderers_; + std::vector<bool> assigned_; + + void Assign(size_t index, + ILayerRenderer* renderer) + { + if (index >= renderers_.size()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + if (renderers_[index] != NULL) + { + delete renderers_[index]; + } + + renderers_[index] = renderer; + assigned_[index] = true; + } + + public: + Renderers(size_t size) + { + renderers_.resize(size); + assigned_.resize(size, false); + } + + ~Renderers() + { + for (size_t i = 0; i < renderers_.size(); i++) + { + Assign(i, NULL); + } + } + + static void Merge(Renderers& target, + Renderers& source) + { + boost::mutex::scoped_lock lockSource(source.mutex_); + boost::mutex::scoped_lock lockTarget(target.mutex_); + + size_t count = target.renderers_.size(); + if (count != source.renderers_.size()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_InternalError); + } + + for (size_t i = 0; i < count; i++) + { + if (source.assigned_[i]) + { + target.Assign(i, source.renderers_[i]); // Transfers ownership + source.renderers_[i] = NULL; + source.assigned_[i] = false; + } + } + } + + void SetRenderer(size_t index, + ILayerRenderer* renderer) // Takes ownership + { + boost::mutex::scoped_lock lock(mutex_); + Assign(index, renderer); + } + + bool RenderScene(CairoContext& context, + const ViewportGeometry& view) + { + boost::mutex::scoped_lock lock(mutex_); + + bool fullQuality = true; + + for (size_t i = 0; i < renderers_.size(); i++) + { + if (renderers_[i] != NULL && + !renderers_[i]->RenderLayer(context, view)) + { + return false; + } + + if (renderers_[i] != NULL && + !renderers_[i]->IsFullQuality()) + { + fullQuality = false; + } + } + + if (!fullQuality) + { + double x, y; + view.MapDisplayToScene(x, y, static_cast<double>(view.GetDisplayWidth()) / 2.0, 10); + + cairo_t *cr = context.GetObject(); + cairo_translate(cr, x, y); + cairo_arc(cr, 0, 0, 5.0 / view.GetZoom(), 0, 2 * M_PI); + cairo_set_line_width(cr, 2.0 / view.GetZoom()); + cairo_set_source_rgb(cr, 1, 1, 1); + cairo_stroke_preserve(cr); + cairo_set_source_rgb(cr, 1, 0, 0); + cairo_fill(cr); + } + + return true; + } + + void SetLayerStyle(size_t index, + const RenderStyle& style) + { + boost::mutex::scoped_lock lock(mutex_); + + if (renderers_[index] != NULL) + { + renderers_[index]->SetLayerStyle(style); + } + } + }; + + + + class LayeredSceneWidget::PendingLayers : public boost::noncopyable + { + private: + boost::mutex mutex_; + boost::condition_variable elementAvailable_; + size_t layerCount_; + std::list<size_t> queue_; + std::vector<bool> layersToUpdate_; + bool continue_; + + void TagAllLayers() + { + queue_.clear(); + + for (unsigned int i = 0; i < layerCount_; i++) + { + queue_.push_back(i); + layersToUpdate_[i] = true; + } + + if (layerCount_ != 0) + { + elementAvailable_.notify_one(); + } + } + + public: + PendingLayers() : + layerCount_(0), + continue_(true) + { + } + + void Stop() + { + continue_ = false; + elementAvailable_.notify_one(); + } + + void SetLayerCount(size_t count) + { + boost::mutex::scoped_lock lock(mutex_); + + layerCount_ = count; + layersToUpdate_.resize(count); + + TagAllLayers(); + } + + void InvalidateAllLayers() + { + boost::mutex::scoped_lock lock(mutex_); + TagAllLayers(); + } + + void InvalidateLayer(size_t layer) + { + boost::mutex::scoped_lock lock(mutex_); + + if (layer < layerCount_) + { + if (layersToUpdate_[layer]) + { + // The layer is already scheduled for update, ignore this + // invalidation + } + else + { + queue_.push_back(layer); + layersToUpdate_[layer] = true; + elementAvailable_.notify_one(); + } + } + } + + bool Dequeue(size_t& layer, + bool& isLast) + { + boost::mutex::scoped_lock lock(mutex_); + + // WARNING: Do NOT use "timed_wait" on condition variables, as + // sleeping is not properly supported by Boost for Google NaCl + while (queue_.empty() && + continue_) + { + elementAvailable_.wait(lock); + } + + if (!continue_) + { + return false; + } + + layer = queue_.front(); + layersToUpdate_[layer] = false; + queue_.pop_front(); + + isLast = queue_.empty(); + + return true; + } + }; + + + class LayeredSceneWidget::Layer : public ISliceableVolume::IChangeObserver + { + private: + boost::mutex mutex_; + std::auto_ptr<ILayerRendererFactory> factory_; + PendingLayers& layers_; + size_t index_; + std::auto_ptr<RenderStyle> style_; + + public: + Layer(ILayerRendererFactory* factory, + PendingLayers& layers, + size_t index) : + factory_(factory), + layers_(layers), + index_(index) + { + if (factory == NULL) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + } + + virtual void NotifyChange(const OrthancStone::ISliceableVolume&) + { + layers_.InvalidateLayer(index_); + } + + void Start() + { + if (factory_->HasSourceVolume()) + { + factory_->GetSourceVolume().Register(*this); + } + } + + void Stop() + { + if (factory_->HasSourceVolume()) + { + factory_->GetSourceVolume().Unregister(*this); + } + } + + bool GetExtent(double& x1, + double& y1, + double& x2, + double& y2, + const SliceGeometry& displaySlice) + { + boost::mutex::scoped_lock lock(mutex_); + assert(factory_.get() != NULL); + return factory_->GetExtent(x1, y1, x2, y2, displaySlice); + } + + RenderStyle GetStyle() + { + boost::mutex::scoped_lock lock(mutex_); + + if (style_.get() == NULL) + { + return RenderStyle(); + } + else + { + return *style_; + } + } + + void SetStyle(const RenderStyle& style) + { + boost::mutex::scoped_lock lock(mutex_); + style_.reset(new RenderStyle(style)); + } + + + ILayerRenderer* CreateRenderer(const SliceGeometry& displaySlice) + { + boost::mutex::scoped_lock lock(mutex_); + assert(factory_.get() != NULL); + + std::auto_ptr<ILayerRenderer> renderer(factory_->CreateLayerRenderer(displaySlice)); + + if (renderer.get() != NULL && + style_.get() != NULL) + { + renderer->SetLayerStyle(*style_); + } + + return renderer.release(); + } + }; + + + + SliceGeometry LayeredSceneWidget::GetSlice() + { + boost::mutex::scoped_lock lock(sliceMutex_); + return slice_; + } + + + void LayeredSceneWidget::UpdateStep() + { + size_t layer = 0; + bool isLast = true; + if (!pendingLayers_->Dequeue(layer, isLast)) + { + return; + } + + SliceGeometry slice = GetSlice(); + + std::auto_ptr<ILayerRenderer> renderer; + renderer.reset(layers_[layer]->CreateRenderer(slice)); + + if (renderer.get() != NULL) + { + pendingRenderers_->SetRenderer(layer, renderer.release()); + } + else + { + pendingRenderers_->SetRenderer(layer, NULL); + } + + if (isLast) + { + Renderers::Merge(*renderers_, *pendingRenderers_); + NotifyChange(); + } + } + + + bool LayeredSceneWidget::RenderScene(CairoContext& context, + const ViewportGeometry& view) + { + assert(IsStarted()); + return renderers_->RenderScene(context, view); + } + + + LayeredSceneWidget::LayeredSceneWidget() + { + pendingLayers_.reset(new PendingLayers); + SetBackgroundCleared(true); + } + + + LayeredSceneWidget::~LayeredSceneWidget() + { + for (size_t i = 0; i < layers_.size(); i++) + { + assert(layers_[i] != NULL); + delete layers_[i]; + } + } + + + void LayeredSceneWidget::GetSceneExtent(double& x1, + double& y1, + double& x2, + double& y2) + { + boost::mutex::scoped_lock lock(sliceMutex_); + + bool first = true; + + for (size_t i = 0; i < layers_.size(); i++) + { + double ax, ay, bx, by; + + assert(layers_[i] != NULL); + if (layers_[i]->GetExtent(ax, ay, bx, by, slice_)) + { + if (ax > bx) + { + std::swap(ax, bx); + } + + if (ay > by) + { + std::swap(ay, by); + } + + if (first) + { + x1 = ax; + y1 = ay; + x2 = bx; + y2 = by; + first = false; + } + else + { + x1 = std::min(x1, ax); + y1 = std::min(y1, ay); + x2 = std::max(x2, bx); + y2 = std::max(y2, by); + } + } + } + + if (first) + { + x1 = -1; + y1 = -1; + x2 = 1; + y2 = 1; + } + + // Ensure the extent is non-empty + if (x1 >= x2) + { + double tmp = x1; + x1 = tmp - 0.5; + x2 = tmp + 0.5; + } + + if (y1 >= y2) + { + double tmp = y1; + y1 = tmp - 0.5; + y2 = tmp + 0.5; + } + } + + + + ILayerRendererFactory& LayeredSceneWidget::AddLayer(size_t& layerIndex, + ILayerRendererFactory* factory) + { + if (IsStarted()) + { + // Start() has already been invoked + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + + layerIndex = layers_.size(); + layers_.push_back(new Layer(factory, *pendingLayers_, layers_.size())); + + return *factory; + } + + + void LayeredSceneWidget::AddLayer(ILayerRendererFactory* factory) + { + size_t layerIndex; // Ignored + AddLayer(layerIndex, factory); + } + + + RenderStyle LayeredSceneWidget::GetLayerStyle(size_t layer) + { + if (layer >= layers_.size()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + return layers_[layer]->GetStyle(); + } + + + void LayeredSceneWidget::SetLayerStyle(size_t layer, + const RenderStyle& style) + { + if (layer >= layers_.size()) + { + throw Orthanc::OrthancException(Orthanc::ErrorCode_ParameterOutOfRange); + } + + layers_[layer]->SetStyle(style); + + if (renderers_.get() != NULL) + { + renderers_->SetLayerStyle(layer, style); + } + + InvalidateLayer(layer); + } + + + + struct LayeredSceneWidget::SliceChangeFunctor + { + const SliceGeometry& slice_; + + SliceChangeFunctor(const SliceGeometry& slice) : + slice_(slice) + { + } + + void operator() (ISliceObserver& observer, + const LayeredSceneWidget& source) + { + observer.NotifySliceChange(source, slice_); + } + }; + + + void LayeredSceneWidget::SetSlice(const SliceGeometry& slice) + { + { + boost::mutex::scoped_lock lock(sliceMutex_); + slice_ = slice; + } + + InvalidateAllLayers(); + + SliceChangeFunctor functor(slice); + observers_.Notify(this, functor); + } + + + void LayeredSceneWidget::InvalidateLayer(unsigned int layer) + { + pendingLayers_->InvalidateLayer(layer); + //NotifyChange(); // TODO Understand why this makes the SDL engine not update the display subsequently + } + + + void LayeredSceneWidget::InvalidateAllLayers() + { + pendingLayers_->InvalidateAllLayers(); + //NotifyChange(); // TODO Understand why this makes the SDL engine not update the display subsequently + } + + + void LayeredSceneWidget::Start() + { + if (IsStarted()) + { + // Start() has already been invoked + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + + for (size_t i = 0; i < layers_.size(); i++) + { + layers_[i]->Start(); + } + + renderers_.reset(new Renderers(layers_.size())); + pendingRenderers_.reset(new Renderers(layers_.size())); + + pendingLayers_->SetLayerCount(layers_.size()); + + WorldSceneWidget::Start(); + } + + + void LayeredSceneWidget::Stop() + { + if (!IsStarted()) + { + // Stop() has already been invoked + throw Orthanc::OrthancException(Orthanc::ErrorCode_BadSequenceOfCalls); + } + + pendingLayers_->Stop(); + WorldSceneWidget::Stop(); + + renderers_.reset(NULL); + pendingRenderers_.reset(NULL); + + for (size_t i = 0; i < layers_.size(); i++) + { + layers_[i]->Stop(); + } + } +}