mirror of
				https://github.com/ciphervance/supercell-wx.git
				synced 2025-10-31 04:30:05 +00:00 
			
		
		
		
	Refactoring common level 3 product view functions to new parent class
This commit is contained in:
		
							parent
							
								
									41fda1e9cf
								
							
						
					
					
						commit
						1dbc68a7d9
					
				
					 5 changed files with 285 additions and 187 deletions
				
			
		
							
								
								
									
										225
									
								
								scwx-qt/source/scwx/qt/view/level3_product_view.cpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										225
									
								
								scwx-qt/source/scwx/qt/view/level3_product_view.cpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,225 @@ | |||
| #include <scwx/qt/view/level3_product_view.hpp> | ||||
| #include <scwx/common/constants.hpp> | ||||
| #include <scwx/util/threads.hpp> | ||||
| #include <scwx/util/time.hpp> | ||||
| #include <scwx/wsr88d/rpg/digital_radial_data_array_packet.hpp> | ||||
| #include <scwx/wsr88d/rpg/graphic_product_message.hpp> | ||||
| #include <scwx/wsr88d/rpg/radial_data_packet.hpp> | ||||
| 
 | ||||
| #include <boost/log/trivial.hpp> | ||||
| #include <boost/range/irange.hpp> | ||||
| #include <boost/timer/timer.hpp> | ||||
| 
 | ||||
| namespace scwx | ||||
| { | ||||
| namespace qt | ||||
| { | ||||
| namespace view | ||||
| { | ||||
| 
 | ||||
| static const std::string logPrefix_ = "[scwx::qt::view::level3_product_view] "; | ||||
| 
 | ||||
| static constexpr uint16_t RANGE_FOLDED = 1u; | ||||
| 
 | ||||
| class Level3ProductViewImpl | ||||
| { | ||||
| public: | ||||
|    explicit Level3ProductViewImpl(const std::string& product) : | ||||
|        product_ {product}, | ||||
|        graphicMessage_ {nullptr}, | ||||
|        colorTable_ {}, | ||||
|        colorTableLut_ {}, | ||||
|        colorTableMin_ {2}, | ||||
|        colorTableMax_ {254}, | ||||
|        savedColorTable_ {nullptr}, | ||||
|        savedScale_ {0.0f}, | ||||
|        savedOffset_ {0.0f} | ||||
|    { | ||||
|    } | ||||
|    ~Level3ProductViewImpl() = default; | ||||
| 
 | ||||
|    std::string product_; | ||||
| 
 | ||||
|    std::shared_ptr<wsr88d::rpg::GraphicProductMessage> graphicMessage_; | ||||
| 
 | ||||
|    std::shared_ptr<common::ColorTable>    colorTable_; | ||||
|    std::vector<boost::gil::rgba8_pixel_t> colorTableLut_; | ||||
|    uint16_t                               colorTableMin_; | ||||
|    uint16_t                               colorTableMax_; | ||||
| 
 | ||||
|    std::shared_ptr<common::ColorTable> savedColorTable_; | ||||
|    float                               savedScale_; | ||||
|    float                               savedOffset_; | ||||
| }; | ||||
| 
 | ||||
| Level3ProductView::Level3ProductView(const std::string& product) : | ||||
|     p(std::make_unique<Level3ProductViewImpl>(product)) | ||||
| { | ||||
| } | ||||
| Level3ProductView::~Level3ProductView() = default; | ||||
| 
 | ||||
| const std::vector<boost::gil::rgba8_pixel_t>& | ||||
| Level3ProductView::color_table() const | ||||
| { | ||||
|    if (p->colorTableLut_.size() == 0) | ||||
|    { | ||||
|       return RadarProductView::color_table(); | ||||
|    } | ||||
|    else | ||||
|    { | ||||
|       return p->colorTableLut_; | ||||
|    } | ||||
| } | ||||
| 
 | ||||
| uint16_t Level3ProductView::color_table_min() const | ||||
| { | ||||
|    if (p->colorTableLut_.size() == 0) | ||||
|    { | ||||
|       return RadarProductView::color_table_min(); | ||||
|    } | ||||
|    else | ||||
|    { | ||||
|       return p->colorTableMin_; | ||||
|    } | ||||
| } | ||||
| 
 | ||||
| uint16_t Level3ProductView::color_table_max() const | ||||
| { | ||||
|    if (p->colorTableLut_.size() == 0) | ||||
|    { | ||||
|       return RadarProductView::color_table_max(); | ||||
|    } | ||||
|    else | ||||
|    { | ||||
|       return p->colorTableMax_; | ||||
|    } | ||||
| } | ||||
| 
 | ||||
| std::shared_ptr<wsr88d::rpg::GraphicProductMessage> | ||||
| Level3ProductView::graphic_product_message() const | ||||
| { | ||||
|    return p->graphicMessage_; | ||||
| } | ||||
| 
 | ||||
| void Level3ProductView::set_graphic_product_message( | ||||
|    std::shared_ptr<wsr88d::rpg::GraphicProductMessage> gpm) | ||||
| { | ||||
|    p->graphicMessage_ = gpm; | ||||
| } | ||||
| 
 | ||||
| common::RadarProductGroup Level3ProductView::GetRadarProductGroup() const | ||||
| { | ||||
|    return common::RadarProductGroup::Level3; | ||||
| } | ||||
| 
 | ||||
| std::string Level3ProductView::GetRadarProductName() const | ||||
| { | ||||
|    return p->product_; | ||||
| } | ||||
| 
 | ||||
| void Level3ProductView::LoadColorTable( | ||||
|    std::shared_ptr<common::ColorTable> colorTable) | ||||
| { | ||||
|    p->colorTable_ = colorTable; | ||||
|    UpdateColorTable(); | ||||
| } | ||||
| 
 | ||||
| void Level3ProductView::Update() | ||||
| { | ||||
|    util::async([=]() { ComputeSweep(); }); | ||||
| } | ||||
| 
 | ||||
| void Level3ProductView::UpdateColorTable() | ||||
| { | ||||
|    if (p->graphicMessage_ == nullptr || //
 | ||||
|        p->colorTable_ == nullptr ||     //
 | ||||
|        !p->colorTable_->IsValid()) | ||||
|    { | ||||
|       // Nothing to update
 | ||||
|       return; | ||||
|    } | ||||
| 
 | ||||
|    std::shared_ptr<wsr88d::rpg::ProductDescriptionBlock> descriptionBlock = | ||||
|       p->graphicMessage_->description_block(); | ||||
| 
 | ||||
|    if (descriptionBlock == nullptr) | ||||
|    { | ||||
|       // No description block
 | ||||
|       return; | ||||
|    } | ||||
| 
 | ||||
|    float    offset    = descriptionBlock->offset(); | ||||
|    float    scale     = descriptionBlock->scale(); | ||||
|    uint16_t threshold = descriptionBlock->threshold(); | ||||
| 
 | ||||
|    if (p->savedColorTable_ == p->colorTable_ && //
 | ||||
|        p->savedOffset_ == offset &&             //
 | ||||
|        p->savedScale_ == scale) | ||||
|    { | ||||
|       // The color table LUT does not need updated
 | ||||
|       return; | ||||
|    } | ||||
| 
 | ||||
|    // If the threshold is 2, the range min should be set to 1 for range folding
 | ||||
|    uint16_t rangeMin = std::min<uint16_t>(1, threshold); | ||||
|    uint16_t rangeMax = descriptionBlock->number_of_levels(); | ||||
| 
 | ||||
|    boost::integer_range<uint16_t> dataRange = | ||||
|       boost::irange<uint16_t>(rangeMin, rangeMax + 1); | ||||
| 
 | ||||
|    std::vector<boost::gil::rgba8_pixel_t>& lut = p->colorTableLut_; | ||||
|    lut.resize(rangeMax - rangeMin + 1); | ||||
|    lut.shrink_to_fit(); | ||||
| 
 | ||||
|    std::for_each(std::execution::par_unseq, | ||||
|                  dataRange.begin(), | ||||
|                  dataRange.end(), | ||||
|                  [&](uint16_t i) | ||||
|                  { | ||||
|                     if (i == RANGE_FOLDED && threshold > RANGE_FOLDED) | ||||
|                     { | ||||
|                        lut[i - *dataRange.begin()] = p->colorTable_->rf_color(); | ||||
|                     } | ||||
|                     else | ||||
|                     { | ||||
|                        float f; | ||||
| 
 | ||||
|                        // Different products use different scale/offset formulas
 | ||||
|                        switch (descriptionBlock->product_code()) | ||||
|                        { | ||||
|                        case 159: | ||||
|                        case 161: | ||||
|                        case 163: | ||||
|                        case 167: | ||||
|                        case 168: | ||||
|                        case 170: | ||||
|                        case 172: | ||||
|                        case 173: | ||||
|                        case 174: | ||||
|                        case 175: | ||||
|                        case 176: | ||||
|                           f = (i - offset) / scale; | ||||
|                           break; | ||||
| 
 | ||||
|                        default: | ||||
|                           f = i * scale + offset; | ||||
|                           break; | ||||
|                        } | ||||
| 
 | ||||
|                        lut[i - *dataRange.begin()] = p->colorTable_->Color(f); | ||||
|                     } | ||||
|                  }); | ||||
| 
 | ||||
|    p->colorTableMin_ = rangeMin; | ||||
|    p->colorTableMax_ = rangeMax; | ||||
| 
 | ||||
|    p->savedColorTable_ = p->colorTable_; | ||||
|    p->savedOffset_     = offset; | ||||
|    p->savedScale_      = scale; | ||||
| 
 | ||||
|    emit ColorTableUpdated(); | ||||
| } | ||||
| 
 | ||||
| } // namespace view
 | ||||
| } // namespace qt
 | ||||
| } // namespace scwx
 | ||||
							
								
								
									
										52
									
								
								scwx-qt/source/scwx/qt/view/level3_product_view.hpp
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								scwx-qt/source/scwx/qt/view/level3_product_view.hpp
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,52 @@ | |||
| #pragma once | ||||
| 
 | ||||
| #include <scwx/common/color_table.hpp> | ||||
| #include <scwx/common/products.hpp> | ||||
| #include <scwx/qt/view/radar_product_view.hpp> | ||||
| #include <scwx/wsr88d/rpg/graphic_product_message.hpp> | ||||
| 
 | ||||
| #include <memory> | ||||
| #include <vector> | ||||
| 
 | ||||
| namespace scwx | ||||
| { | ||||
| namespace qt | ||||
| { | ||||
| namespace view | ||||
| { | ||||
| 
 | ||||
| class Level3ProductViewImpl; | ||||
| 
 | ||||
| class Level3ProductView : public RadarProductView | ||||
| { | ||||
|    Q_OBJECT | ||||
| 
 | ||||
| public: | ||||
|    explicit Level3ProductView(const std::string& product); | ||||
|    ~Level3ProductView(); | ||||
| 
 | ||||
|    const std::vector<boost::gil::rgba8_pixel_t>& color_table() const override; | ||||
|    uint16_t color_table_min() const override; | ||||
|    uint16_t color_table_max() const override; | ||||
| 
 | ||||
|    void LoadColorTable(std::shared_ptr<common::ColorTable> colorTable) override; | ||||
|    void Update() override; | ||||
| 
 | ||||
|    common::RadarProductGroup GetRadarProductGroup() const override; | ||||
|    std::string               GetRadarProductName() const override; | ||||
| 
 | ||||
| protected: | ||||
|    std::shared_ptr<wsr88d::rpg::GraphicProductMessage> | ||||
|         graphic_product_message() const; | ||||
|    void set_graphic_product_message( | ||||
|       std::shared_ptr<wsr88d::rpg::GraphicProductMessage> gpm); | ||||
| 
 | ||||
|    void UpdateColorTable() override; | ||||
| 
 | ||||
| private: | ||||
|    std::unique_ptr<Level3ProductViewImpl> p; | ||||
| }; | ||||
| 
 | ||||
| } // namespace view
 | ||||
| } // namespace qt
 | ||||
| } // namespace scwx
 | ||||
|  | @ -3,7 +3,6 @@ | |||
| #include <scwx/util/threads.hpp> | ||||
| #include <scwx/util/time.hpp> | ||||
| #include <scwx/wsr88d/rpg/digital_radial_data_array_packet.hpp> | ||||
| #include <scwx/wsr88d/rpg/graphic_product_message.hpp> | ||||
| #include <scwx/wsr88d/rpg/radial_data_packet.hpp> | ||||
| 
 | ||||
| #include <boost/log/trivial.hpp> | ||||
|  | @ -32,19 +31,11 @@ public: | |||
|        product_ {product}, | ||||
|        radarProductManager_ {radarProductManager}, | ||||
|        selectedTime_ {}, | ||||
|        graphicMessage_ {nullptr}, | ||||
|        latitude_ {}, | ||||
|        longitude_ {}, | ||||
|        range_ {}, | ||||
|        vcp_ {}, | ||||
|        sweepTime_ {}, | ||||
|        colorTable_ {}, | ||||
|        colorTableLut_ {}, | ||||
|        colorTableMin_ {2}, | ||||
|        colorTableMax_ {254}, | ||||
|        savedColorTable_ {nullptr}, | ||||
|        savedScale_ {0.0f}, | ||||
|        savedOffset_ {0.0f} | ||||
|        sweepTime_ {} | ||||
|    { | ||||
|    } | ||||
|    ~Level3RadialViewImpl() = default; | ||||
|  | @ -54,8 +45,6 @@ public: | |||
| 
 | ||||
|    std::chrono::system_clock::time_point selectedTime_; | ||||
| 
 | ||||
|    std::shared_ptr<wsr88d::rpg::GraphicProductMessage> graphicMessage_; | ||||
| 
 | ||||
|    std::vector<float>   vertices_; | ||||
|    std::vector<uint8_t> dataMoments8_; | ||||
| 
 | ||||
|  | @ -65,62 +54,17 @@ public: | |||
|    uint16_t vcp_; | ||||
| 
 | ||||
|    std::chrono::system_clock::time_point sweepTime_; | ||||
| 
 | ||||
|    std::shared_ptr<common::ColorTable>    colorTable_; | ||||
|    std::vector<boost::gil::rgba8_pixel_t> colorTableLut_; | ||||
|    uint16_t                               colorTableMin_; | ||||
|    uint16_t                               colorTableMax_; | ||||
| 
 | ||||
|    std::shared_ptr<common::ColorTable> savedColorTable_; | ||||
|    float                               savedScale_; | ||||
|    float                               savedOffset_; | ||||
| }; | ||||
| 
 | ||||
| Level3RadialView::Level3RadialView( | ||||
|    const std::string&                            product, | ||||
|    std::shared_ptr<manager::RadarProductManager> radarProductManager) : | ||||
|     Level3ProductView(product), | ||||
|     p(std::make_unique<Level3RadialViewImpl>(product, radarProductManager)) | ||||
| { | ||||
| } | ||||
| Level3RadialView::~Level3RadialView() = default; | ||||
| 
 | ||||
| const std::vector<boost::gil::rgba8_pixel_t>& | ||||
| Level3RadialView::color_table() const | ||||
| { | ||||
|    if (p->colorTableLut_.size() == 0) | ||||
|    { | ||||
|       return RadarProductView::color_table(); | ||||
|    } | ||||
|    else | ||||
|    { | ||||
|       return p->colorTableLut_; | ||||
|    } | ||||
| } | ||||
| 
 | ||||
| uint16_t Level3RadialView::color_table_min() const | ||||
| { | ||||
|    if (p->colorTableLut_.size() == 0) | ||||
|    { | ||||
|       return RadarProductView::color_table_min(); | ||||
|    } | ||||
|    else | ||||
|    { | ||||
|       return p->colorTableMin_; | ||||
|    } | ||||
| } | ||||
| 
 | ||||
| uint16_t Level3RadialView::color_table_max() const | ||||
| { | ||||
|    if (p->colorTableLut_.size() == 0) | ||||
|    { | ||||
|       return RadarProductView::color_table_max(); | ||||
|    } | ||||
|    else | ||||
|    { | ||||
|       return p->colorTableMax_; | ||||
|    } | ||||
| } | ||||
| 
 | ||||
| float Level3RadialView::range() const | ||||
| { | ||||
|    return p->range_; | ||||
|  | @ -141,16 +85,6 @@ const std::vector<float>& Level3RadialView::vertices() const | |||
|    return p->vertices_; | ||||
| } | ||||
| 
 | ||||
| common::RadarProductGroup Level3RadialView::GetRadarProductGroup() const | ||||
| { | ||||
|    return common::RadarProductGroup::Level3; | ||||
| } | ||||
| 
 | ||||
| std::string Level3RadialView::GetRadarProductName() const | ||||
| { | ||||
|    return p->product_; | ||||
| } | ||||
| 
 | ||||
| std::tuple<const void*, size_t, size_t> Level3RadialView::GetMomentData() const | ||||
| { | ||||
|    const void* data; | ||||
|  | @ -164,114 +98,11 @@ std::tuple<const void*, size_t, size_t> Level3RadialView::GetMomentData() const | |||
|    return std::tie(data, dataSize, componentSize); | ||||
| } | ||||
| 
 | ||||
| void Level3RadialView::LoadColorTable( | ||||
|    std::shared_ptr<common::ColorTable> colorTable) | ||||
| { | ||||
|    p->colorTable_ = colorTable; | ||||
|    UpdateColorTable(); | ||||
| } | ||||
| 
 | ||||
| void Level3RadialView::SelectTime(std::chrono::system_clock::time_point time) | ||||
| { | ||||
|    p->selectedTime_ = time; | ||||
| } | ||||
| 
 | ||||
| void Level3RadialView::Update() | ||||
| { | ||||
|    util::async([=]() { ComputeSweep(); }); | ||||
| } | ||||
| 
 | ||||
| void Level3RadialView::UpdateColorTable() | ||||
| { | ||||
|    if (p->graphicMessage_ == nullptr || //
 | ||||
|        p->colorTable_ == nullptr ||     //
 | ||||
|        !p->colorTable_->IsValid()) | ||||
|    { | ||||
|       // Nothing to update
 | ||||
|       return; | ||||
|    } | ||||
| 
 | ||||
|    std::shared_ptr<wsr88d::rpg::ProductDescriptionBlock> descriptionBlock = | ||||
|       p->graphicMessage_->description_block(); | ||||
| 
 | ||||
|    if (descriptionBlock == nullptr) | ||||
|    { | ||||
|       // No description block
 | ||||
|       return; | ||||
|    } | ||||
| 
 | ||||
|    float    offset    = descriptionBlock->offset(); | ||||
|    float    scale     = descriptionBlock->scale(); | ||||
|    uint16_t threshold = descriptionBlock->threshold(); | ||||
| 
 | ||||
|    if (p->savedColorTable_ == p->colorTable_ && //
 | ||||
|        p->savedOffset_ == offset &&             //
 | ||||
|        p->savedScale_ == scale) | ||||
|    { | ||||
|       // The color table LUT does not need updated
 | ||||
|       return; | ||||
|    } | ||||
| 
 | ||||
|    // If the threshold is 2, the range min should be set to 1 for range folding
 | ||||
|    uint16_t rangeMin = std::min<uint16_t>(1, threshold); | ||||
|    uint16_t rangeMax = descriptionBlock->number_of_levels(); | ||||
| 
 | ||||
|    boost::integer_range<uint16_t> dataRange = | ||||
|       boost::irange<uint16_t>(rangeMin, rangeMax + 1); | ||||
| 
 | ||||
|    std::vector<boost::gil::rgba8_pixel_t>& lut = p->colorTableLut_; | ||||
|    lut.resize(rangeMax - rangeMin + 1); | ||||
|    lut.shrink_to_fit(); | ||||
| 
 | ||||
|    std::for_each(std::execution::par_unseq, | ||||
|                  dataRange.begin(), | ||||
|                  dataRange.end(), | ||||
|                  [&](uint16_t i) | ||||
|                  { | ||||
|                     if (i == RANGE_FOLDED && threshold > RANGE_FOLDED) | ||||
|                     { | ||||
|                        lut[i - *dataRange.begin()] = p->colorTable_->rf_color(); | ||||
|                     } | ||||
|                     else | ||||
|                     { | ||||
|                        float f; | ||||
| 
 | ||||
|                        // Different products use different scale/offset formulas
 | ||||
|                        switch (descriptionBlock->product_code()) | ||||
|                        { | ||||
|                        case 159: | ||||
|                        case 161: | ||||
|                        case 163: | ||||
|                        case 167: | ||||
|                        case 168: | ||||
|                        case 170: | ||||
|                        case 172: | ||||
|                        case 173: | ||||
|                        case 174: | ||||
|                        case 175: | ||||
|                        case 176: | ||||
|                           f = (i - offset) / scale; | ||||
|                           break; | ||||
| 
 | ||||
|                        default: | ||||
|                           f = i * scale + offset; | ||||
|                           break; | ||||
|                        } | ||||
| 
 | ||||
|                        lut[i - *dataRange.begin()] = p->colorTable_->Color(f); | ||||
|                     } | ||||
|                  }); | ||||
| 
 | ||||
|    p->colorTableMin_ = rangeMin; | ||||
|    p->colorTableMax_ = rangeMax; | ||||
| 
 | ||||
|    p->savedColorTable_ = p->colorTable_; | ||||
|    p->savedOffset_     = offset; | ||||
|    p->savedScale_      = scale; | ||||
| 
 | ||||
|    emit ColorTableUpdated(); | ||||
| } | ||||
| 
 | ||||
| void Level3RadialView::ComputeSweep() | ||||
| { | ||||
|    BOOST_LOG_TRIVIAL(debug) << logPrefix_ << "ComputeSweep()"; | ||||
|  | @ -293,12 +124,12 @@ void Level3RadialView::ComputeSweep() | |||
|          << logPrefix_ << "Graphic Product Message not found"; | ||||
|       return; | ||||
|    } | ||||
|    else if (gpm == p->graphicMessage_) | ||||
|    else if (gpm == graphic_product_message()) | ||||
|    { | ||||
|       // Skip if this is the message we previously processed
 | ||||
|       return; | ||||
|    } | ||||
|    p->graphicMessage_ = gpm; | ||||
|    set_graphic_product_message(gpm); | ||||
| 
 | ||||
|    // A message with radial data should have a Product Description Block and
 | ||||
|    // Product Symbology Block
 | ||||
|  |  | |||
|  | @ -1,9 +1,7 @@ | |||
| #pragma once | ||||
| 
 | ||||
| #include <scwx/common/color_table.hpp> | ||||
| #include <scwx/common/products.hpp> | ||||
| #include <scwx/qt/view/level3_product_view.hpp> | ||||
| #include <scwx/qt/manager/radar_product_manager.hpp> | ||||
| #include <scwx/qt/view/radar_product_view.hpp> | ||||
| 
 | ||||
| #include <chrono> | ||||
| #include <memory> | ||||
|  | @ -18,7 +16,7 @@ namespace view | |||
| 
 | ||||
| class Level3RadialViewImpl; | ||||
| 
 | ||||
| class Level3RadialView : public RadarProductView | ||||
| class Level3RadialView : public Level3ProductView | ||||
| { | ||||
|    Q_OBJECT | ||||
| 
 | ||||
|  | @ -28,29 +26,19 @@ public: | |||
|       std::shared_ptr<manager::RadarProductManager> radarProductManager); | ||||
|    ~Level3RadialView(); | ||||
| 
 | ||||
|    const std::vector<boost::gil::rgba8_pixel_t>& color_table() const override; | ||||
|    uint16_t                              color_table_min() const override; | ||||
|    uint16_t                              color_table_max() const override; | ||||
|    float                                 range() const override; | ||||
|    std::chrono::system_clock::time_point sweep_time() const override; | ||||
|    uint16_t                              vcp() const override; | ||||
|    const std::vector<float>&             vertices() const override; | ||||
| 
 | ||||
|    void LoadColorTable(std::shared_ptr<common::ColorTable> colorTable) override; | ||||
|    void SelectTime(std::chrono::system_clock::time_point time) override; | ||||
|    void Update() override; | ||||
| 
 | ||||
|    common::RadarProductGroup GetRadarProductGroup() const override; | ||||
|    std::string               GetRadarProductName() const override; | ||||
|    std::tuple<const void*, size_t, size_t> GetMomentData() const override; | ||||
| 
 | ||||
|    static std::shared_ptr<Level3RadialView> | ||||
|    Create(const std::string&                            product, | ||||
|           std::shared_ptr<manager::RadarProductManager> radarProductManager); | ||||
| 
 | ||||
| protected: | ||||
|    void UpdateColorTable() override; | ||||
| 
 | ||||
| protected slots: | ||||
|    void ComputeSweep() override; | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Dan Paulat
						Dan Paulat